diff --git a/.gitignore b/.gitignore index 98b73cb7cb4..c7ed9df2f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,17 @@ /captures *.apk build/ -.idea/ +.idea/* +!.idea/codeStyles/ app/src/main/jniLibs full/ debug/ release/ app/com.crashlytics.settings.json -app/session_analytics.tap \ No newline at end of file +app/session_analytics.tap +.project +.settings/org.eclipse.buildship.core.prefs +app/.classpath +app/.settings/org.eclipse.buildship.core.prefs +wear/.classpath +wear/.settings/org.eclipse.buildship.core.prefs diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000000..d1c3d63de51 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,134 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000000..79ee123c2b2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 5c6309f817d..165a1eff9a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,25 @@ language: android jdk: oraclejdk8 +dist: trusty env: matrix: - - ANDROID_TARGET=android-23 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow + - ANDROID_TARGET=android-28 ANDROID_ABI=x86 org.gradle.jvmargs=-XX:-OmitStackTraceInFastThrow android: components: - platform-tools - tools - - build-tools-27.0.2 - - android-23 + - build-tools-28.0.3 + - android-28 - extra-google-m2repository - extra-android-m2repository - extra-google-google_play_services before_install: -- yes | sdkmanager "platforms;android-27" +#- yes | sdkmanager "platforms;android-28" script: # Unit Test - - ./gradlew -Pcoverage testFullDebugUnitTest jacocoTestFullDebugUnitTestReport + - ./gradlew -Pcoverage -PfirebaseDisable testFullDebugUnitTest jacocoTestFullDebugUnitTestReport after_success: - bash <(curl -s https://codecov.io/bash) @@ -31,4 +32,4 @@ cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - - $HOME/.android/build-cache \ No newline at end of file + - $HOME/.android/build-cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..d15d2440fd8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,48 @@ +This document speciffy hints and good practices for source code contributions. + +AndroidAPS is community effort and all contributions are welcome! If you wish help us improving AndroidAPS - please read and try to adhere to +this guidelines, to make the development and process of change aproval as smooth as possible :) + +General rules +============= + +* There are plenty of ways you can help, some of them are listed on wiki: + https://androidaps.readthedocs.io/en/latest/EN/Getting-Started/How-can-I-help.html +* If you wish to help with documentation or translating: + https://androidaps.readthedocs.io/en/latest/EN/translations.html + +Development guidelines +====================== + +Coding convetions +----------------- +1. Use Android Studio with default indents (4 chars, use spaces) +2. Use autoformat feature CTRL-ALT-L in every changed file before commit + +Commiting Changes / Pull Requests +--------------------------------- + +1. Make fork of repository on github +2. Create separate branch for each feature, branch from most recent dev +3. Commit all changes to your fork +4. When ready, rebase on top of dev and make pull request to main repo + +Naming Conventions for Pull Requests / Branches +----------------------------------------------- + +TODO + +Translations +------------ + +* If possible, always use Android translation mechanism (with strings.xml and @strings/id) instead of hardcoded texts +* Provide only English strings - all other languages will be crowd translated via Crowdn https://translations.androidaps.org/ + +Hints +----- + +* Start small, it is easier to review smaller changes that affect fewer parts of code +* Take a look into Issues list (https://github.com/MilosKozak/AndroidAPS/issues) - maybe there is somthing you can fix or implement +* For new features, make sure there is Issue to track progress and have on-topic discussion +* Reach out to community, discuss idea on Gitter (https://gitter.im/MilosKozak/AndroidAPS) +* Speak with other developers to minimise merge conflicts. Find out who worked, working or plan to work on speciffic issue or part of app diff --git a/README.md b/README.md index d1e1e8a47c6..32aef48cd9b 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ dev: [![codecov](https://codecov.io/gh/MilosKozak/AndroidAPS/branch/dev/graph/badge.svg)](https://codecov.io/gh/MilosKozak/AndroidAPS) -[![Donate via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y4LHGJJESAVB8) +[![Donate via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y4LHGJJESAVB8) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 8c153a9e81d..bd7509a670d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,23 +6,28 @@ buildscript { dependencies { classpath 'io.fabric.tools:gradle:1.+' - classpath 'com.dicedmelon.gradle:jacoco-android:0.1.3' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' + classpath 'de.undercouch:gradle-download-task:3.4.3' } } -apply plugin: "com.android.application" -apply plugin: 'kotlin-android-extensions' +apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'com.google.gms.google-services' -apply plugin: "io.fabric" -apply plugin: "jacoco-android" -apply plugin: 'com.jakewharton.butterknife' +apply plugin: 'io.fabric' +apply plugin: 'jacoco-android' +apply plugin: 'de.undercouch.download' + + +jacoco { + toolVersion = "0.8.3" +} ext { - supportLibraryVersion = "27.1.1" + supportLibraryVersion = "28.0.0" ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" - butterknifeVersion = "8.8.1" } @@ -33,7 +38,7 @@ repositories { } def generateGitBuild = { -> - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder() try { def stdout = new ByteArrayOutputStream() exec { @@ -49,7 +54,7 @@ def generateGitBuild = { -> } def generateGitRemote = { -> - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder() try { def stdout = new ByteArrayOutputStream() exec { @@ -65,7 +70,7 @@ def generateGitRemote = { -> } def generateDate = { -> - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder() stringBuilder.append((new Date()).format('yyyy.MM.dd-HH:mm')) return stringBuilder.toString() } @@ -75,7 +80,7 @@ def isMaster = { -> } def allCommited = { -> - StringBuilder stringBuilder = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder() try { def stdout = new ByteArrayOutputStream() exec { @@ -85,7 +90,7 @@ def allCommited = { -> String commitObject = stdout.toString().trim() stringBuilder.append(commitObject) } catch (ignored) { - return false; // NoGitSystemAvailable + return false // NoGitSystemAvailable } return stringBuilder.toString().isEmpty() @@ -97,21 +102,26 @@ tasks.matching { it instanceof Test }.all { } android { - compileSdkVersion 27 + compileSdkVersion 28 defaultConfig { - minSdkVersion 21 - targetSdkVersion 25 + minSdkVersion 23 + targetSdkVersion 28 multiDexEnabled true versionCode 1500 - version "2.3" + version "2.7-omnipod-0.3" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", '"' + generateGitBuild() + '-' + generateDate() + '"' buildConfigField "String", "REMOTE", '"' + generateGitRemote() + '"' buildConfigField "String", "HEAD", '"' + generateGitBuild() + '"' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // if you change minSdkVersion to less than 11, you need to change executeTask for wear + // OMNIPOD: Keep track of what commit from the main repository we're on, these fields aren't actually used anywhere + buildConfigField "String", "DEV_VERSION", '"2.7-dev"' + buildConfigField "String", "DEV_VERSION_COMMIT", '"c3dbd1ec2446647169bb8ee1e3067a96ff8af394"' + buildConfigField "String", "DEV_VERSION_COMMIT_DATE", '"1.3.2020"' // 1st of March + ndk { moduleName "BleCommandUtil" } @@ -138,6 +148,9 @@ android { debug { testCoverageEnabled(project.hasProperty('coverage')) } + firebaseDisable { + System.setProperty("disableFirebase", "true") + } } productFlavors { flavorDimensions "standard" @@ -188,8 +201,15 @@ android { } testOptions { - unitTests.returnDefaultValues = true - unitTests.includeAndroidResources = true + unitTests { + returnDefaultValues = true + includeAndroidResources = true + + all { + maxParallelForks = 10 + forkEvery = 20 + } + } } useLibrary "org.apache.http.legacy" @@ -201,46 +221,48 @@ allprojects { flatDir { dirs 'libs' } + maven { url 'https://jitpack.io' } } } -configurations { - libs -} - dependencies { wearApp project(':wear') implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.google.android.gms:play-services-wearable:16.0.1' - implementation 'com.google.firebase:firebase-core:16.0.8' - implementation("com.crashlytics.sdk.android:crashlytics:2.9.9@aar") { + implementation 'com.google.android.gms:play-services-wearable:17.0.0' + implementation 'com.google.firebase:firebase-core:17.2.1' + implementation 'com.google.firebase:firebase-auth:19.2.0' + implementation 'com.google.firebase:firebase-database:19.2.0' + implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') { transitive = true; } - libs "MilosKozak:danars-support-lib:master@zip" - - implementation "com.android.support:appcompat-v7:${supportLibraryVersion}" - implementation "com.android.support:support-v13:${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.squareup:otto:1.3.7" + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v13:1.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.gridlayout:gridlayout:1.0.0' + implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.percentlayout:percentlayout:1.0.0' + implementation 'com.wdullaer:materialdatetimepicker:4.2.3' + + implementation "io.reactivex.rxjava2:rxandroid:2.1.1" + 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" } - implementation "org.apache.commons:commons-lang3:3.7" - implementation "org.slf4j:slf4j-api:1.7.21" + implementation "org.apache.commons:commons-lang3:3.9" + implementation "org.slf4j:slf4j-api:1.7.29" // Graphview cannot be upgraded implementation "com.jjoe64:graphview:4.0.1" - implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.1.1" - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation "com.joanzapata.iconify:android-iconify-fontawesome:2.2.2" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation(name: "android-edittext-validator-v1.3.4-mod", ext: "aar") + implementation(name: "com.atech-software.android.library.wizardpager-1.1.1", ext: "aar") + implementation 'com.madgag.spongycastle:core:1.58.0.0' implementation("com.google.android:flexbox:0.3.0") { @@ -250,45 +272,73 @@ dependencies { // excluding org.json which is provided by Android exclude group: "org.json", module: "json" } - implementation "com.google.code.gson:gson:2.8.2" - implementation "com.google.guava:guava:24.1-jre" + implementation "com.google.code.gson:gson:2.8.6" + implementation ("com.google.guava:guava:24.1-jre") { + exclude group: "com.google.code.findbugs", module: "jsr305" + } + implementation 'com.google.code.findbugs:jsr305:3.0.2' - implementation "net.danlew:android.joda:2.9.9.1" - implementation "uk.com.robust-it:cloning:1.9.9" + implementation "net.danlew:android.joda:2.10.3" - implementation 'org.mozilla:rhino:1.7.7.2' + implementation 'org.mozilla:rhino:1.7.11' - implementation "com.jakewharton:butterknife:${butterknifeVersion}" - annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}" + implementation 'com.github.DavidProdinger:weekdays-selector:1.1.0' testImplementation "junit:junit:4.12" - testImplementation "org.json:json:20140107" + testImplementation "org.json:json:20190722" testImplementation "org.mockito:mockito-core:2.8.47" 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.9" - testImplementation "com.google.truth:truth:0.39" - testImplementation 'org.robolectric:robolectric:3.8' + testImplementation "joda-time:joda-time:2.10.5" + testImplementation("com.google.truth:truth:0.39") { + exclude group: "com.google.guava", module: "guava" + exclude group: "com.google.code.findbugs", module: "jsr305" + } testImplementation "org.skyscreamer:jsonassert:1.5.0" + testImplementation "org.hamcrest:hamcrest-all:1.3" +/* + testImplementation("uk.org.lidalia:slf4j-test:1.2.0") { + exclude group: "com.google.guava", module: "guava" + } +*/ - androidTestImplementation "org.mockito:mockito-core:2.8.47" - androidTestImplementation "com.google.dexmaker:dexmaker:${dexmakerVersion}" - androidTestImplementation "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + + + // new for tidepool + implementation 'com.squareup.okhttp3:okhttp:4.2.2' + implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2' + implementation "com.squareup.retrofit2:retrofit:2.6.2" + implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.2" + implementation "com.squareup.retrofit2:converter-gson:2.6.2" + + // Phone checker + implementation 'com.scottyab:rootbeer-lib:0.0.7' + + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha03' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.3.0-alpha03' + androidTestImplementation 'com.google.code.findbugs:jsr305:3.0.2' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' } -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) +task downloadZipFile(type: Download) { + src 'https://github.com/MilosKozak/danars-support-lib/archive/master.zip' + dest new File(buildDir, 'danars.zip') +} + +task downloadAndUnzipFile(dependsOn: downloadZipFile, type: Copy) { + from zipTree(downloadZipFile.dest) + def outputDir = file("${buildDir}/unpacked/dist") into outputDir } -task copyLibs(dependsOn: unzip, type: Copy) { + +task copyLibs(dependsOn: downloadAndUnzipFile, type: Copy) { def src = file("${buildDir}/unpacked/dist/danars-support-lib-master") def target = file("src/main/jniLibs/") @@ -299,7 +349,37 @@ task copyLibs(dependsOn: unzip, type: Copy) { task full_clean(type: Delete) { delete file("src/main/jniLibs") } - +/* +// Run 'adb' shell command to clear application data of main app for 'debug' variant +task clearMainAppData(type: Exec) { + // we have to iterate to find the 'debug' variant to obtain a variant reference + android.applicationVariants.all { variant -> + if (variant.name == "fullDebug") { + def applicationId = [variant.mergedFlavor.applicationId, variant.buildType.applicationIdSuffix].findAll().join() + def clearDataCommand = ['adb', 'shell', 'pm', 'clear', applicationId] + println "Clearing application data of ${variant.name} variant: [${clearDataCommand}]" + def stdout = new ByteArrayOutputStream() + exec { + commandLine clearDataCommand + standardOutput = stdout + } + String result = stdout.toString().trim() + if (!result.startsWith("Success")) { + println result + throw new GradleException(clearDataCommand.join(" ")) + } + } + } +} +// Clear Application Data (once) before running instrumentation test +tasks.whenTaskAdded { task -> + // Both of these targets are equivalent today, although in future connectedCheck + // will also include connectedUiAutomatorTest (not implemented yet) + if(task.name == "connectedAndroidTest" || task.name == "connectedCheck"){ + task.dependsOn(clearMainAppData) + } +} +*/ clean.dependsOn full_clean preBuild.dependsOn copyLibs diff --git a/app/google-services.json b/app/google-services.json index 42db6f42895..507c792c936 100644 --- a/app/google-services.json +++ b/app/google-services.json @@ -13,7 +13,12 @@ "package_name": "info.nightscout.aapspumpcontrol" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "477603612366-a925drvlvs7qn7gt73r585erbqto8c79.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDcZpDRMaGjdhihXp531cVYM6LkEL8KbgM" @@ -37,7 +42,12 @@ "package_name": "info.nightscout.androidaps" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "477603612366-a925drvlvs7qn7gt73r585erbqto8c79.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDcZpDRMaGjdhihXp531cVYM6LkEL8KbgM" @@ -61,7 +71,12 @@ "package_name": "info.nightscout.nsclient" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "477603612366-a925drvlvs7qn7gt73r585erbqto8c79.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDcZpDRMaGjdhihXp531cVYM6LkEL8KbgM" @@ -85,7 +100,12 @@ "package_name": "info.nightscout.nsclient2" } }, - "oauth_client": [], + "oauth_client": [ + { + "client_id": "477603612366-a925drvlvs7qn7gt73r585erbqto8c79.apps.googleusercontent.com", + "client_type": 3 + } + ], "api_key": [ { "current_key": "AIzaSyDcZpDRMaGjdhihXp531cVYM6LkEL8KbgM" diff --git a/app/libs/com.atech-software.android.library.wizardpager-1.1.1.aar b/app/libs/com.atech-software.android.library.wizardpager-1.1.1.aar new file mode 100644 index 00000000000..57e2f806bb1 Binary files /dev/null and b/app/libs/com.atech-software.android.library.wizardpager-1.1.1.aar differ diff --git a/app/libs/ustwo-clockwise-debug.aar b/app/libs/ustwo-clockwise-debug.aar new file mode 100644 index 00000000000..8257a991be6 Binary files /dev/null and b/app/libs/ustwo-clockwise-debug.aar differ diff --git a/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java b/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java deleted file mode 100644 index a047e606a91..00000000000 --- a/app/src/androidTest/java/info/nightscout/androidaps/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package info.nightscout.androidaps; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt b/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt new file mode 100644 index 00000000000..cf7118ebd28 --- /dev/null +++ b/app/src/androidTest/java/info/nightscout/androidaps/EspressoHelper.kt @@ -0,0 +1,35 @@ +package info.nightscout.androidaps + +import androidx.test.espresso.ViewAction +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiSelector + +fun ViewInteraction.isDisplayed(): Boolean { + try { + check(matches(ViewMatchers.isDisplayed())) + return true + } catch (e: Throwable) { + return false + } +} + +fun ViewInteraction.waitAndPerform(viewActions: ViewAction): ViewInteraction? { + val startTime = System.currentTimeMillis() + while (!isDisplayed()) { + Thread.sleep(100) + if (System.currentTimeMillis() - startTime >= 5000) { + throw AssertionError("View not visible after 5000 milliseconds") + } + } + return perform(viewActions) +} + +fun clickOkInDialog() { + val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + val button = uiDevice.findObject(UiSelector().clickable(true).checkable(false).index(1)) + if (button.exists() && button.isEnabled) button.click() +} diff --git a/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt b/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt new file mode 100644 index 00000000000..5463fbcd26e --- /dev/null +++ b/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt @@ -0,0 +1,116 @@ +package info.nightscout.androidaps + +import android.os.SystemClock +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.rule.ActivityTestRule +import androidx.test.rule.GrantPermissionRule +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.interfaces.PumpInterface +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin +import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.source.RandomBgPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.isRunningTest +import org.json.JSONObject +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.slf4j.LoggerFactory + +@LargeTest +@RunWith(AndroidJUnit4::class) +class RealPumpTest { + + private val log = LoggerFactory.getLogger(L.CORE) + + companion object { + val pump: PumpInterface = DanaRv2Plugin.getPlugin() + const val R_PASSWORD = 1234 + const val R_SERIAL = "PBB00013LR_P" + } + + private val validProfile = "{\"dia\":\"6\",\"carbratio\":[{\"time\":\"00:00\",\"value\":\"30\"}],\"carbs_hr\":\"20\",\"delay\":\"20\",\"sens\":[{\"time\":\"00:00\",\"value\":\"10\"},{\"time\":\"2:00\",\"value\":\"11\"}],\"timezone\":\"UTC\",\"basal\":[{\"time\":\"00:00\",\"value\":\"0.1\"}],\"target_low\":[{\"time\":\"00:00\",\"value\":\"4\"}],\"target_high\":[{\"time\":\"00:00\",\"value\":\"5\"}],\"startDate\":\"1970-01-01T00:00:00.000Z\",\"units\":\"mmol\"}" + + @Rule + @JvmField + var mActivityTestRule = ActivityTestRule(MainActivity::class.java) + + @Rule + @JvmField + var mGrantPermissionRule: GrantPermissionRule = + GrantPermissionRule.grant( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + + @Before + fun clear() { + SP.clear() + SP.putBoolean(R.string.key_setupwizard_processed, true) + SP.putString(R.string.key_aps_mode, "closed") + MainApp.getDbHelper().resetDatabases() + MainApp.devBranch = false + } + + private fun preparePlugins() { + // Source + RandomBgPlugin.performPluginSwitch(true, PluginType.BGSOURCE) + // Profile + LocalProfilePlugin.performPluginSwitch(true, PluginType.PROFILE) + val profile = Profile(JSONObject(validProfile), Constants.MGDL) + Assert.assertTrue(profile.isValid("Test")) + LocalProfilePlugin.profiles.clear() + LocalProfilePlugin.numOfProfiles = 0 + val singleProfile = LocalProfilePlugin.SingleProfile().copyFrom(profile, "TestProfile") + LocalProfilePlugin.addProfile(singleProfile) + ProfileFunctions.doProfileSwitch(LocalProfilePlugin.createProfileStore(), "TestProfile", 0, 100, 0, DateUtil.now()) + // Insulin + InsulinOrefUltraRapidActingPlugin.getPlugin().performPluginSwitch(true, PluginType.INSULIN) + // Pump + SP.putInt(R.string.key_danar_password, R_PASSWORD) + SP.putString(R.string.key_danar_bt_name, R_SERIAL) + (pump as PluginBase).performPluginSwitch(true, PluginType.PUMP) + // Sensitivity + SensitivityOref1Plugin.getPlugin().performPluginSwitch(true, PluginType.SENSITIVITY) + // APS + OpenAPSSMBPlugin.getPlugin().performPluginSwitch(true, PluginType.APS) + LoopPlugin.getPlugin().performPluginSwitch(true, PluginType.LOOP) + + // Enable common + ActionsPlugin.performPluginSwitch(true, PluginType.GENERAL) + + // Disable unneeded + MainApp.getPluginsList().remove(ObjectivesPlugin) + } + + @Test + fun doTest() { + Assert.assertTrue(isRunningTest()) + preparePlugins() + + while (!pump.isInitialized) { + log.debug("Waiting for initialization") + SystemClock.sleep(1000) + } + + while (true) { + log.debug("Tick") + SystemClock.sleep(1000) + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt b/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt new file mode 100644 index 00000000000..a2742c2a348 --- /dev/null +++ b/app/src/androidTest/java/info/nightscout/androidaps/SetupWizardActivityTest.kt @@ -0,0 +1,231 @@ +package info.nightscout.androidaps + +import android.os.SystemClock +import android.view.View +import android.view.ViewGroup +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withClassName +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withTagValue +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.rule.ActivityTestRule +import androidx.test.rule.GrantPermissionRule +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.source.RandomBgPlugin +import info.nightscout.androidaps.setupwizard.SetupWizardActivity +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.isRunningTest +import org.hamcrest.CoreMatchers.allOf +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers +import org.hamcrest.TypeSafeMatcher +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@LargeTest +@RunWith(AndroidJUnit4::class) +class SetupWizardActivityTest { + + @Rule + @JvmField + var mActivityTestRule = ActivityTestRule(SetupWizardActivity::class.java) + + @Rule + @JvmField + var mGrantPermissionRule: GrantPermissionRule = + GrantPermissionRule.grant( + android.Manifest.permission.ACCESS_FINE_LOCATION, + android.Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + + @Before + fun clear() { + SP.clear() + } +/* + +To run from command line +gradlew connectedFullDebugAndroidTest + +do not run when your production phone is connected !!! + +do this before for running in emulator +adb shell settings put global window_animation_scale 0 & +adb shell settings put global transition_animation_scale 0 & +adb shell settings put global animator_duration_scale 0 & + */ + + @Test + fun setupWizardActivityTest() { + SP.clear() + Assert.assertTrue(isRunningTest()) + // Welcome page + onView(withId(R.id.next_button)).perform(click()) + // Language selection + onView(withText("English")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Agreement page + onView(withText("I UNDERSTAND AND AGREE")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Location permission + var askButton = onView(withText("Ask for permission")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Store permission + askButton = onView(withText("Ask for permission")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withText("OK")).perform(click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Import settings : skip of found + askButton = onView(withText("IMPORT SETTINGS")) + if (askButton.isDisplayed()) { + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Units selection + onView(withText("mmol/L")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).perform(click()) + // Display target selection + onView(withText("4.2")).perform(scrollTo(), ViewActions.replaceText("5")) + onView(withText("10.0")).perform(scrollTo(), ViewActions.replaceText("11")) + onView(withId(R.id.next_button)).perform(click()) + // NSClient + onView(withId(R.id.next_button)).perform(click()) + // Age selection + onView(withText("Adult")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Insulin selection + onView(withText("Ultra-Rapid Oref")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // BG source selection + onView(withText("Random BG")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Profile selection + onView(withText("Local Profile")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Local profile - DIA + onView(withTagValue(Matchers.`is`("LP_DIA"))).perform(scrollTo(), ViewActions.replaceText("6.0")) + // Local profile - IC + onView(withId(R.id.ic_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("IC-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("2"), ViewActions.closeSoftKeyboard()) + // Local profile - ISF + onView(withId(R.id.isf_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("ISF-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("3"), ViewActions.closeSoftKeyboard()) + // Local profile - BAS + onView(withId(R.id.basal_tab)).perform(scrollTo(), click()) + onView(childAtPosition(Matchers.allOf(withId(R.id.localprofile_basal), childAtPosition(withClassName(Matchers.`is`("android.widget.LinearLayout")), 6)), 2)) + .perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("BASAL-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("1.1"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("BASAL-1-1")), isDisplayed())) + .perform(ViewActions.replaceText("1.2"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withId(R.id.timelistedit_time), childAtPosition(childAtPosition(withId(R.id.localprofile_basal), 2), 0))) + .perform(scrollTo(), click()) + onData(Matchers.anything()).inAdapterView(childAtPosition(withClassName(Matchers.`is`("android.widget.PopupWindow\$PopupBackgroundView")), 0)).atPosition(13) + .perform(click()) + // Local profile - TARGET + onView(withId(R.id.target_tab)).perform(scrollTo(), click()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("TARGET-1-0")), isDisplayed())) + .perform(ViewActions.replaceText("6"), ViewActions.closeSoftKeyboard()) + onView(Matchers.allOf(withTagValue(Matchers.`is`("TARGET-2-0")), isDisplayed())) + .perform(ViewActions.replaceText("6.5"), ViewActions.closeSoftKeyboard()) + onView(withText("Save")).perform(scrollTo(), click()) + onView(Matchers.allOf(withId(R.id.localprofile_profileswitch), isDisplayed())) + .perform(scrollTo(), click()) + onView(allOf(withId(R.id.ok), isDisplayed())).perform(click()) + // confirm dialog + //onView(Matchers.allOf(withText("OK"), isDisplayed())).perform(click()) not working on real phone + clickOkInDialog() + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Profile switch + askButton = onView(withText("Do Profile Switch")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(allOf(withId(R.id.ok), isDisplayed())).perform(click()) + // onView(Matchers.allOf(withText("OK"), isDisplayed())).perform(click()) not working on real phone + clickOkInDialog() + while (ProfileFunctions.getInstance().profile == null) SystemClock.sleep(100) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Pump + onView(withText("Virtual Pump")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // APS + onView(withText("OpenAPS SMB")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Open Closed Loop + onView(withText("Closed Loop")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Loop + askButton = onView(withText("Enable loop")) + if (askButton.isDisplayed()) { + askButton.perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + } + // Sensitivity + onView(withText("Sensitivity Oref1")).perform(scrollTo(), click()) + onView(withId(R.id.next_button)).waitAndPerform(click()) + // Objectives + onView(allOf(withText("Start"), isDisplayed())).perform(scrollTo(), click()) + onView(withId(R.id.finish_button)).waitAndPerform(click()) + + // Verify settings + Assert.assertEquals(Constants.MMOL, ProfileFunctions.getSystemUnits()) + Assert.assertEquals(17.0, HardLimits.maxBolus(), 0.0001) // Adult + Assert.assertTrue(RandomBgPlugin.isEnabled(PluginType.BGSOURCE)) + Assert.assertTrue(LocalProfilePlugin.isEnabled(PluginType.PROFILE)) + val p = ProfileFunctions.getInstance().profile + Assert.assertNotNull(p) + Assert.assertEquals(2.0, p!!.ic, 0.0001) + Assert.assertEquals(3.0 * Constants.MMOLL_TO_MGDL, p.isfMgdl, 0.0001) + Assert.assertEquals(1.1, p.getBasalTimeFromMidnight(0), 0.0001) + Assert.assertEquals(6.0 * Constants.MMOLL_TO_MGDL, p.targetLowMgdl, 0.0001) + Assert.assertTrue(VirtualPumpPlugin.getPlugin().isEnabled(PluginType.PUMP)) + Assert.assertTrue(OpenAPSSMBPlugin.getPlugin().isEnabled(PluginType.APS)) + Assert.assertTrue(LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) + Assert.assertTrue(SensitivityOref1Plugin.getPlugin().isEnabled(PluginType.SENSITIVITY)) + Assert.assertTrue(ObjectivesPlugin.objectives[0].isStarted) + } + + private fun childAtPosition( + parentMatcher: Matcher, position: Int): Matcher { + + return object : TypeSafeMatcher() { + override fun describeTo(description: Description) { + description.appendText("Child at position $position in parent ") + parentMatcher.describeTo(description) + } + + public override fun matchesSafely(view: View): Boolean { + val parent = view.parent + return parent is ViewGroup && parentMatcher.matches(parent) + && view == parent.getChildAt(position) + } + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3eaa81ba45a..2b020f56db1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,10 +17,12 @@ + + - + @@ -34,25 +36,27 @@ android:label="@string/app_name" android:roundIcon="${appIconRound}" android:supportsRtl="true" - android:theme="@style/AppTheme.NoActionBar"> + android:theme="@style/AppTheme.Launcher" + android:fullBackupContent="true"> + + - @@ -72,38 +76,38 @@ + + + android:name=".receivers.DataReceiver" + android:enabled="true" + android:exported="true"> - + - + - - - - - + + + - + - + + android:name=".receivers.SmsReceiver" + android:enabled="true" + android:exported="true" + android:permission="android.permission.BROADCAST_SMS"> - + @@ -112,7 +116,7 @@ @@ -120,24 +124,6 @@ - - - - - - - - - - - - @@ -147,7 +133,7 @@ @@ -159,6 +145,10 @@ + + android:exported="false" /> @@ -242,11 +232,11 @@ + android:exported="false" /> + android:exported="false" /> @@ -281,6 +271,41 @@ android:name=".plugins.pump.insight.activities.InsightPairingInformationActivity" android:label="@string/pairing_information" android:theme="@style/AppTheme" /> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/OpenAPSAMA/loggerhelper.js b/app/src/main/assets/OpenAPSAMA/loggerhelper.js index e790f465c8f..81c19b5a38b 100644 --- a/app/src/main/assets/OpenAPSAMA/loggerhelper.js +++ b/app/src/main/assets/OpenAPSAMA/loggerhelper.js @@ -1,12 +1,33 @@ var console = { }; console.error = function error(){ + var s = ''; for (var i = 0, len = arguments.length; i < len; i++) { - console2.log(arguments[i]); + if (i > 0) s = s + ' '; + if (typeof arguments[i] === 'undefined') { + s = s + 'undefined'; + } else if (typeof arguments[i] === 'object') { + s = s + JSON.stringify(arguments[i]); + } else { + s = s + arguments[i].toString(); + } } + s = s + "\n"; + console2.log(s); }; console.log = function log(){ + var s = ''; for (var i = 0, len = arguments.length; i < len; i++) { - console2.log(arguments[i]); + if (i > 0) s = s + ' '; + if (typeof arguments[i] === 'undefined') { + s = s + 'undefined'; + } else if (typeof arguments[i] === 'object') { + s = s + JSON.stringify(arguments[i]); + } else { + s = s + arguments[i].toString(); + } + //console2.log(arguments[i]); } + s = s + "\n"; + console2.log(s); }; diff --git a/app/src/main/assets/revoked_certs.txt b/app/src/main/assets/revoked_certs.txt new file mode 100644 index 00000000000..41177d5667b --- /dev/null +++ b/app/src/main/assets/revoked_certs.txt @@ -0,0 +1,4 @@ +#Demo certificate +51:6D:12:67:4C:27:F4:9B:9F:E5:42:9B:01:B3:98:E4:66:2B:85:B7:A8:DD:70:32:B7:6A:D7:97:9A:0D:97:10 +#Leaked +55:5D:70:C9:BE:10:41:7E:4B:01:A9:C4:C6:44:4A:F8:69:71:35:25:ED:95:23:16:C7:15:E8:EB:C6:08:FC:B1 diff --git a/app/src/main/java/com/squareup/otto/LoggingBus.java b/app/src/main/java/com/squareup/otto/LoggingBus.java deleted file mode 100644 index d9758a9a24e..00000000000 --- a/app/src/main/java/com/squareup/otto/LoggingBus.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.squareup.otto; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.logging.L; - -/** - * Logs events has they're being posted to and dispatched from the event bus. - *

- * A summary of event-receiver calls that occurred so far is logged - * after 10s (after startup) and then again every 60s. - */ -public class LoggingBus extends Bus { - private static Logger log = LoggerFactory.getLogger(L.EVENTS); - - private static long everyMinute = System.currentTimeMillis() + 10 * 1000; - private Map> event2Receiver = new HashMap<>(); - - public LoggingBus(ThreadEnforcer enforcer) { - super(enforcer); - } - - @Override - public void post(Object event) { - if (event instanceof DeadEvent) { - log.debug("Event has no receiver: " + ((DeadEvent) event).event + ", source: " + ((DeadEvent) event).source); - return; - } - - if (!(event instanceof Event)) { - log.error("Posted event not an event class: " + event.getClass()); - } - - log.debug("<<< " + event); - try { - StackTraceElement caller = new Throwable().getStackTrace()[1]; - String className = caller.getClassName(); - className = className.substring(className.lastIndexOf(".") + 1); - log.debug(" source: " + className + "." + caller.getMethodName() + ":" + caller.getLineNumber()); - } catch (RuntimeException e) { - log.debug(" source: "); - } - - try { - super.post(event); - } catch (IllegalStateException ignored) { - } - } - - @Override - protected void dispatch(Object event, EventHandler wrapper) { - try { - log.debug(">>> " + event); - Field methodField = wrapper.getClass().getDeclaredField("method"); - methodField.setAccessible(true); - Method targetMethod = (Method) methodField.get(wrapper); - String className = targetMethod.getDeclaringClass().getSimpleName(); - String methodName = targetMethod.getName(); - String receiverMethod = className + "." + methodName; - log.debug(" receiver: " + receiverMethod); - - String key = event.getClass().getSimpleName(); - if (!event2Receiver.containsKey(key)) event2Receiver.put(key, new HashSet()); - event2Receiver.get(key).add(receiverMethod); - } catch (ReflectiveOperationException e) { - log.debug(" receiver: "); - } - - try { - if (everyMinute < System.currentTimeMillis()) { - log.debug("***************** Event -> receiver pairings seen so far ****************"); - for (Map.Entry> stringSetEntry : event2Receiver.entrySet()) { - log.debug(" " + stringSetEntry.getKey()); - for (String s : stringSetEntry.getValue()) { - log.debug(" -> " + s); - } - } - log.debug("*************************************************************************"); - everyMinute = System.currentTimeMillis() + 60 * 1000; - } - } catch (ConcurrentModificationException ignored) { - } - - super.dispatch(event, wrapper); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index ae0e14c9b69..bb967cdeb67 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -12,13 +12,4 @@ public class Config { public static final boolean PUMPCONTROL = BuildConfig.FLAVOR.equals("pumpcontrol"); public static final boolean PUMPDRIVERS = BuildConfig.FLAVOR.equals("full") || BuildConfig.FLAVOR.equals("pumpcontrol"); - - public static final boolean ACTION = !NSCLIENT; - public static final boolean MDI = !NSCLIENT; - public static final boolean OTHERPROFILES = !NSCLIENT; - public static final boolean SAFETY = !NSCLIENT; - - public static final boolean SMSCOMMUNICATORENABLED = !NSCLIENT; - - } diff --git a/app/src/main/java/info/nightscout/androidaps/Constants.java b/app/src/main/java/info/nightscout/androidaps/Constants.java index 2ecabfe9659..fb322a82394 100644 --- a/app/src/main/java/info/nightscout/androidaps/Constants.java +++ b/app/src/main/java/info/nightscout/androidaps/Constants.java @@ -25,17 +25,17 @@ public class Constants { public static final int hoursToKeepInDatabase = 72; public static final int daysToKeepHistoryInDatabase = 30; - public static final long keepAliveMsecs = 5 * 60 * 1000L; - // SMS COMMUNICATOR public static final long remoteBolusMinDistance = 15 * 60 * 1000L; // Circadian Percentage Profile - public static final int CPP_MIN_PERCENTAGE = 50; + public static final int CPP_MIN_PERCENTAGE = 30; public static final int CPP_MAX_PERCENTAGE = 200; public static final int CPP_MIN_TIMESHIFT = -6; public static final int CPP_MAX_TIMESHIFT = 23; + public static final double MAX_PROFILE_SWITCH_DURATION = 7 * 24 * 60; // [min] ~ 7 days + //DanaR public static final double dailyLimitWarning = 0.95d; @@ -50,6 +50,11 @@ public class Constants { public static final double defaultHypoTTmgdl = 120d; public static final double defaultHypoTTmmol = 6.5d; + public static final double MIN_TT_MGDL = 72d; + public static final double MAX_TT_MGDL = 180d; + public static final double MIN_TT_MMOL = 4d; + public static final double MAX_TT_MMOL = 10d; + //NSClientInternal public static final int MAX_LOG_LINES = 100; @@ -71,4 +76,15 @@ public class Constants { //Storage [MB] public static final long MINIMUM_FREE_SPACE = 200; + // Overview + public static final double LOWMARK = 76.0; + public static final double HIGHMARK = 180.0; + + // STATISTICS + public static final double STATS_TARGET_LOW_MMOL = 3.9; + public static final double STATS_TARGET_HIGH_MMOL = 7.8; + public static final double STATS_RANGE_LOW_MMOL = 3.9; + public static final double STATS_RANGE_HIGH_MMOL = 10.0; + + } diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index 6779b0a4673..c152c415cb5 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -6,17 +6,6 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.PersistableBundle; -import android.os.PowerManager; -import android.support.annotation.Nullable; -import android.support.design.widget.NavigationView; -import android.support.design.widget.TabLayout; -import android.support.v4.app.ActivityCompat; -import android.support.v4.view.ViewPager; -import android.support.v4.widget.DrawerLayout; -import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; import android.text.SpannableString; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; @@ -31,29 +20,38 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.navigation.NavigationView; +import com.google.android.material.tabs.TabLayout; import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.fonts.FontAwesomeModule; -import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.activities.AgreementActivity; import info.nightscout.androidaps.activities.HistoryBrowseActivity; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.activities.PreferencesActivity; import info.nightscout.androidaps.activities.SingleFragmentActivity; -import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.activities.StatsActivity; 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.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtilsKt; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; -import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerUtilsKt; import info.nightscout.androidaps.setupwizard.SetupWizardActivity; import info.nightscout.androidaps.tabs.TabPageAdapter; import info.nightscout.androidaps.utils.AndroidPermission; @@ -62,25 +60,25 @@ import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.PasswordProtection; import info.nightscout.androidaps.utils.SP; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class MainActivity extends AppCompatActivity { - private static Logger log = LoggerFactory.getLogger(L.CORE); +import static info.nightscout.androidaps.utils.EspressoTestHelperKt.isRunningRealPumpTest; - protected PowerManager.WakeLock mWakeLock; +public class MainActivity extends NoSplashAppCompatActivity { + private static Logger log = LoggerFactory.getLogger(L.CORE); + private CompositeDisposable disposable = new CompositeDisposable(); private ActionBarDrawerToggle actionBarDrawerToggle; private MenuItem pluginPreferencesMenuItem; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (L.isEnabled(L.CORE)) - log.debug("onCreate"); - Iconify.with(new FontAwesomeModule()); - LocaleHelper.onCreate(this, "en"); + LocaleHelper.INSTANCE.update(getApplicationContext()); setContentView(R.layout.activity_main); setSupportActionBar(findViewById(R.id.toolbar)); @@ -94,13 +92,7 @@ protected void onCreate(Bundle savedInstanceState) { actionBarDrawerToggle.syncState(); // initialize screen wake lock - onEventPreferenceChange(new EventPreferenceChange(R.string.key_keep_screen_on)); - - doMigrations(); - - registerBus(); - setupTabs(); - setupViews(false); + processPreferenceChange(new EventPreferenceChange(R.string.key_keep_screen_on)); final ViewPager viewPager = findViewById(R.id.pager); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @@ -123,6 +115,42 @@ public void onPageScrollStateChanged(int state) { VersionCheckerUtilsKt.triggerCheckVersion(); FabricPrivacy.setUserStats(); + + setupTabs(); + setupViews(); + + disposable.add(RxBus.INSTANCE + .toObservable(EventRebuildTabs.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + LocaleHelper.INSTANCE.update(getApplicationContext()); + if (event.getRecreate()) { + recreate(); + } else { + setupTabs(); + setupViews(); + } + setWakeLock(); + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::processPreferenceChange, FabricPrivacy::logException) + ); + + if (!SP.getBoolean(R.string.key_setupwizard_processed, false) && !isRunningRealPumpTest()) { + Intent intent = new Intent(this, SetupWizardActivity.class); + startActivity(intent); + } + + AndroidPermission.notifyForStoragePermission(this); + AndroidPermission.notifyForBatteryOptimizationPermission(this); + if (Config.PUMPDRIVERS) { + AndroidPermission.notifyForLocationPermissions(this); + AndroidPermission.notifyForSMSPermissions(this); + AndroidPermission.notifyForSystemWindowPermissions(this); + } } private void checkPluginPreferences(ViewPager viewPager) { @@ -138,86 +166,29 @@ public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable Persista actionBarDrawerToggle.syncState(); } - @Override - protected void onResume() { - super.onResume(); - - if (L.isEnabled(L.CORE)) - log.debug("onResume"); - - if (!SP.getBoolean(R.string.key_setupwizard_processed, false)) { - Intent intent = new Intent(this, SetupWizardActivity.class); - startActivity(intent); - } else { - checkEula(); - } - - AndroidPermission.notifyForStoragePermission(this); - AndroidPermission.notifyForBatteryOptimizationPermission(this); - if (Config.PUMPDRIVERS) { - AndroidPermission.notifyForLocationPermissions(this); - AndroidPermission.notifyForSMSPermissions(this); - } - - MainApp.bus().post(new EventFeatureRunning(EventFeatureRunning.Feature.MAIN)); - } - @Override public void onDestroy() { - if (L.isEnabled(L.CORE)) - log.debug("onDestroy"); - if (mWakeLock != null) - if (mWakeLock.isHeld()) - mWakeLock.release(); super.onDestroy(); + disposable.clear(); } - @Subscribe - public void onEventPreferenceChange(final EventPreferenceChange ev) { - if (ev.isChanged(R.string.key_keep_screen_on)) { - boolean keepScreenOn = SP.getBoolean(R.string.key_keep_screen_on, false); - final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - if (keepScreenOn) { - mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "AndroidAPS:MainActivity_onEventPreferenceChange"); - if (!mWakeLock.isHeld()) - mWakeLock.acquire(); - } else { - if (mWakeLock != null && mWakeLock.isHeld()) - mWakeLock.release(); - } - } + private void setWakeLock() { + boolean keepScreenOn = SP.getBoolean(R.string.key_keep_screen_on, false); + if (keepScreenOn) + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - @Subscribe - public void onStatusEvent(final EventRefreshGui ev) { - String lang = SP.getString(R.string.key_language, "en"); - LocaleHelper.setLocale(getApplicationContext(), lang); - runOnUiThread(() -> { - if (ev.recreate) { - recreate(); - } else { - try { // activity may be destroyed - setupTabs(); - setupViews(true); - } catch (IllegalStateException e) { - log.error("Unhandled exception", e); - } - } - - boolean keepScreenOn = Config.NSCLIENT && SP.getBoolean(R.string.key_keep_screen_on, false); - if (keepScreenOn) - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - else - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - }); + public void processPreferenceChange(final EventPreferenceChange ev) { + if (ev.isChanged(R.string.key_keep_screen_on)) + setWakeLock(); } - private void setupViews(boolean switchToLast) { + private void setupViews() { TabPageAdapter pageAdapter = new TabPageAdapter(getSupportFragmentManager(), this); NavigationView navigationView = findViewById(R.id.navigation_view); - navigationView.setNavigationItemSelectedListener(menuItem -> { - return true; - }); + navigationView.setNavigationItemSelectedListener(menuItem -> true); Menu menu = navigationView.getMenu(); menu.clear(); for (PluginBase p : MainApp.getPluginsList()) { @@ -236,8 +207,8 @@ private void setupViews(boolean switchToLast) { } ViewPager mPager = findViewById(R.id.pager); mPager.setAdapter(pageAdapter); - if (switchToLast) - mPager.setCurrentItem(pageAdapter.getCount() - 1, false); + //if (switchToLast) + // mPager.setCurrentItem(pageAdapter.getCount() - 1, false); checkPluginPreferences(mPager); } @@ -263,78 +234,21 @@ private void setupTabs() { } } - private void registerBus() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); - } - - private void checkEula() { - //SP.removeBoolean(R.string.key_i_understand); - boolean IUnderstand = SP.getBoolean(R.string.key_i_understand, false); - if (!IUnderstand) { - Intent intent = new Intent(getApplicationContext(), AgreementActivity.class); - startActivity(intent); - finish(); - } - } - - private void doMigrations() { - - checkUpgradeToProfileTarget(); - - // guarantee that the unreachable threshold is at least 30 and of type String - // 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; - SP.putString(R.string.key_pump_unreachable_threshold, unreachable_threshold.toString()); - } - - - private void checkUpgradeToProfileTarget() { // TODO: can be removed in the future - boolean oldKeyExists = SP.contains("openapsma_min_bg"); - if (oldKeyExists) { - Profile profile = ProfileFunctions.getInstance().getProfile(); - String oldRange = SP.getDouble("openapsma_min_bg", 0d) + " - " + SP.getDouble("openapsma_max_bg", 0d); - String newRange = ""; - if (profile != null) { - newRange = profile.getTargetLow() + " - " + profile.getTargetHigh(); - } - String message = "Target range is changed in current version.\n\nIt's not taken from preferences but from profile.\n\n!!! REVIEW YOUR SETTINGS !!!"; - message += "\n\nOld settings: " + oldRange; - message += "\nProfile settings: " + newRange; - OKDialog.show(this, "Target range change", message, new Runnable() { - @Override - public void run() { - SP.remove("openapsma_min_bg"); - SP.remove("openapsma_max_bg"); - SP.remove("openapsma_target_bg"); - } - }); - } - } - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (permissions.length != 0) { if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { switch (requestCode) { case AndroidPermission.CASE_STORAGE: //show dialog after permission is granted - AlertDialog.Builder alert = new AlertDialog.Builder(this); - alert.setMessage(R.string.alert_dialog_storage_permission_text); - alert.setPositiveButton(R.string.ok, null); - alert.show(); + OKDialog.show(this, "", MainApp.gs(R.string.alert_dialog_storage_permission_text)); break; case AndroidPermission.CASE_LOCATION: case AndroidPermission.CASE_SMS: case AndroidPermission.CASE_BATTERY: - case AndroidPermission.CASE_PHONESTATE: + case AndroidPermission.CASE_PHONE_STATE: + case AndroidPermission.CASE_SYSTEM_WINDOW: break; } } @@ -403,9 +317,7 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.nav_exit: log.debug("Exiting"); - MainApp.instance().stopKeepAliveService(); - MainApp.bus().post(new EventAppExit()); - MainApp.closeDbHelper(); + RxBus.INSTANCE.send(new EventAppExit()); finish(); System.runFinalization(); System.exit(0); @@ -419,6 +331,14 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivity(i); }, null); return true; +/* + case R.id.nav_survey: + startActivity(new Intent(this, SurveyActivity.class)); + return true; +*/ + case R.id.nav_stats: + startActivity(new Intent(this, StatsActivity.class)); + return true; } return actionBarDrawerToggle.onOptionsItemSelected(item); } diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 32594830332..da4ad4a1b3e 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -4,19 +4,20 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.os.SystemClock; -import android.support.annotation.Nullable; -import android.support.annotation.PluralsRes; -import android.support.v4.content.LocalBroadcastManager; + +import androidx.annotation.ColorRes; +import androidx.annotation.PluralsRes; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.crashlytics.android.Crashlytics; import com.google.firebase.analytics.FirebaseAnalytics; import com.j256.ormlite.android.apptools.OpenHelperManager; -import com.squareup.otto.Bus; -import com.squareup.otto.LoggingBus; -import com.squareup.otto.ThreadEnforcer; import net.danlew.android.joda.JodaTimeAndroid; +import org.json.JSONException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +25,7 @@ import java.util.ArrayList; import info.nightscout.androidaps.data.ConstraintChecker; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; @@ -34,23 +36,24 @@ import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin; import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin; import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; +import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin; import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin; -import info.nightscout.androidaps.plugins.general.actions.ActionsFragment; +import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin; +import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin; +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; import info.nightscout.androidaps.plugins.general.food.FoodPlugin; import info.nightscout.androidaps.plugins.general.maintenance.LoggerUtils; import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.nsclient.receivers.AckAlarmReceiver; -import info.nightscout.androidaps.plugins.general.nsclient.receivers.DBAccessReceiver; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.general.wear.WearPlugin; import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; @@ -59,7 +62,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin; import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; @@ -67,13 +69,15 @@ import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.source.SourceDexcomG5Plugin; -import info.nightscout.androidaps.plugins.source.SourceDexcomG6Plugin; +import info.nightscout.androidaps.plugins.source.RandomBgPlugin; +import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin; import info.nightscout.androidaps.plugins.source.SourceEversensePlugin; import info.nightscout.androidaps.plugins.source.SourceGlimpPlugin; import info.nightscout.androidaps.plugins.source.SourceMM640gPlugin; @@ -85,18 +89,20 @@ import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver; import info.nightscout.androidaps.receivers.NSAlarmReceiver; +import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver; import info.nightscout.androidaps.services.Intents; +import info.nightscout.androidaps.utils.ActivityMonitor; import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.LocaleHelper; +import info.nightscout.androidaps.utils.SP; import io.fabric.sdk.android.Fabric; -import static info.nightscout.androidaps.plugins.general.versionChecker.VersionCheckerUtilsKt.triggerCheckVersion; +import static info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtilsKt.triggerCheckVersion; public class MainApp extends Application { private static Logger log = LoggerFactory.getLogger(L.CORE); - private static KeepAliveReceiver keepAliveReceiver; - private static Bus sBus; private static MainApp sInstance; public static Resources sResources; @@ -108,10 +114,8 @@ public class MainApp extends Application { private static ArrayList pluginsList = null; private static DataReceiver dataReceiver = new DataReceiver(); - private static NSAlarmReceiver alarmReciever = new NSAlarmReceiver(); - private static AckAlarmReceiver ackAlarmReciever = new AckAlarmReceiver(); - private static DBAccessReceiver dbAccessReciever = new DBAccessReceiver(); - private LocalBroadcastManager lbm; + private static NSAlarmReceiver alarmReceiver = new NSAlarmReceiver(); + TimeDateOrTZChangeReceiver timeDateOrTZChangeReceiver; public static boolean devBranch; public static boolean engineeringMode; @@ -122,9 +126,19 @@ public void onCreate() { log.debug("onCreate"); sInstance = this; sResources = getResources(); + LocaleHelper.INSTANCE.update(this); sConstraintsChecker = new ConstraintChecker(); sDatabaseHelper = OpenHelperManager.getHelper(sInstance, DatabaseHelper.class); + Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { + if (ex instanceof InternalError) { + // usually the app trying to spawn a thread while being killed + return; + } + + log.error("Uncaught exception crashing app", ex); + }); + try { if (FabricPrivacy.fabricEnabled()) { Fabric.with(this, new Crashlytics()); @@ -133,7 +147,10 @@ public void onCreate() { log.error("Error with Fabric init! " + e); } + registerActivityLifecycleCallbacks(ActivityMonitor.INSTANCE); + mFirebaseAnalytics = FirebaseAnalytics.getInstance(this); + mFirebaseAnalytics.setAnalyticsCollectionEnabled(!Boolean.getBoolean("disableFirebase")); JodaTimeAndroid.init(this); @@ -145,9 +162,7 @@ public void onCreate() { File engineeringModeSemaphore = new File(extFilesDir, "engineering_mode"); engineeringMode = engineeringModeSemaphore.exists() && engineeringModeSemaphore.isFile(); - devBranch = BuildConfig.VERSION.contains("dev"); - - sBus = L.isEnabled(L.EVENTS) && devBranch ? new LoggingBus(ThreadEnforcer.ANY) : new Bus(ThreadEnforcer.ANY); + devBranch = BuildConfig.VERSION.contains("-") || BuildConfig.VERSION.matches(".*[a-zA-Z]+.*"); registerLocalBroadcastReceiver(); @@ -157,9 +172,9 @@ public void onCreate() { if (pluginsList == null) { pluginsList = new ArrayList<>(); // Register all tabs in app here - pluginsList.add(OverviewPlugin.getPlugin()); + pluginsList.add(OverviewPlugin.INSTANCE); pluginsList.add(IobCobCalculatorPlugin.getPlugin()); - if (Config.ACTION) pluginsList.add(ActionsFragment.getPlugin()); + if (!Config.NSCLIENT) pluginsList.add(ActionsPlugin.INSTANCE); pluginsList.add(InsulinOrefRapidActingPlugin.getPlugin()); pluginsList.add(InsulinOrefUltraRapidActingPlugin.getPlugin()); pluginsList.add(InsulinOrefFreePeakPlugin.getPlugin()); @@ -172,39 +187,44 @@ public void onCreate() { if (Config.PUMPDRIVERS) pluginsList.add(DanaRv2Plugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(DanaRSPlugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(LocalInsightPlugin.getPlugin()); - pluginsList.add(CareportalPlugin.getPlugin()); if (Config.PUMPDRIVERS) pluginsList.add(ComboPlugin.getPlugin()); - if (Config.MDI) pluginsList.add(MDIPlugin.getPlugin()); + if (Config.PUMPDRIVERS) pluginsList.add(MedtronicPumpPlugin.getPlugin()); + if (Config.PUMPDRIVERS && engineeringMode) + pluginsList.add(OmnipodPumpPlugin.getPlugin()); + if (!Config.NSCLIENT) pluginsList.add(MDIPlugin.getPlugin()); pluginsList.add(VirtualPumpPlugin.getPlugin()); + if (Config.NSCLIENT) pluginsList.add(CareportalPlugin.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.NSCLIENT) pluginsList.add(LocalProfilePlugin.INSTANCE); pluginsList.add(TreatmentsPlugin.getPlugin()); - if (Config.SAFETY) pluginsList.add(SafetyPlugin.getPlugin()); - if (Config.SAFETY) pluginsList.add(VersionCheckerPlugin.INSTANCE); - if (Config.SAFETY) pluginsList.add(StorageConstraintPlugin.getPlugin()); - if (Config.APS) pluginsList.add(ObjectivesPlugin.getPlugin()); + if (!Config.NSCLIENT) pluginsList.add(SafetyPlugin.getPlugin()); + if (!Config.NSCLIENT) pluginsList.add(VersionCheckerPlugin.INSTANCE); + if (Config.APS) pluginsList.add(StorageConstraintPlugin.getPlugin()); + if (Config.APS) pluginsList.add(SignatureVerifierPlugin.getPlugin()); + if (Config.APS) pluginsList.add(ObjectivesPlugin.INSTANCE); pluginsList.add(SourceXdripPlugin.getPlugin()); pluginsList.add(SourceNSClientPlugin.getPlugin()); pluginsList.add(SourceMM640gPlugin.getPlugin()); pluginsList.add(SourceGlimpPlugin.getPlugin()); - pluginsList.add(SourceDexcomG5Plugin.getPlugin()); - pluginsList.add(SourceDexcomG6Plugin.getPlugin()); + pluginsList.add(SourceDexcomPlugin.INSTANCE); pluginsList.add(SourcePoctechPlugin.getPlugin()); pluginsList.add(SourceTomatoPlugin.getPlugin()); pluginsList.add(SourceEversensePlugin.getPlugin()); - if (Config.SMSCOMMUNICATORENABLED) pluginsList.add(SmsCommunicatorPlugin.getPlugin()); + pluginsList.add(RandomBgPlugin.INSTANCE); + if (!Config.NSCLIENT) pluginsList.add(SmsCommunicatorPlugin.INSTANCE); pluginsList.add(FoodPlugin.getPlugin()); pluginsList.add(WearPlugin.initPlugin(this)); pluginsList.add(StatuslinePlugin.initPlugin(this)); pluginsList.add(PersistentNotificationPlugin.getPlugin()); pluginsList.add(NSClientPlugin.getPlugin()); +// if (engineeringMode) pluginsList.add(TidepoolPlugin.INSTANCE); pluginsList.add(MaintenancePlugin.initPlugin(this)); + pluginsList.add(AutomationPlugin.INSTANCE); pluginsList.add(ConfigBuilderPlugin.getPlugin()); @@ -221,13 +241,40 @@ public void onCreate() { new Thread(() -> { SystemClock.sleep(5000); ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("Initialization", null); - startKeepAliveService(); }).start(); } + + new Thread(() -> KeepAliveReceiver.setAlarm(this)).start(); + doMigrations(); + } + + private void doMigrations() { + + // guarantee that the unreachable threshold is at least 30 and of type String + // Added in 1.57 at 21.01.2018 + int 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; + SP.putString(R.string.key_pump_unreachable_threshold, Integer.toString(unreachable_threshold)); + + // 2.5 -> 2.6 + if (!SP.contains(R.string.key_units)) { + String newUnits = Constants.MGDL; + Profile p = ProfileFunctions.getInstance().getProfile(); + if (p != null && p.getData() != null && p.getData().has("units")) { + try { + newUnits = p.getData().getString("units"); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } + SP.putString(R.string.key_units, newUnits); + } } + private void registerLocalBroadcastReceiver() { - lbm = LocalBroadcastManager.getInstance(this); + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_TREATMENT)); lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_CHANGED_TREATMENT)); lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_REMOVED_TREATMENT)); @@ -242,55 +289,21 @@ private void registerLocalBroadcastReceiver() { lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_CAL)); //register alarms - lbm.registerReceiver(alarmReciever, new IntentFilter(Intents.ACTION_ALARM)); - lbm.registerReceiver(alarmReciever, new IntentFilter(Intents.ACTION_ANNOUNCEMENT)); - lbm.registerReceiver(alarmReciever, new IntentFilter(Intents.ACTION_CLEAR_ALARM)); - lbm.registerReceiver(alarmReciever, new IntentFilter(Intents.ACTION_URGENT_ALARM)); - - //register ack alarm - lbm.registerReceiver(ackAlarmReciever, new IntentFilter(Intents.ACTION_ACK_ALARM)); - - //register dbaccess - lbm.registerReceiver(dbAccessReciever, new IntentFilter(Intents.ACTION_DATABASE)); - } - - private void startKeepAliveService() { - if (keepAliveReceiver == null) { - keepAliveReceiver = new KeepAliveReceiver(); - keepAliveReceiver.setAlarm(this); - } - } - - public void stopKeepAliveService() { - if (keepAliveReceiver != null) - KeepAliveReceiver.cancelAlarm(this); - } - - public static void subscribe(Object subscriber) { - try { - bus().register(subscriber); - } catch (IllegalArgumentException e) { - // already registered - } - } + lbm.registerReceiver(alarmReceiver, new IntentFilter(Intents.ACTION_ALARM)); + lbm.registerReceiver(alarmReceiver, new IntentFilter(Intents.ACTION_ANNOUNCEMENT)); + lbm.registerReceiver(alarmReceiver, new IntentFilter(Intents.ACTION_CLEAR_ALARM)); + lbm.registerReceiver(alarmReceiver, new IntentFilter(Intents.ACTION_URGENT_ALARM)); - public static void unsubscribe(Object subscriber) { - try { - bus().unregister(subscriber); - } catch (IllegalArgumentException e) { - // already unregistered - } - } + this.timeDateOrTZChangeReceiver = new TimeDateOrTZChangeReceiver(); + this.timeDateOrTZChangeReceiver.registerBroadcasts(this); - public static Bus bus() { - return sBus; } - public static String gs(int id) { + public static String gs(@StringRes int id) { return sResources.getString(id); } - public static String gs(int id, Object... args) { + public static String gs(@StringRes int id, Object... args) { return sResources.getString(id, args); } @@ -298,8 +311,8 @@ public static String gq(@PluralsRes int id, int quantity, Object... args) { return sResources.getQuantityString(id, quantity, args); } - public static int gc(int id) { - return sResources.getColor(id); + public static int gc(@ColorRes int id) { + return ContextCompat.getColor(instance(), id); } public static MainApp instance() { @@ -310,13 +323,6 @@ public static DatabaseHelper getDbHelper() { return sDatabaseHelper; } - public static void closeDbHelper() { - if (sDatabaseHelper != null) { - sDatabaseHelper.close(); - sDatabaseHelper = null; - } - } - public static FirebaseAnalytics getFirebaseAnalytics() { return mFirebaseAnalytics; } @@ -387,19 +393,6 @@ public static ArrayList getSpecificPluginsVisibleInListByInterface(C return newList; } - @Nullable - public static T getSpecificPlugin(Class pluginClass) { - if (pluginsList != null) { - for (PluginBase p : pluginsList) { - if (pluginClass.isAssignableFrom(p.getClass())) - return (T) p; - } - } else { - log.error("pluginsList=null"); - } - return null; - } - public static boolean isEngineeringModeOrRelease() { if (!Config.APS) return true; @@ -432,10 +425,16 @@ else if (Config.PUMPCONTROL) public void onTerminate() { if (L.isEnabled(L.CORE)) log.debug("onTerminate"); + + if (timeDateOrTZChangeReceiver != null) + unregisterReceiver(timeDateOrTZChangeReceiver); + unregisterActivityLifecycleCallbacks(ActivityMonitor.INSTANCE); + KeepAliveReceiver.cancelAlarm(this); super.onTerminate(); - if (sDatabaseHelper != null) { - sDatabaseHelper.close(); - sDatabaseHelper = null; - } + } + + public static int dpToPx(int dp) { + float scale = sResources.getDisplayMetrics().density; + return (int) (dp * scale + 0.5f); } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/AgreementActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/AgreementActivity.java deleted file mode 100644 index 32352e13710..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/activities/AgreementActivity.java +++ /dev/null @@ -1,44 +0,0 @@ -package info.nightscout.androidaps.activities; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.CheckBox; - -import info.nightscout.androidaps.MainActivity; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.utils.SP; - -public class AgreementActivity extends Activity { - boolean IUnderstand; - CheckBox agreeCheckBox; - Button saveButton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_agreement); - IUnderstand = SP.getBoolean(R.string.key_i_understand, false); - setContentView(R.layout.activity_agreement); - agreeCheckBox = (CheckBox)findViewById(R.id.agreementCheckBox); - agreeCheckBox.setChecked(IUnderstand); - saveButton = (Button)findViewById(R.id.agreementSaveButton); - addListenerOnButton(); - } - - public void addListenerOnButton() { - saveButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - - SP.putBoolean(R.string.key_i_understand, agreeCheckBox.isChecked()); - - Intent intent = new Intent(getApplicationContext(), MainActivity.class); - startActivity(intent); - finish(); - } - - }); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/BolusProgressHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/BolusProgressHelperActivity.kt new file mode 100644 index 00000000000..fe55cdcdf2a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/BolusProgressHelperActivity.kt @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.activities + +import android.os.Bundle +import info.nightscout.androidaps.dialogs.BolusProgressDialog + +class BolusProgressHelperActivity : DialogAppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + BolusProgressDialog() + .setHelperActivity(this) + .setInsulin(intent.getDoubleExtra("insulin", 0.0)) + .show(supportFragmentManager, "BolusProgress") + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/DialogAppCompatActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/DialogAppCompatActivity.kt new file mode 100644 index 00000000000..a1c6dd12365 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/DialogAppCompatActivity.kt @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.activities + +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import info.nightscout.androidaps.utils.LocaleHelper + +open class DialogAppCompatActivity : AppCompatActivity() { + public override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LocaleHelper.wrap(newBase)) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/ErrorHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/ErrorHelperActivity.kt new file mode 100644 index 00000000000..4cf828825fa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/ErrorHelperActivity.kt @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.activities + +import android.os.Bundle +import info.nightscout.androidaps.R +import info.nightscout.androidaps.dialogs.ErrorDialog +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.utils.SP + +class ErrorHelperActivity : DialogAppCompatActivity() { + + @Override + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val errorDialog = ErrorDialog() + errorDialog.helperActivity = this + errorDialog.status = intent.getStringExtra("status") + errorDialog.sound = intent.getIntExtra("soundid", R.raw.error) + errorDialog.title = intent.getStringExtra("title") + if (intent.hasExtra("clipboardContent")) + errorDialog.clipboardContent = intent.getStringExtra("clipboardContent") + errorDialog.show(supportFragmentManager, "Error") + + if (SP.getBoolean(R.string.key_ns_create_announcements_from_errors, true)) { + NSUpload.uploadError(intent.getStringExtra("status")) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java index c007d1fcede..c0f654bbbb5 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/HistoryBrowseActivity.java @@ -2,9 +2,6 @@ import android.os.Bundle; import android.os.SystemClock; -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; @@ -15,8 +12,10 @@ import android.widget.SeekBar; import android.widget.TextView; +import androidx.appcompat.widget.PopupMenu; +import androidx.core.content.res.ResourcesCompat; + import com.jjoe64.graphview.GraphView; -import com.squareup.otto.Subscribe; import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; import org.slf4j.Logger; @@ -25,49 +24,43 @@ import java.util.Calendar; import java.util.Date; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; -import butterknife.OnLongClick; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.events.EventCustomCalculationFinished; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; import info.nightscout.androidaps.plugins.general.overview.OverviewFragment; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class HistoryBrowseActivity extends AppCompatActivity { +public class HistoryBrowseActivity extends NoSplashAppCompatActivity { private static Logger log = LoggerFactory.getLogger(HistoryBrowseActivity.class); - + private CompositeDisposable disposable = new CompositeDisposable(); ImageButton chartButton; boolean showBasal = true; - boolean showIob, showCob, showDev, showRat, showDevslope; + boolean showIob, showCob, showDev, showRat, showActPrim, showActSec, 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; - @BindView(R.id.overview_iobcalculationprogess) TextView iobCalculationProgressView; private int rangeToDisplay = 24; // for graph @@ -82,11 +75,83 @@ public HistoryBrowseActivity() { } @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_historybrowse); - ButterKnife.bind(this); + buttonDate = findViewById(R.id.historybrowse_date); + buttonZoom = findViewById(R.id.historybrowse_zoom); + bgGraph = findViewById(R.id.historyybrowse_bggraph); + iobGraph = findViewById(R.id.historybrowse_iobgraph); + seekBar = findViewById(R.id.historybrowse_seekBar); + noProfile = findViewById(R.id.historybrowse_noprofile); + iobCalculationProgressView = findViewById(R.id.overview_iobcalculationprogess); + + findViewById(R.id.historybrowse_left).setOnClickListener(v -> { + start -= T.hours(rangeToDisplay).msecs(); + updateGUI("onClickLeft"); + runCalculation("onClickLeft"); + }); + + findViewById(R.id.historybrowse_right).setOnClickListener(v -> { + start += T.hours(rangeToDisplay).msecs(); + updateGUI("onClickRight"); + runCalculation("onClickRight"); + }); + + findViewById(R.id.historybrowse_end).setOnClickListener(v -> { + 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("onClickEnd"); + runCalculation("onClickEnd"); + }); + + findViewById(R.id.historybrowse_zoom).setOnClickListener(v -> { + rangeToDisplay += 6; + rangeToDisplay = rangeToDisplay > 24 ? 6 : rangeToDisplay; + updateGUI("rangeChange"); + }); + + findViewById(R.id.historybrowse_zoom).setOnLongClickListener(v -> { + 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"); + runCalculation("onLongClickZoom"); + return true; + }); + + findViewById(R.id.historybrowse_date).setOnClickListener(v -> { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(start)); + DatePickerDialog dpd = DatePickerDialog.newInstance( + (view, year, monthOfYear, dayOfMonth) -> { + Date date = new Date(0); + date.setYear(year - 1900); + date.setMonth(monthOfYear); + date.setDate(dayOfMonth); + date.setHours(0); + start = date.getTime(); + updateGUI("onClickDate"); + runCalculation("onClickDate"); + }, + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH) + ); + dpd.setThemeDark(true); + dpd.dismissOnPause(true); + dpd.show(getSupportFragmentManager(), "Datepickerdialog"); + }); bgGraph.getGridLabelRenderer().setGridColor(MainApp.gc(R.color.graphgrid)); bgGraph.getGridLabelRenderer().reloadStyles(); @@ -103,14 +168,33 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onPause() { super.onPause(); - MainApp.bus().unregister(this); + disposable.clear(); iobCobCalculatorPlugin.stopCalculation("onPause"); } @Override public void onResume() { super.onResume(); - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + if (event.getCause() == eventCustomCalculationFinished) { + log.debug("EventAutosensCalculationFinished"); + synchronized (HistoryBrowseActivity.this) { + updateGUI("EventAutosensCalculationFinished"); + } + } + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventIobCalculationProgress.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + if (iobCalculationProgressView != null) + iobCalculationProgressView.setText(event.getProgress()); + }, FabricPrivacy::logException) + ); // set start of current day Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); @@ -124,78 +208,6 @@ public void onResume() { updateGUI("onResume"); } - @OnClick(R.id.historybrowse_left) - void onClickLeft() { - start -= T.hours(rangeToDisplay).msecs(); - updateGUI("onClickLeft"); - runCalculation("onClickLeft"); - } - - @OnClick(R.id.historybrowse_right) - void onClickRight() { - start += T.hours(rangeToDisplay).msecs(); - updateGUI("onClickRight"); - runCalculation("onClickRight"); - } - - @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("onClickEnd"); - runCalculation("onClickEnd"); - } - - @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"); - runCalculation("onLongClickZoom"); - return true; - } - - @OnClick(R.id.historybrowse_date) - void onClickDate() { - Calendar calendar = Calendar.getInstance(); - calendar.setTime(new Date(start)); - DatePickerDialog dpd = DatePickerDialog.newInstance( - (view, year, monthOfYear, dayOfMonth) -> { - Date date = new Date(0); - date.setYear(year - 1900); - date.setMonth(monthOfYear); - date.setDate(dayOfMonth); - date.setHours(0); - start = date.getTime(); - updateGUI("onClickDate"); - runCalculation("onClickDate"); - }, - calendar.get(Calendar.YEAR), - calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH) - ); - dpd.setThemeDark(true); - dpd.dismissOnPause(true); - dpd.show(getFragmentManager(), "Datepickerdialog"); - } - private void runCalculation(String from) { long end = start + T.hours(rangeToDisplay).msecs(); iobCobCalculatorPlugin.stopCalculation(from); @@ -203,26 +215,6 @@ private void runCalculation(String from) { iobCobCalculatorPlugin.runCalculation(from, end, true, false, eventCustomCalculationFinished); } - @Subscribe - public void onStatusEvent(final EventAutosensCalculationFinished e) { - if (e.cause == eventCustomCalculationFinished) { - log.debug("EventAutosensCalculationFinished"); - runOnUiThread(() -> { - synchronized (HistoryBrowseActivity.this) { - updateGUI("EventAutosensCalculationFinished"); - } - }); - } - } - - @Subscribe - public void onStatusEvent(final EventIobCalculationProgress e) { - runOnUiThread(() -> { - if (iobCalculationProgressView != null) - iobCalculationProgressView.setText(e.progress); - }); - } - void updateGUI(String from) { log.debug("updateGUI from: " + from); @@ -239,15 +231,23 @@ void updateGUI(String from) { noProfile.setVisibility(View.GONE); } - final String units = profile.getUnits(); - final double lowLine = OverviewPlugin.getPlugin().determineLowLine(units); - final double highLine = OverviewPlugin.getPlugin().determineHighLine(units); + final double lowLine = OverviewPlugin.INSTANCE.determineLowLine(); + final double highLine = OverviewPlugin.INSTANCE.determineHighLine(); buttonDate.setText(DateUtil.dateAndTimeString(start)); buttonZoom.setText(String.valueOf(rangeToDisplay)); final boolean showPrediction = false; + showBasal = SP.getBoolean("hist_showbasals", true); + showIob = SP.getBoolean("hist_showiob", true); + showCob = SP.getBoolean("hist_showcob", true); + showDev = SP.getBoolean("hist_showdeviations", false); + showRat = SP.getBoolean("hist_showratios", false); + showActPrim = SP.getBoolean("hist_showactivityprimary", false); + showActSec = SP.getBoolean("hist_showactivitysecondary", false); + showDevslope = SP.getBoolean("hist_showdevslope", false); + int hoursToFetch; final long toTime; final long fromTime; @@ -285,6 +285,10 @@ void updateGUI(String from) { // set manual x bounds to have nice steps graphData.formatAxis(fromTime, toTime); + if (showActPrim) { + graphData.addActivity(fromTime, toTime, false, 1d); + } + // Treatments graphData.addTreatments(fromTime, toTime); @@ -305,6 +309,7 @@ void updateGUI(String from) { boolean useCobForScale = false; boolean useDevForScale = false; boolean useRatioForScale = false; + boolean useIAForScale = false; boolean useDSForScale = false; if (showIob) { @@ -315,18 +320,22 @@ void updateGUI(String from) { useDevForScale = true; } else if (showRat) { useRatioForScale = true; + } else if (showActSec) { + useIAForScale = true; } else if (showDevslope) { useDSForScale = true; } if (showIob) - secondGraphData.addIob(fromTime, toTime, useIobForScale, 1d); + secondGraphData.addIob(fromTime, toTime, useIobForScale, 1d, showPrediction); 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 (showActSec) + secondGraphData.addActivity(fromTime, toTime, useIAForScale, useIAForScale ? 2d : 1d); if (showDevslope) secondGraphData.addDeviationSlope(fromTime, toTime, useDSForScale, 1d); @@ -337,14 +346,14 @@ void updateGUI(String from) { // do GUI update runOnUiThread(() -> { - if (showIob || showCob || showDev || showRat || showDevslope) { + if (showIob || showCob || showDev || showRat || showActSec || showDevslope) { iobGraph.setVisibility(View.VISIBLE); } else { iobGraph.setVisibility(View.GONE); } // finally enforce drawing of graphs graphData.performUpdate(); - if (showIob || showCob || showDev || showRat || showDevslope) + if (showIob || showCob || showDev || showRat || showActSec || showDevslope) secondGraphData.performUpdate(); }); }).start(); @@ -353,22 +362,37 @@ void updateGUI(String from) { private void setupChartMenu() { chartButton = (ImageButton) findViewById(R.id.overview_chartMenuButton); chartButton.setOnClickListener(v -> { - MenuItem item; + MenuItem item, dividerItem; CharSequence title; + int titleMaxChars = 0; 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(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); 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.ACTPRIM.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_activity)); + title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.activity, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showActPrim); + + dividerItem = popup.getMenu().add(""); + dividerItem.setEnabled(false); + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.IOB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_iob)); title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); s = new SpannableString(title); s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.iob, null)), 0, s.length(), 0); item.setTitle(s); @@ -377,6 +401,7 @@ private void setupChartMenu() { item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.COB.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_cob)); title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); s = new SpannableString(title); s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.cob, null)), 0, s.length(), 0); item.setTitle(s); @@ -385,6 +410,7 @@ private void setupChartMenu() { item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEV.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_deviations)); title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); s = new SpannableString(title); s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.deviations, null)), 0, s.length(), 0); item.setTitle(s); @@ -393,15 +419,27 @@ private void setupChartMenu() { item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.SEN.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_sensitivity)); title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); 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); + item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.ACTSEC.ordinal(), Menu.NONE, MainApp.gs(R.string.overview_show_activity)); + title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); + s = new SpannableString(title); + s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.activity, null)), 0, s.length(), 0); + item.setTitle(s); + item.setCheckable(true); + item.setChecked(showActSec); + + if (MainApp.devBranch) { item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal(), Menu.NONE, "Deviation slope"); title = item.getTitle(); + if (titleMaxChars < title.length()) titleMaxChars = title.length(); s = new SpannableString(title); s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.devslopepos, null)), 0, s.length(), 0); item.setTitle(s); @@ -409,19 +447,27 @@ private void setupChartMenu() { item.setChecked(showDevslope); } + // Fairly good guestimate for required divider text size... + title = new String(new char[titleMaxChars + 10]).replace("\0", "_"); + dividerItem.setTitle(title); + popup.setOnMenuItemClickListener(item1 -> { if (item1.getItemId() == OverviewFragment.CHARTTYPE.BAS.ordinal()) { - showBasal = !item1.isChecked(); + SP.putBoolean("hist_showbasals", !item1.isChecked()); } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.IOB.ordinal()) { - showIob = !item1.isChecked(); + SP.putBoolean("hist_showiob", !item1.isChecked()); } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.COB.ordinal()) { - showCob = !item1.isChecked(); + SP.putBoolean("hist_showcob", !item1.isChecked()); } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.DEV.ordinal()) { - showDev = !item1.isChecked(); + SP.putBoolean("hist_showdeviations", !item1.isChecked()); } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.SEN.ordinal()) { - showRat = !item1.isChecked(); + SP.putBoolean("hist_showratios", !item1.isChecked()); + } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.ACTPRIM.ordinal()) { + SP.putBoolean("hist_showactivityprimary", !item1.isChecked()); + } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.ACTSEC.ordinal()) { + SP.putBoolean("hist_showactivitysecondary", !item1.isChecked()); } else if (item1.getItemId() == OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal()) { - showDevslope = !item1.isChecked(); + SP.putBoolean("hist_showdevslope", !item1.isChecked()); } updateGUI("onGraphCheckboxesCheckedChanged"); return true; diff --git a/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt new file mode 100644 index 00000000000..3f59cd56ef3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.activities + +import android.content.Context +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.LocaleHelper + +open class NoSplashAppCompatActivity : AppCompatActivity() { + public override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.AppTheme_NoActionBar) + super.onCreate(savedInstanceState) + } + + public override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LocaleHelper.wrap(newBase)) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java index aea7bcfca6a..e0407d876f3 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/PreferencesActivity.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.activities; +import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.EditTextPreference; @@ -10,47 +11,58 @@ import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; -import android.text.TextUtils; +import android.preference.SwitchPreference; + +import java.util.Arrays; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.events.EventRefreshGui; +import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; -import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; +import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin; import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; +import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; +import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; +import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; +import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; +import info.nightscout.androidaps.plugins.general.wear.WearPlugin; +import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; +import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.source.SourceDexcomG5Plugin; -import info.nightscout.androidaps.plugins.general.wear.WearPlugin; -import info.nightscout.androidaps.plugins.general.xdripStatusline.StatuslinePlugin; +import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin; import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.SafeParse; public class PreferencesActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { MyPreferenceFragment myPreferenceFragment; @Override protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.AppTheme_NoActionBar); super.onCreate(savedInstanceState); myPreferenceFragment = new MyPreferenceFragment(); Bundle args = new Bundle(); @@ -60,23 +72,47 @@ protected void onCreate(Bundle savedInstanceState) { PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); } + @Override + public void attachBaseContext(Context newBase) { + super.attachBaseContext(LocaleHelper.INSTANCE.wrap(newBase)); + } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - MainApp.bus().post(new EventPreferenceChange(key)); - if (key.equals("language")) { - String lang = sharedPreferences.getString("language", "en"); - LocaleHelper.setLocale(getApplicationContext(), lang); - MainApp.bus().post(new EventRefreshGui(true)); + RxBus.INSTANCE.send(new EventPreferenceChange(key)); + if (key.equals(MainApp.gs(R.string.key_language))) { + RxBus.INSTANCE.send(new EventRebuildTabs(true)); //recreate() does not update language so better close settings finish(); } - if (key.equals("short_tabtitles")) { - MainApp.bus().post(new EventRefreshGui()); + if (key.equals(MainApp.gs(R.string.key_short_tabtitles))) { + RxBus.INSTANCE.send(new EventRebuildTabs()); + } + if (key.equals(MainApp.gs(R.string.key_units))) { + recreate(); + return; } 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); + OKDialog.show(this, MainApp.gs(R.string.configbuilder_sensitivity), MainApp.gs(R.string.sensitivity_warning)); + } + updatePrefSummary(myPreferenceFragment.findPreference(key)); + } + + private static void adjustUnitDependentPrefs(Preference pref) { + // convert preferences values to current units + String[] unitDependent = new String[]{ + MainApp.gs(R.string.key_hypo_target), + MainApp.gs(R.string.key_activity_target), + MainApp.gs(R.string.key_eatingsoon_target), + MainApp.gs(R.string.key_high_mark), + MainApp.gs(R.string.key_low_mark) + }; + if (Arrays.asList(unitDependent).contains(pref.getKey())) { + EditTextPreference editTextPref = (EditTextPreference) pref; + String converted = Profile.toCurrentUnitsString(SafeParse.stringToDouble(editTextPref.getText())); + editTextPref.setSummary(converted); + editTextPref.setText(converted); } - updatePrefSummary(myPreferenceFragment.getPreference(key)); } private static void updatePrefSummary(Preference pref) { @@ -88,15 +124,17 @@ private static void updatePrefSummary(Preference pref) { EditTextPreference editTextPref = (EditTextPreference) pref; if (pref.getKey().contains("password") || pref.getKey().contains("secret")) { pref.setSummary("******"); - } 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) { ((EditTextPreference) pref).setDialogMessage(editTextPref.getDialogMessage()); pref.setSummary(editTextPref.getText()); - } else if (pref.getKey().contains("smscommunicator_allowednumbers") && TextUtils.isEmpty(editTextPref.getText().trim())) { - pref.setSummary(MainApp.gs(R.string.smscommunicator_allowednumbers_summary)); + } else { + for (PluginBase plugin : MainApp.getPluginsList()) { + plugin.updatePreferenceSummary(pref); + } } } + if (pref != null) + adjustUnitDependentPrefs(pref); } public static void initSummary(Preference p) { @@ -139,12 +177,12 @@ public void onCreate(final Bundle savedInstanceState) { if (!Config.NSCLIENT) { addPreferencesFromResource(R.xml.pref_password); } + addPreferencesFromResource(R.xml.pref_general); addPreferencesFromResource(R.xml.pref_age); - addPreferencesFromResource(R.xml.pref_language); addPreferencesFromResource(R.xml.pref_overview); - addPreferencesFromResourceIfEnabled(SourceDexcomG5Plugin.getPlugin(), PluginType.BGSOURCE); + addPreferencesFromResourceIfEnabled(SourceDexcomPlugin.INSTANCE, PluginType.BGSOURCE); addPreferencesFromResourceIfEnabled(CareportalPlugin.getPlugin(), PluginType.GENERAL); addPreferencesFromResourceIfEnabled(SafetyPlugin.getPlugin(), PluginType.CONSTRAINTS); if (Config.APS) { @@ -166,6 +204,7 @@ public void onCreate(final Bundle savedInstanceState) { addPreferencesFromResourceIfEnabled(DanaRSPlugin.getPlugin(), PluginType.PUMP); addPreferencesFromResourceIfEnabled(LocalInsightPlugin.getPlugin(), PluginType.PUMP); addPreferencesFromResourceIfEnabled(ComboPlugin.getPlugin(), PluginType.PUMP); + addPreferencesFromResourceIfEnabled(MedtronicPumpPlugin.getPlugin(), PluginType.PUMP); if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE) || DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE) @@ -182,7 +221,9 @@ public void onCreate(final Bundle savedInstanceState) { addPreferencesFromResourceIfEnabled(InsulinOrefFreePeakPlugin.getPlugin(), PluginType.INSULIN); addPreferencesFromResourceIfEnabled(NSClientPlugin.getPlugin(), PluginType.GENERAL); - addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.getPlugin(), PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(TidepoolPlugin.INSTANCE, PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(SmsCommunicatorPlugin.INSTANCE, PluginType.GENERAL); + addPreferencesFromResourceIfEnabled(AutomationPlugin.INSTANCE, PluginType.GENERAL); addPreferencesFromResource(R.xml.pref_others); addPreferencesFromResource(R.xml.pref_datachoices); @@ -191,18 +232,27 @@ public void onCreate(final Bundle savedInstanceState) { addPreferencesFromResourceIfEnabled(StatuslinePlugin.getPlugin(), PluginType.GENERAL); } - if (Config.NSCLIENT) { - PreferenceScreen scrnAdvancedSettings = (PreferenceScreen)findPreference(getString(R.string.key_advancedsettings)); - if (scrnAdvancedSettings != null) { - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_res_warning))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_res_critical))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_bat_warning))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_statuslights_bat_critical))); - scrnAdvancedSettings.removePreference(getPreference(getString(R.string.key_show_statuslights))); - } + initSummary(getPreferenceScreen()); + + for (PluginBase plugin : MainApp.getPluginsList()) { + plugin.preprocessPreferences(this); } - initSummary(getPreferenceScreen()); + PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump(); + PreferenceScreen localAlertsPreferenceScreen = (PreferenceScreen) findPreference(MainApp.gs(R.string.key_preferences_screen_local_alerts)); + if (activePump != null && localAlertsPreferenceScreen != null && activePump.getPumpDescription().hasFixedUnreachableAlert) { + Preference pumpUnreachableEnabledPreference = findPreference(MainApp.gs(R.string.key_enable_pump_unreachable_alert)); + if (pumpUnreachableEnabledPreference != null) { + ((SwitchPreference) pumpUnreachableEnabledPreference).setChecked(true); + pumpUnreachableEnabledPreference.setEnabled(false); + pumpUnreachableEnabledPreference.setShouldDisableView(true); + } + + Preference pumpUnreachableThresholdPreference = findPreference(MainApp.gs(R.string.key_pump_unreachable_threshold)); + if (pumpUnreachableThresholdPreference != null) { + pumpUnreachableThresholdPreference.setDependency(null); + } + } } @Override @@ -210,9 +260,5 @@ public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("id", id); } - - public Preference getPreference(String key) { - return findPreference(key); - } } } diff --git a/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt new file mode 100644 index 00000000000..f50212dadd9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/RequestDexcomPermissionActivity.kt @@ -0,0 +1,19 @@ +package info.nightscout.androidaps.activities + +import android.os.Bundle +import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin + +class RequestDexcomPermissionActivity : DialogAppCompatActivity() { + + private val requestCode = "AndroidAPS <3".map { it.toInt() }.sum() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestPermissions(arrayOf(SourceDexcomPlugin.PERMISSION), requestCode) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java deleted file mode 100644 index 2a381cd0a83..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.java +++ /dev/null @@ -1,59 +0,0 @@ -package info.nightscout.androidaps.activities; - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.app.AppCompatActivity; -import android.view.Menu; -import android.view.MenuItem; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.utils.PasswordProtection; - -public class SingleFragmentActivity extends AppCompatActivity { - - private PluginBase plugin; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_single_fragment); - - this.plugin = MainApp.getPluginsList().get(getIntent().getIntExtra("plugin", -1)); - setTitle(plugin.getName()); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - - if (savedInstanceState == null) { - getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, - Fragment.instantiate(this, plugin.pluginDescription.getFragmentClass())).commit(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - else if (item.getItemId() == R.id.nav_plugin_preferences) { - PasswordProtection.QueryPassword(this, R.string.settings_password, "settings_password", () -> { - Intent i = new Intent(this, PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - startActivity(i); - }, null); - return true; - } - return false; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (plugin.getPreferencesId() != -1) - getMenuInflater().inflate(R.menu.menu_single_fragment, menu); - return super.onCreateOptionsMenu(menu); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.kt new file mode 100644 index 00000000000..3bf329debe2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/SingleFragmentActivity.kt @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.utils.LocaleHelper +import info.nightscout.androidaps.utils.PasswordProtection + +class SingleFragmentActivity : AppCompatActivity() { + private var plugin: PluginBase? = null + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_single_fragment) + plugin = MainApp.getPluginsList()[intent.getIntExtra("plugin", -1)] + title = plugin?.name + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction().replace(R.id.frame_layout, + supportFragmentManager.fragmentFactory.instantiate(ClassLoader.getSystemClassLoader(), plugin?.pluginDescription?.fragmentClass!!)).commit() + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + finish() + return true + } else if (item.itemId == R.id.nav_plugin_preferences) { + PasswordProtection.QueryPassword(this, R.string.settings_password, "settings_password", Runnable { + val i = Intent(this, PreferencesActivity::class.java) + i.putExtra("id", plugin?.preferencesId) + startActivity(i) + }, null) + return true + } + return false + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + if (plugin?.preferencesId != -1) menuInflater.inflate(R.menu.menu_single_fragment, menu) + return super.onCreateOptionsMenu(menu) + } + + public override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(LocaleHelper.wrap(newBase)) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt new file mode 100644 index 00000000000..d0ac8e3f6f1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/StatsActivity.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.activities + +import android.os.Bundle +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.ActivityMonitor +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.TddCalculator +import info.nightscout.androidaps.utils.TirCalculator +import kotlinx.android.synthetic.main.stats_activity.* + +class StatsActivity : NoSplashAppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.stats_activity) + + stats_tdds.text = TddCalculator.stats() + stats_tir.text = TirCalculator.stats() + stats_activity.text = ActivityMonitor.stats() + + ok.setOnClickListener { finish() } + stats_reset.setOnClickListener { + OKDialog.showConfirmation(this, MainApp.gs(R.string.doyouwantresetstats), Runnable { + ActivityMonitor.reset() + recreate() + }) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt new file mode 100644 index 00000000000..bf07eda9afa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/activities/SurveyActivity.kt @@ -0,0 +1,110 @@ +package info.nightscout.androidaps.activities + +import android.os.Bundle +import android.widget.ArrayAdapter +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.database.FirebaseDatabase +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.defaultProfile.DefaultProfile +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.dialogs.ProfileViewerDialog +import info.nightscout.androidaps.utils.* +import kotlinx.android.synthetic.main.survey_activity.* +import org.slf4j.LoggerFactory + +class SurveyActivity : NoSplashAppCompatActivity() { + private val log = LoggerFactory.getLogger(SurveyActivity::class.java) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.survey_activity) + + survey_id.text = InstanceId.instanceId() + + val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile + val profileList = profileStore?.getProfileList() ?: return + survey_spinner.adapter = ArrayAdapter(this, R.layout.spinner_centered, profileList) + + survey_tdds.text = TddCalculator.stats() + survey_tir.text = TirCalculator.stats() + survey_activity.text = ActivityMonitor.stats() + + survey_profile.setOnClickListener { + val age = SafeParse.stringToDouble(survey_age.text.toString()) + val weight = SafeParse.stringToDouble(survey_weight.text.toString()) + val tdd = SafeParse.stringToDouble(survey_tdd.text.toString()) + if (age < 1 || age > 120) { + ToastUtils.showToastInUiThread(this, R.string.invalidage) + return@setOnClickListener + } + if ((weight < 5 || weight > 150) && tdd == 0.0) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + if ((tdd < 5 || tdd > 150) && weight == 0.0) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + val profile = DefaultProfile().profile(age, tdd, weight, ProfileFunctions.getSystemUnits()) + val args = Bundle() + args.putLong("time", DateUtil.now()) + args.putInt("mode", ProfileViewerDialog.Mode.CUSTOM_PROFILE.ordinal) + args.putString("customProfile", profile.data.toString()) + args.putString("customProfileUnits", profile.units) + args.putString("customProfileName", "Age: $age TDD: $tdd Weight: $weight") + val pvd = ProfileViewerDialog() + pvd.arguments = args + pvd.show(supportFragmentManager, "ProfileViewDialog") + } + + survey_submit.setOnClickListener { + val r = FirebaseRecord() + r.id = InstanceId.instanceId() + r.age = SafeParse.stringToInt(survey_age.text.toString()) + r.weight = SafeParse.stringToInt(survey_weight.text.toString()) + if (r.age < 1 || r.age > 120) { + ToastUtils.showToastInUiThread(this, R.string.invalidage) + return@setOnClickListener + } + if (r.weight < 5 || r.weight > 150) { + ToastUtils.showToastInUiThread(this, R.string.invalidweight) + return@setOnClickListener + } + + if (survey_spinner.selectedItem == null) + return@setOnClickListener + val profileName = survey_spinner.selectedItem.toString() + val specificProfile = profileStore.getSpecificProfile(profileName) + + r.profileJson = specificProfile.toString() + + val auth = FirebaseAuth.getInstance() + auth.signInAnonymously() + .addOnCompleteListener(this) { task -> + if (task.isSuccessful) { + log.debug("signInAnonymously:success") + val user = auth.currentUser // TODO: do we need this, seems unused? + + val database = FirebaseDatabase.getInstance().reference + database.child("survey").child(r.id).setValue(r) + } else { + log.error("signInAnonymously:failure", task.exception) + ToastUtils.showToastInUiThread(this, "Authentication failed.") + //updateUI(null) + } + + // ... + } + finish() + } + } + + inner class FirebaseRecord { + var id = "" + var age: Int = 0 + var weight: Int = 0 + var profileJson = "ghfg" + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java b/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java index e42d31d7c07..03fb034c5cb 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/activities/TDDStatsActivity.java @@ -1,10 +1,8 @@ package info.nightscout.androidaps.activities; -import android.app.Activity; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; import android.text.TextUtils; import android.view.KeyEvent; import android.view.MotionEvent; @@ -18,7 +16,7 @@ import android.widget.TableRow; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.recyclerview.widget.LinearLayoutManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +37,7 @@ import info.nightscout.androidaps.db.TDD; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; @@ -49,11 +48,15 @@ import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.SafeParse; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class TDDStatsActivity extends Activity { +public class TDDStatsActivity extends NoSplashAppCompatActivity { private static Logger log = LoggerFactory.getLogger(TDDStatsActivity.class); + private CompositeDisposable disposable = new CompositeDisposable(); TextView statusView, statsMessage, totalBaseBasal2; EditText totalBaseBasal; @@ -74,13 +77,25 @@ public TDDStatsActivity() { @Override protected void onResume() { super.onResume(); - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(EventPumpStatusChanged.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> statusView.setText(event.getStatus()), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventDanaRSyncStatus.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + log.debug("EventDanaRSyncStatus: " + event.getMessage()); + statusView.setText(event.getMessage()); + }, FabricPrivacy::logException) + ); } @Override protected void onPause() { super.onPause(); - MainApp.bus().unregister(this); + disposable.clear(); } @Override @@ -99,7 +114,7 @@ public boolean dispatchTouchEvent(MotionEvent event) { } @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.danar_statsactivity); getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); @@ -239,7 +254,7 @@ public void run() { statsMessage.setText(MainApp.gs(R.string.danar_stats_warning_Message)); } }); - ConfigBuilderPlugin.getPlugin().getCommandQueue().loadTDDs( new Callback() { + ConfigBuilderPlugin.getPlugin().getCommandQueue().loadTDDs(new Callback() { @Override public void run() { loadDataFromDB(); @@ -399,7 +414,7 @@ public void run() { //cumulative TDDs for (TDD record : historyList) { - if(!historyList.isEmpty() && df.format(new Date(record.date)).equals(df.format(new Date()))) { + if (!historyList.isEmpty() && df.format(new Date(record.date)).equals(df.format(new Date()))) { //Today should not be included continue; } @@ -448,7 +463,7 @@ public void run() { tl.setBackgroundColor(Color.TRANSPARENT); } - if(!historyList.isEmpty() && df.format(new Date(historyList.get(0).date)).equals(df.format(new Date()))) { + if (!historyList.isEmpty() && df.format(new Date(historyList.get(0).date)).equals(df.format(new Date()))) { //Today should not be included historyList.remove(0); } @@ -519,42 +534,17 @@ private void cleanTable(TableLayout table) { } } - @Subscribe - public void onStatusEvent(final EventDanaRSyncStatus s) { - log.debug("EventDanaRSyncStatus: " + s.message); - runOnUiThread( - new Runnable() { - @Override - public void run() { - statusView.setText(s.message); - } - }); - } - - @Subscribe - public void onStatusEvent(final EventPumpStatusChanged c) { - runOnUiThread( - new Runnable() { - @Override - public void run() { - statusView.setText(c.textStatus()); - } - } - ); - } - - public static boolean isOldData(List historyList) { Object activePump = ConfigBuilderPlugin.getPlugin().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(LocalInsightPlugin.class); + PumpInterface dana = DanaRPlugin.getPlugin(); + PumpInterface danaRS = DanaRSPlugin.getPlugin(); + PumpInterface danaV2 = DanaRv2Plugin.getPlugin(); + PumpInterface danaKorean = DanaRKoreanPlugin.getPlugin(); + PumpInterface insight = LocalInsightPlugin.getPlugin(); 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)))))); + 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 index b1267e81bc4..9d7a49493b6 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ConstraintChecker.java @@ -1,11 +1,9 @@ package info.nightscout.androidaps.data; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import java.util.ArrayList; -import javax.annotation.Nonnull; - import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.interfaces.Constraint; 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 66d567d9bd3..efb3b76aea2 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Intervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Intervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; import java.util.ArrayList; import java.util.List; @@ -22,7 +22,7 @@ public Intervals() { rawData = new LongSparseArray(); } - public synchronized Intervals reset() { + public synchronized Intervals reset() { rawData = new LongSparseArray(); return this; } 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 73f75b9c7d4..60e1b210489 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java +++ b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java @@ -1,7 +1,5 @@ package info.nightscout.androidaps.data; -import com.rits.cloning.Cloner; - import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -9,10 +7,12 @@ import java.util.Date; +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.Round; -public class IobTotal { +public class IobTotal implements DataPointWithLabelInterface { private static Logger log = LoggerFactory.getLogger(IobTotal.class); public double iob; @@ -35,8 +35,19 @@ public class IobTotal { public IobTotal copy() { - Cloner cloner = new Cloner(); - return cloner.deepClone(this); + IobTotal i = new IobTotal(time); + i.iob = iob; + i.activity = activity; + i.bolussnooze = bolussnooze; + i.basaliob = basaliob; + i.netbasalinsulin = netbasalinsulin; + i.hightempinsulin = hightempinsulin; + i.lastBolusTime = lastBolusTime; + if (iobWithZeroTemp != null) i.iobWithZeroTemp = iobWithZeroTemp.copy(); + i.netInsulin = netInsulin; + i.netRatio = netRatio; + i.extendedBolusInsulin = extendedBolusInsulin; + return i; } public IobTotal(long time) { @@ -133,4 +144,52 @@ public JSONObject determineBasalJson() { return json; } + // DataPoint interface + + private int color; + + @Override + public double getX() { + return time; + } + + @Override + public double getY() { + return iob; + } + + @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.IOBPREDICTION; + } + + @Override + public float getSize() { + return 0.5f; + } + + @Override + public int getColor() { + return color; + } + + public IobTotal setColor(int color) { + this.color = color; + return this; + } } 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 8e0c286e7ea..c426aede402 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/NonOverlappingIntervals.java @@ -1,10 +1,8 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; -import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.interfaces.Interval; /** @@ -21,7 +19,7 @@ public NonOverlappingIntervals (Intervals other) { rawData = other.rawData.clone(); } - protected synchronized void merge() { + public synchronized void merge() { for (int index = 0; index < rawData.size() - 1; index++) { Interval i = rawData.valueAt(index); long startOfNewer = rawData.valueAt(index + 1).start(); 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 76c2ff36154..5515ea15e1f 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/OverlappingIntervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.interfaces.Interval; 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 23a7e62426d..a84fb405376 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.data; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.json.JSONArray; import org.json.JSONException; @@ -11,18 +11,22 @@ import java.text.DecimalFormat; 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.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.MidnightTime; +import info.nightscout.androidaps.utils.Round; public class Profile { private static Logger log = LoggerFactory.getLogger(Profile.class); @@ -73,6 +77,11 @@ public Profile(JSONObject json, String units) { } } + // Constructor from profileStore JSON + public Profile(JSONObject json) { + init(json, 100, 0); + } + public Profile(JSONObject json, int percentage, int timeshift) { init(json, percentage, timeshift); } @@ -98,8 +107,6 @@ protected void init(JSONObject json, int percentage, int timeshift) { units = json.getString("units").toLowerCase(); if (json.has("dia")) dia = json.getDouble("dia"); - if (json.has("dia")) - dia = json.getDouble("dia"); if (json.has("timezone")) timeZone = TimeZone.getTimeZone(json.getString("timezone")); isf = json.getJSONArray("sens"); @@ -147,7 +154,7 @@ public String getUnits() { return units; } - public TimeZone getTimeZone() { + TimeZone getTimeZone() { return timeZone; } @@ -160,22 +167,23 @@ private LongSparseArray convertToSparseArray(JSONArray array) { double multiplier = getMultiplier(array); LongSparseArray sparse = new LongSparseArray<>(); - for (Integer index = 0; index < array.length(); index++) { + for (int index = 0; index < array.length(); index++) { try { final JSONObject o = array.getJSONObject(index); long tas = 0; try { - tas = getShitfTimeSecs((int) o.getLong("timeAsSeconds")); - } catch (JSONException e) { String time = o.getString("time"); tas = getShitfTimeSecs(DateUtil.toSeconds(time)); + } catch (JSONException e) { //log.debug(">>>>>>>>>>>> Used recalculated timeAsSecons: " + time + " " + tas); + tas = getShitfTimeSecs((int) o.getLong("timeAsSeconds")); } double value = o.getDouble("value") * multiplier; sparse.put(tas, value); - } catch (JSONException e) { + } catch (Exception e) { log.error("Unhandled exception", e); log.error(json.toString()); + FabricPrivacy.logException(e); } } @@ -226,8 +234,10 @@ public synchronized boolean isValid(String from, boolean notify) { for (int index = 0; index < basal_v.size(); index++) { long secondsFromMidnight = basal_v.keyAt(index); if (notify && 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)); + if (Config.APS) { + Notification notification = new Notification(Notification.BASAL_PROFILE_NOT_ALIGNED_TO_HOURS, String.format(MainApp.gs(R.string.basalprofilenotaligned), from), Notification.NORMAL); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } } } } @@ -258,11 +268,11 @@ public synchronized boolean isValid(String from, boolean notify) { } 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))); + RxBus.INSTANCE.send(new EventNewNotification(new Notification(Notification.MINIMAL_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.minimalbasalvaluereplaced), from), Notification.NORMAL))); } protected void sendAboveMaximumNotification(String from) { - MainApp.bus().post(new EventNewNotification(new Notification(Notification.MAXIMUM_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.maximumbasalvaluereplaced), from), Notification.NORMAL))); + RxBus.INSTANCE.send(new EventNewNotification(new Notification(Notification.MAXIMUM_BASAL_VALUE_REPLACED, String.format(MainApp.gs(R.string.maximumbasalvaluereplaced), from), Notification.NORMAL))); } private void validate(LongSparseArray array) { @@ -380,15 +390,15 @@ private String getValuesList(LongSparseArray array, LongSparseArray max) max = value; } return max; @@ -543,22 +633,39 @@ public static double fromMgdlToUnits(double value, String units) { else return value * Constants.MGDL_TO_MMOLL; } - public static double toUnits(Double valueInMgdl, Double valueInMmol, String units) { + public static double fromMmolToUnits(double value, String units) { + if (units.equals(Constants.MMOL)) return value; + else return value * Constants.MMOLL_TO_MGDL; + } + + public static double toUnits(double valueInMgdl, double valueInMmol, String units) { if (units.equals(Constants.MGDL)) return valueInMgdl; else return valueInMmol; } - public static String toUnitsString(Double valueInMgdl, Double valueInMmol, String units) { + public static String toUnitsString(double valueInMgdl, double valueInMmol, String units) { if (units.equals(Constants.MGDL)) return DecimalFormatter.to0Decimal(valueInMgdl); else return DecimalFormatter.to1Decimal(valueInMmol); } - public static String toSignedUnitsString(Double valueInMgdl, Double valueInMmol, String units) { + public static String toSignedUnitsString(double valueInMgdl, double valueInMmol, String units) { if (units.equals(Constants.MGDL)) return (valueInMgdl > 0 ? "+" : "") + DecimalFormatter.to0Decimal(valueInMgdl); else return (valueInMmol > 0 ? "+" : "") + DecimalFormatter.to1Decimal(valueInMmol); } + public static double toCurrentUnits(double anyBg) { + if (anyBg < 32) return fromMmolToUnits(anyBg, ProfileFunctions.getSystemUnits()); + else return fromMgdlToUnits(anyBg, ProfileFunctions.getSystemUnits()); + } + + public static String toCurrentUnitsString(double anyBg) { + if (anyBg < 32) + return toUnitsString(anyBg * Constants.MMOLL_TO_MGDL, anyBg, ProfileFunctions.getSystemUnits()); + else + return toUnitsString(anyBg, anyBg * Constants.MGDL_TO_MMOLL, ProfileFunctions.getSystemUnits()); + } + // targets are stored in mg/dl but profile vary public static String toTargetRangeString(double low, double high, String sourceUnits, String units) { double lowMgdl = toMgdl(low, sourceUnits); @@ -596,4 +703,133 @@ public int getPercentage() { public int getTimeshift() { return timeshift; } + + public Profile convertToNonCustomizedProfile() { + JSONObject o = new JSONObject(); + try { + o.put("units", units); + o.put("dia", dia); + o.put("timezone", timeZone.getID()); + // SENS + JSONArray sens = new JSONArray(); + double lastValue = -1d; + for (int i = 0; i < 24; i++) { + int timeAsSeconds = i * 60 * 60; + double value = getIsfTimeFromMidnight(timeAsSeconds); + if (value != lastValue) { + JSONObject item = new JSONObject(); + String time; + DecimalFormat df = new DecimalFormat("00"); + time = df.format(i) + ":00"; + item.put("time", time); + item.put("timeAsSeconds", timeAsSeconds); + item.put("value", value); + lastValue = value; + sens.put(item); + } + } + o.put("sens", sens); + // CARBRATIO + JSONArray carbratio = new JSONArray(); + lastValue = -1d; + for (int i = 0; i < 24; i++) { + int timeAsSeconds = i * 60 * 60; + double value = getIcTimeFromMidnight(timeAsSeconds); + if (value != lastValue) { + JSONObject item = new JSONObject(); + String time; + DecimalFormat df = new DecimalFormat("00"); + time = df.format(i) + ":00"; + item.put("time", time); + item.put("timeAsSeconds", timeAsSeconds); + item.put("value", value); + lastValue = value; + carbratio.put(item); + } + } + o.put("carbratio", carbratio); + // BASAL + JSONArray basal = new JSONArray(); + lastValue = -1d; + for (int i = 0; i < 24; i++) { + int timeAsSeconds = i * 60 * 60; + double value = getBasalTimeFromMidnight(timeAsSeconds); + if (value != lastValue) { + JSONObject item = new JSONObject(); + String time; + DecimalFormat df = new DecimalFormat("00"); + time = df.format(i) + ":00"; + item.put("time", time); + item.put("timeAsSeconds", timeAsSeconds); + item.put("value", value); + lastValue = value; + basal.put(item); + } + } + o.put("basal", basal); + // TARGET_LOW + JSONArray target_low = new JSONArray(); + lastValue = -1d; + for (int i = 0; i < 24; i++) { + int timeAsSeconds = i * 60 * 60; + double value = getTargetLowTimeFromMidnight(timeAsSeconds); + if (value != lastValue) { + JSONObject item = new JSONObject(); + String time; + DecimalFormat df = new DecimalFormat("00"); + time = df.format(i) + ":00"; + item.put("time", time); + item.put("timeAsSeconds", timeAsSeconds); + item.put("value", value); + lastValue = value; + target_low.put(item); + } + } + o.put("target_low", target_low); + // TARGET_HIGH + JSONArray target_high = new JSONArray(); + lastValue = -1d; + for (int i = 0; i < 24; i++) { + int timeAsSeconds = i * 60 * 60; + double value = getTargetHighTimeFromMidnight(timeAsSeconds); + if (value != lastValue) { + JSONObject item = new JSONObject(); + String time; + DecimalFormat df = new DecimalFormat("00"); + time = df.format(i) + ":00"; + item.put("time", time); + item.put("timeAsSeconds", timeAsSeconds); + item.put("value", value); + lastValue = value; + target_high.put(item); + } + } + o.put("target_high", target_high); + + } catch (JSONException e) { + log.error("Unhandled exception" + e); + } + return new Profile(o); + } + + public boolean isProfileTheSame(Profile otherProfile) { + + if (!Round.isSame(this.baseBasalSum(), otherProfile.baseBasalSum())) + return false; + + ProfileValue[] basalValues = this.getBasalValues(); + ProfileValue[] otherBasalValues = otherProfile.getBasalValues(); + + if (basalValues.length != otherBasalValues.length) + return false; + + for (int i = 0; i < basalValues.length; i++) { + if (!basalValues[i].equals(otherBasalValues[i])) { + return false; + } + } + + return true; + } + } 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 8938b539361..37be1dc6e93 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java +++ b/app/src/main/java/info/nightscout/androidaps/data/ProfileIntervals.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.data; -import android.support.annotation.Nullable; -import android.support.v4.util.LongSparseArray; +import androidx.annotation.Nullable; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +31,7 @@ public ProfileIntervals (ProfileIntervals other) { rawData = other.rawData.clone(); } - public synchronized ProfileIntervals reset() { + public synchronized ProfileIntervals reset() { rawData = new LongSparseArray<>(); return this; } diff --git a/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java b/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java deleted file mode 100644 index f97d8b5e029..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.java +++ /dev/null @@ -1,118 +0,0 @@ -package info.nightscout.androidaps.data; - -import android.support.annotation.Nullable; -import android.support.v4.util.ArrayMap; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Iterator; - -import info.nightscout.androidaps.Constants; - -/** - * Created by mike on 01.06.2017. - */ - -public class ProfileStore { - private static Logger log = LoggerFactory.getLogger(ProfileStore.class); - private JSONObject json = null; - private String units = Constants.MGDL; - - ArrayMap cachedObjects = new ArrayMap<>(); - - public ProfileStore(JSONObject json) { - this.json = json; - getDefaultProfile(); // initialize units - } - - public JSONObject getData() { - return json; - } - - @Nullable - public Profile getDefaultProfile() { - Profile profile = null; - try { - String defaultProfileName = json.getString("defaultProfile"); - JSONObject store = json.getJSONObject("store"); - if (store.has(defaultProfileName)) { - profile = cachedObjects.get(defaultProfileName); - if (profile == null) { - if (store.has("units")) - units = store.getString("units"); - profile = new Profile(store.getJSONObject(defaultProfileName), units); - units = profile.getUnits(); - cachedObjects.put(defaultProfileName, profile); - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return profile; - } - - @Nullable - public String getDefaultProfileName() { - String defaultProfileName = null; - try { - defaultProfileName = json.getString("defaultProfile"); - JSONObject store = json.getJSONObject("store"); - if (store.has(defaultProfileName)) { - return defaultProfileName; - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return defaultProfileName; - } - - public String getUnits() { - return units; - } - - @Nullable - public Profile getSpecificProfile(String profileName) { - Profile profile = null; - try { - JSONObject store = json.getJSONObject("store"); - if (store.has(profileName)) { - profile = cachedObjects.get(profileName); - if (profile == null) { - if (store.has("units")) - units = store.getString("units"); - profile = new Profile(store.getJSONObject(profileName), units); - units = profile.getUnits(); - cachedObjects.put(profileName, profile); - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return profile; - } - - public ArrayList getProfileList() { - ArrayList ret = new ArrayList(); - - JSONObject store; - try { - store = json.getJSONObject("store"); - Iterator keys = store.keys(); - - while (keys.hasNext()) { - String profileName = (String) keys.next(); - ret.add(profileName); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - - return ret; - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.kt b/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.kt new file mode 100644 index 00000000000..9415a25f2cb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/data/ProfileStore.kt @@ -0,0 +1,60 @@ +package info.nightscout.androidaps.data + +import androidx.collection.ArrayMap +import info.nightscout.androidaps.utils.JsonHelper +import org.json.JSONException +import org.json.JSONObject +import org.slf4j.LoggerFactory +import java.util.* + +class ProfileStore(val data: JSONObject) { + private val log = LoggerFactory.getLogger(ProfileStore::class.java) + + private val cachedObjects = ArrayMap() + + private fun getStore(): JSONObject? { + try { + if (data.has("store")) return data.getJSONObject("store") + } catch (e: JSONException) { + log.error("Unhandled exception", e) + } + return null + } + + fun getDefaultProfile(): Profile? = getDefaultProfileName()?.let { getSpecificProfile(it) } + + fun getDefaultProfileName(): String? { + val defaultProfileName = data.getString("defaultProfile") + return getStore()?.has(defaultProfileName)?.let { defaultProfileName } + } + + fun getProfileList(): ArrayList { + val ret = ArrayList() + getStore()?.keys()?.let { keys -> + while (keys.hasNext()) { + val profileName = keys.next() as String + ret.add(profileName) + } + } + return ret + } + + fun getSpecificProfile(profileName: String): Profile? { + var profile: Profile? = null + getStore()?.let { store -> + if (store.has(profileName)) { + profile = cachedObjects[profileName] + if (profile == null) { + JsonHelper.safeGetJSONObject(store, profileName, null)?.let { profileObject -> + // take units from profile and if N/A from store + JsonHelper.safeGetStringAllowNull(profileObject, "units", JsonHelper.safeGetString(data, "units"))?.let { units -> + profile = Profile(profileObject, units) + cachedObjects[profileName] = profile + } + } + } + } + } + return profile + } +} 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 d38b3556343..461d8faa7bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java +++ b/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java @@ -45,6 +45,11 @@ public PumpEnactResult comment(String comment) { return this; } + public PumpEnactResult comment(int comment) { + this.comment = MainApp.gs(comment); + return this; + } + public PumpEnactResult duration(int duration) { this.duration = duration; return this; diff --git a/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java b/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java deleted file mode 100644 index 80a6e1b4578..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.java +++ /dev/null @@ -1,86 +0,0 @@ -package info.nightscout.androidaps.data; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.utils.SP; - -/** - * Created by mike on 12.10.2016. - */ - -public class QuickWizard { - private static Logger log = LoggerFactory.getLogger(QuickWizard.class); - - private JSONArray storage = new JSONArray(); - - public void setData(JSONArray newData) { - storage = newData; - } - - public void save() { - SP.putString("QuickWizard", storage.toString()); - } - - public int size() { - return storage.length(); - } - - public QuickWizardEntry get(int position) { - try { - return new QuickWizardEntry((JSONObject) storage.get(position), position); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return null; - } - - public Boolean isActive() { - for (int i = 0; i < storage.length(); i++) { - try { - if (new QuickWizardEntry((JSONObject) storage.get(i), i).isActive()) return true; - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - return false; - } - - public QuickWizardEntry getActive() { - for (int i = 0; i < storage.length(); i++) { - QuickWizardEntry entry; - try { - entry = new QuickWizardEntry((JSONObject) storage.get(i), i); - } catch (JSONException e) { - continue; - } - if (entry.isActive()) return entry; - } - return null; - } - - public QuickWizardEntry newEmptyItem() { - return new QuickWizardEntry(); - } - - public void addOrUpdate(QuickWizardEntry newItem) { - if (newItem.position == -1) - storage.put(newItem.storage); - else { - try { - storage.put(newItem.position, newItem.storage); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - save(); - } - - public void remove(int position) { - storage.remove(position); - save(); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.kt b/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.kt new file mode 100644 index 00000000000..f8bca8c450e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/data/QuickWizard.kt @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.data + +import info.nightscout.androidaps.utils.SP +import org.json.JSONArray +import org.json.JSONObject + +object QuickWizard { + private var storage = JSONArray() + + init { + setData(JSONArray(SP.getString("QuickWizard", "[]"))) + } + + fun getActive(): QuickWizardEntry? { + for (i in 0 until storage.length()) { + val entry = QuickWizardEntry(storage.get(i) as JSONObject, i) + if (entry.isActive) return entry + } + return null + } + + fun setData(newData: JSONArray) { + storage = newData + } + + fun save() { + SP.putString("QuickWizard", storage.toString()) + } + + fun size(): Int = storage.length() + + operator fun get(position: Int): QuickWizardEntry = + QuickWizardEntry(storage.get(position) as JSONObject, position) + + + fun newEmptyItem(): QuickWizardEntry { + return QuickWizardEntry() + } + + fun addOrUpdate(newItem: QuickWizardEntry) { + if (newItem.position == -1) + storage.put(newItem.storage) + else + storage.put(newItem.position, newItem.storage) + save() + } + + fun remove(position: Int) { + storage.remove(position) + save() + } +} 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 0bfcbb1002e..66123e1ce95 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java +++ b/app/src/main/java/info/nightscout/androidaps/data/QuickWizardEntry.java @@ -11,6 +11,7 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.interfaces.TreatmentsInterface; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.CobInfo; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; @@ -50,7 +51,7 @@ public class QuickWizardEntry { useTemptarget: 0 } */ - public QuickWizardEntry() { + QuickWizardEntry() { String emptyData = "{\"buttonText\":\"\",\"carbs\":0,\"validFrom\":0,\"validTo\":86340}"; try { storage = new JSONObject(emptyData); @@ -60,22 +61,21 @@ public QuickWizardEntry() { position = -1; } - public QuickWizardEntry(JSONObject entry, int position) { + QuickWizardEntry(JSONObject entry, int position) { storage = entry; this.position = position; } - public Boolean isActive() { + Boolean isActive() { return Profile.secondsFromMidnight() >= validFrom() && Profile.secondsFromMidnight() <= validTo(); } - public BolusWizard doCalc(Profile profile, TempTarget tempTarget, BgReading lastBG, boolean _synchronized) { - BolusWizard wizard = new BolusWizard(); - + public BolusWizard doCalc(Profile profile, String profileName, BgReading lastBG, boolean _synchronized) { + final TempTarget tempTarget = TreatmentsPlugin.getPlugin().getTempTargetFromHistory(); //BG double bg = 0; if (lastBG != null && useBG() == YES) { - bg = lastBG.valueToUnits(profile.getUnits()); + bg = lastBG.valueToUnits(ProfileFunctions.getSystemUnits()); } // COB @@ -86,11 +86,6 @@ public BolusWizard doCalc(Profile profile, TempTarget tempTarget, BgReading last cob = cobInfo.displayCob; } - // Temp target - if (useTempTarget() == NO) { - tempTarget = null; - } - // Bolus IOB boolean bolusIOB = false; if (useBolusIOB() == YES) { @@ -130,8 +125,8 @@ public BolusWizard doCalc(Profile profile, TempTarget tempTarget, BgReading last trend = true; } - wizard.doCalc(profile, tempTarget, carbs(), cob, bg, 0d, bolusIOB, basalIOB, superBolus, trend); - return wizard; + double percentage = SP.getDouble(R.string.key_boluswizard_percentage, 100.0); + return new BolusWizard(profile, profileName, tempTarget, carbs(), cob, bg, 0d, percentage, true, useCOB() == YES, bolusIOB, basalIOB, superBolus, useTempTarget() == YES, trend, "QuickWizard"); } public String buttonText() { diff --git a/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt new file mode 100644 index 00000000000..b121dbee02b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/data/defaultProfile/DefaultProfile.kt @@ -0,0 +1,145 @@ +package info.nightscout.androidaps.data.defaultProfile + +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.utils.Round +import org.json.JSONArray +import org.json.JSONObject +import java.util.* + + +class DefaultProfile { + var oneToFive: TreeMap> = TreeMap() + var sixToEleven: TreeMap> = TreeMap() + var twelveToSeventeen: TreeMap> = TreeMap() + var eighteenToTwentyfor: TreeMap> = TreeMap() + + fun profile(age: Double, tdd: Double, weight: Double, units: String): Profile { + val profile = JSONObject() + if (age >= 1 && age < 6) { + val _tdd = if (tdd == 0.0) 0.6 * weight else tdd + closest(oneToFive, _tdd * 0.3)?.let { array -> profile.put("basal", arrayToJson(array)) } + val ic = Round.roundTo(250.0 / _tdd, 1.0) + profile.put("carbratio", singleValueArray(ic, arrayOf( 0.0, -4.0, -1.0, -2.0, -4.0, 0.0, -4.0))) + val isf = Round.roundTo(200.0 / _tdd, 0.1) + profile.put("sens", singleValueArray(isf, arrayOf( 0.0, -2.0, -0.0, -0.0, -2.0, 0.0, -2.0))) + } else if (age >= 6 && age < 12) { + val _tdd = if (tdd == 0.0) 0.8 * weight else tdd + closest(sixToEleven, _tdd * 0.4)?.let { array -> profile.put("basal", arrayToJson(array)) } + val ic = Round.roundTo(375.0 / _tdd, 1.0) + profile.put("carbratio", singleValueArray(ic, arrayOf( 0.0, -3.0, 0.0, -1.0, -3.0, 0.0, -2.0))) + val isf = Round.roundTo(170.0 / _tdd, 0.1) + profile.put("sens", singleValueArray(isf, arrayOf( 0.0, -1.0, -0.0, -0.0, -1.0, 0.0, -1.0))) + } else if (age >= 12 && age < 17) { + val _tdd = if (tdd == 0.0) 1.0 * weight else tdd + closest(twelveToSeventeen, _tdd * 0.5)?.let { array -> profile.put("basal", arrayToJson(array)) } + val ic = Round.roundTo(500.0 / _tdd, 1.0) + profile.put("carbratio", singleValueArray(ic, arrayOf( 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, -1.0))) + val isf = Round.roundTo(100.0 / _tdd, 0.1) + profile.put("sens", singleValueArray(isf, arrayOf( 0.2, 0.0, 0.2, 0.2, 0.0, 0.2, 0.2))) + } else if (age >= 18) { + + } + profile.put("dia", 5.0) + profile.put("carbs_hr", 20) // not used + profile.put("delay", 5.0) // not used + profile.put("timezone", TimeZone.getDefault().getID()) + profile.put("target_high", JSONArray().put(JSONObject().put("time", "00:00").put("value", Profile.fromMgdlToUnits(108.0, units)))) + profile.put("target_low", JSONArray().put(JSONObject().put("time", "00:00").put("value", Profile.fromMgdlToUnits(108.0, units)))) + return Profile(profile, units) + } + + init { + oneToFive[1.00] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050) + oneToFive[1.13] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050) + oneToFive[1.25] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.050, 0.050) + oneToFive[1.38] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.050, 0.050) + oneToFive[1.50] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.075, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.050, 0.050, 0.050, 0.075, 0.100, 0.100, 0.050, 0.050) + oneToFive[1.75] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.050, 0.050, 0.050, 0.060, 0.060, 0.075, 0.075, 0.050, 0.050, 0.050, 0.100, 0.125, 0.100, 0.050, 0.050) + oneToFive[2.00] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.075, 0.050, 0.050, 0.065, 0.065, 0.075, 0.075, 0.050, 0.050, 0.050, 0.100, 0.125, 0.100, 0.050, 0.050) + oneToFive[2.25] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.100, 0.100, 0.075, 0.060, 0.060, 0.070, 0.070, 0.100, 0.100, 0.050, 0.050, 0.050, 0.125, 0.150, 0.125, 0.065, 0.050) + oneToFive[2.50] = arrayOf(0.050, 0.050, 0.050, 0.050, 0.075, 0.075, 0.100, 0.125, 0.125, 0.100, 0.065, 0.065, 0.075, 0.075, 0.125, 0.125, 0.060, 0.060, 0.060, 0.150, 0.150, 0.150, 0.070, 0.060) + oneToFive[2.75] = arrayOf(0.075, 0.075, 0.075, 0.100, 0.100, 0.100, 0.125, 0.150, 0.125, 0.100, 0.070, 0.070, 0.080, 0.080, 0.150, 0.150, 0.070, 0.070, 0.070, 0.175, 0.175, 0.175, 0.080, 0.070) + oneToFive[3.25] = arrayOf(0.100, 0.100, 0.100, 0.125, 0.125, 0.125, 0.150, 0.150, 0.150, 0.100, 0.080, 0.080, 0.100, 0.100, 0.175, 0.175, 0.075, 0.075, 0.075, 0.200, 0.200, 0.200, 0.090, 0.080) + oneToFive[3.75] = arrayOf(0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.175, 0.175, 0.175, 0.100, 0.085, 0.085, 0.110, 0.110, 0.185, 0.185, 0.080, 0.080, 0.080, 0.225, 0.225, 0.225, 0.100, 0.090) + oneToFive[4.25] = arrayOf(0.125, 0.125, 0.130, 0.140, 0.140, 0.140, 0.200, 0.200, 0.200, 0.125, 0.090, 0.090, 0.120, 0.120, 0.200, 0.200, 0.100, 0.100, 0.100, 0.250, 0.250, 0.250, 0.125, 0.100) + oneToFive[4.75] = arrayOf(0.125, 0.130, 0.135, 0.150, 0.150, 0.150, 0.200, 0.225, 0.200, 0.125, 0.100, 0.100, 0.125, 0.125, 0.250, 0.200, 0.110, 0.125, 0.125, 0.275, 0.275, 0.275, 0.130, 0.125) + oneToFive[5.25] = arrayOf(0.150, 0.150, 0.150, 0.170, 0.170, 0.170, 0.225, 0.225, 0.225, 0.130, 0.125, 0.125, 0.140, 0.140, 0.250, 0.250, 0.150, 0.150, 0.150, 0.300, 0.300, 0.300, 0.150, 0.150) + oneToFive[6.00] = arrayOf(0.170, 0.170, 0.175, 0.200, 0.200, 0.200, 0.250, 0.250, 0.250, 0.150, 0.125, 0.125, 0.150, 0.150, 0.275, 0.275, 0.170, 0.150, 0.150, 0.350, 0.350, 0.350, 0.175, 0.150) + oneToFive[6.75] = arrayOf(0.200, 0.200, 0.200, 0.225, 0.225, 0.225, 0.275, 0.275, 0.275, 0.200, 0.130, 0.130, 0.175, 0.175, 0.300, 0.300, 0.170, 0.175, 0.175, 0.375, 0.375, 0.375, 0.200, 0.175) + oneToFive[7.50] = arrayOf(0.225, 0.230, 0.235, 0.250, 0.250, 0.250, 0.300, 0.300, 0.300, 0.250, 0.150, 0.150, 0.200, 0.200, 0.325, 0.325, 0.200, 0.200, 0.200, 0.400, 0.450, 0.400, 0.350, 0.200) + + sixToEleven[5.26] = arrayOf(0.18, 0.18, 0.18, 0.20, 0.20, 0.23, 0.25, 0.25, 0.25, 0.18, 0.15, 0.13, 0.15, 0.15, 0.25, 0.25, 0.20, 0.15, 0.18, 0.25, 0.25, 0.25, 0.23, 0.20) + sixToEleven[5.61] = arrayOf(0.18, 0.20, 0.20, 0.23, 0.23, 0.25, 0.28, 0.28, 0.25, 0.20, 0.15, 0.13, 0.15, 0.18, 0.25, 0.25, 0.20, 0.15, 0.18, 0.28, 0.25, 0.25, 0.23, 0.20) + sixToEleven[5.93] = arrayOf(0.20, 0.20, 0.23, 0.25, 0.25, 0.25, 0.30, 0.30, 0.30, 0.25, 0.15, 0.15, 0.18, 0.18, 0.28, 0.28, 0.20, 0.20, 0.20, 0.28, 0.28, 0.28, 0.25, 0.23) + sixToEleven[6.26] = arrayOf(0.20, 0.23, 0.23, 0.25, 0.25, 0.28, 0.33, 0.30, 0.30, 0.25, 0.18, 0.15, 0.18, 0.18, 0.28, 0.28, 0.23, 0.20, 0.20, 0.28, 0.30, 0.28, 0.25, 0.23) + sixToEleven[6.60] = arrayOf(0.23, 0.23, 0.25, 0.25, 0.25, 0.28, 0.33, 0.33, 0.33, 0.28, 0.18, 0.15, 0.18, 0.18, 0.30, 0.28, 0.23, 0.23, 0.20, 0.30, 0.30, 0.30, 0.25, 0.25) + sixToEleven[7.26] = arrayOf(0.23, 0.25, 0.28, 0.28, 0.30, 0.33, 0.38, 0.35, 0.35, 0.30, 0.18, 0.18, 0.18, 0.19, 0.33, 0.30, 0.25, 0.23, 0.23, 0.33, 0.33, 0.33, 0.30, 0.25) + sixToEleven[7.92] = arrayOf(0.25, 0.25, 0.28, 0.30, 0.33, 0.35, 0.38, 0.38, 0.38, 0.35, 0.20, 0.20, 0.23, 0.25, 0.35, 0.33, 0.30, 0.25, 0.25, 0.35, 0.35, 0.35, 0.33, 0.28) + sixToEleven[8.57] = arrayOf(0.28, 0.28, 0.30, 0.30, 0.33, 0.38, 0.40, 0.43, 0.40, 0.38, 0.25, 0.25, 0.25, 0.28, 0.38, 0.38, 0.33, 0.28, 0.28, 0.38, 0.40, 0.38, 0.35, 0.30) + sixToEleven[9.24] = arrayOf(0.30, 0.33, 0.35, 0.38, 0.40, 0.40, 0.43, 0.45, 0.43, 0.40, 0.30, 0.25, 0.28, 0.28, 0.38, 0.40, 0.33, 0.30, 0.30, 0.40, 0.43, 0.40, 0.38, 0.35) + sixToEleven[9.89] = arrayOf(0.35, 0.35, 0.38, 0.40, 0.40, 0.43, 0.45, 0.48, 0.45, 0.40, 0.30, 0.25, 0.28, 0.30, 0.40, 0.43, 0.35, 0.33, 0.33, 0.43, 0.45, 0.43, 0.40, 0.38) + sixToEleven[10.56] = arrayOf(0.38, 0.38, 0.40, 0.43, 0.45, 0.45, 0.48, 0.50, 0.48, 0.40, 0.35, 0.25, 0.30, 0.33, 0.43, 0.45, 0.35, 0.35, 0.35, 0.45, 0.48, 0.45, 0.43, 0.40) + sixToEleven[11.21] = arrayOf(0.40, 0.43, 0.43, 0.45, 0.48, 0.50, 0.53, 0.55, 0.50, 0.40, 0.35, 0.30, 0.33, 0.33, 0.45, 0.48, 0.38, 0.35, 0.37, 0.50, 0.50, 0.48, 0.45, 0.43) + sixToEleven[11.88] = arrayOf(0.43, 0.43, 0.45, 0.45, 0.48, 0.50, 0.55, 0.58, 0.50, 0.40, 0.35, 0.33, 0.33, 0.33, 0.48, 0.50, 0.40, 0.38, 0.38, 0.53, 0.53, 0.50, 0.48, 0.45) + sixToEleven[12.53] = arrayOf(0.45, 0.45, 0.48, 0.50, 0.53, 0.55, 0.60, 0.60, 0.60, 0.45, 0.40, 0.35, 0.35, 0.38, 0.50, 0.53, 0.40, 0.38, 0.38, 0.55, 0.58, 0.55, 0.50, 0.48) + sixToEleven[13.19] = arrayOf(0.48, 0.48, 0.50, 0.55, 0.58, 0.60, 0.65, 0.65, 0.65, 0.50, 0.45, 0.36, 0.38, 0.40, 0.55, 0.55, 0.45, 0.40, 0.40, 0.60, 0.60, 0.58, 0.55, 0.50) + sixToEleven[14.18] = arrayOf(0.53, 0.53, 0.55, 0.60, 0.65, 0.68, 0.70, 0.70, 0.68, 0.60, 0.55, 0.40, 0.40, 0.45, 0.60, 0.60, 0.50, 0.45, 0.45, 0.63, 0.65, 0.63, 0.60, 0.60) + sixToEleven[15.17] = arrayOf(0.55, 0.58, 0.60, 0.65, 0.70, 0.70, 0.75, 0.75, 0.70, 0.65, 0.60, 0.42, 0.42, 0.45, 0.65, 0.65, 0.60, 0.50, 0.50, 0.68, 0.68, 0.65, 0.63, 0.63) + sixToEleven[16.50] = arrayOf(0.60, 0.63, 0.65, 0.70, 0.70, 0.70, 0.80, 0.80, 0.80, 0.70, 0.60, 0.45, 0.45, 0.50, 0.65, 0.70, 0.60, 0.55, 0.55, 0.75, 0.75, 0.70, 0.65, 0.65) + + twelveToSeventeen[10.70] = arrayOf(0.30, 0.30, 0.30, 0.30, 0.40, 0.40, 0.60, 0.60, 0.60, 0.40, 0.35, 0.30, 0.30, 0.35, 0.45, 0.50, 0.40, 0.30, 0.30, 0.40, 0.50, 0.40, 0.40, 0.30) + twelveToSeventeen[11.10] = arrayOf(0.30, 0.30, 0.30, 0.35, 0.40, 0.45, 0.60, 0.60, 0.60, 0.40, 0.40, 0.30, 0.35, 0.40, 0.50, 0.50, 0.30, 0.30, 0.30, 0.50, 0.50, 0.50, 0.30, 0.30) + twelveToSeventeen[11.60] = arrayOf(0.30, 0.30, 0.35, 0.45, 0.45, 0.50, 0.65, 0.65, 0.65, 0.45, 0.40, 0.40, 0.40, 0.40, 0.50, 0.55, 0.55, 0.45, 0.45, 0.50, 0.50, 0.50, 0.40, 0.40) + twelveToSeventeen[13.00] = arrayOf(0.40, 0.40, 0.40, 0.50, 0.55, 0.60, 0.70, 0.70, 0.70, 0.60, 0.50, 0.40, 0.40, 0.40, 0.50, 0.60, 0.60, 0.50, 0.50, 0.60, 0.60, 0.60, 0.40, 0.30) + twelveToSeventeen[15.60] = arrayOf(0.45, 0.50, 0.50, 0.60, 0.65, 0.70, 0.80, 0.80, 0.80, 0.70, 0.60, 0.60, 0.50, 0.50, 0.60, 0.70, 0.70, 0.60, 0.60, 0.60, 0.70, 0.70, 0.50, 0.50) + twelveToSeventeen[17.00] = arrayOf(0.50, 0.55, 0.60, 0.70, 0.75, 0.80, 1.00, 1.00, 1.00, 0.80, 0.70, 0.60, 0.60, 0.60, 0.70, 0.80, 0.80, 0.65, 0.65, 0.65, 0.70, 0.70, 0.60, 0.60) + twelveToSeventeen[18.00] = arrayOf(0.60, 0.65, 0.70, 0.80, 0.85, 0.90, 1.10, 1.10, 1.10, 0.90, 0.80, 0.60, 0.60, 0.60, 0.70, 0.80, 0.80, 0.70, 0.65, 0.70, 0.75, 0.70, 0.60, 0.60) + twelveToSeventeen[20.20] = arrayOf(0.70, 0.75, 0.80, 0.90, 0.95, 1.00, 1.10, 1.10, 1.10, 0.90, 0.80, 0.70, 0.70, 0.70, 0.80, 0.90, 0.90, 0.75, 0.75, 0.75, 0.80, 0.80, 0.70, 0.70) + twelveToSeventeen[21.60] = arrayOf(0.75, 0.80, 0.90, 0.90, 1.00, 1.00, 1.20, 1.20, 1.20, 0.90, 0.80, 0.70, 0.70, 0.70, 0.90, 1.00, 1.00, 0.80, 0.80, 0.80, 0.80, 0.80, 0.70, 0.70) + twelveToSeventeen[23.80] = arrayOf(0.75, 0.80, 0.90, 1.00, 1.10, 1.10, 1.20, 1.20, 1.20, 1.00, 0.90, 0.80, 0.80, 0.80, 0.90, 1.10, 1.10, 0.90, 0.90, 0.90, 1.00, 1.00, 0.80, 0.80) + twelveToSeventeen[26.10] = arrayOf(0.80, 0.80, 0.90, 1.00, 1.20, 1.20, 1.30, 1.30, 1.30, 1.10, 1.00, 0.90, 0.90, 0.90, 1.00, 1.20, 1.10, 0.90, 0.90, 1.00, 1.00, 1.00, 0.90, 0.90) + twelveToSeventeen[28.00] = arrayOf(0.90, 0.90, 1.00, 1.10, 1.10, 1.20, 1.30, 1.30, 1.30, 1.20, 1.00, 1.00, 1.00, 1.00, 1.20, 1.20, 1.20, 1.00, 1.00, 1.10, 1.10, 1.10, 0.90, 0.90) + twelveToSeventeen[30.10] = arrayOf(1.00, 1.00, 1.10, 1.20, 1.30, 1.40, 1.50, 1.50, 1.50, 1.30, 1.20, 1.00, 1.00, 1.00, 1.30, 1.40, 1.40, 1.00, 1.00, 1.15, 1.15, 1.10, 1.00, 1.00) + twelveToSeventeen[32.60] = arrayOf(1.10, 1.10, 1.20, 1.20, 1.40, 1.50, 1.50, 1.50, 1.50, 1.30, 1.20, 1.10, 1.10, 1.10, 1.40, 1.50, 1.40, 1.10, 1.10, 1.20, 1.20, 1.20, 1.10, 1.10) + twelveToSeventeen[35.20] = arrayOf(1.20, 1.20, 1.30, 1.40, 1.50, 1.60, 1.70, 1.70, 1.50, 1.40, 1.20, 1.10, 1.10, 1.10, 1.40, 1.50, 1.60, 1.40, 1.20, 1.20, 1.30, 1.30, 1.20, 1.20) + twelveToSeventeen[39.00] = arrayOf(1.30, 1.30, 1.40, 1.60, 1.60, 1.60, 1.90, 1.90, 1.90, 1.50, 1.30, 1.20, 1.20, 1.30, 1.50, 1.60, 1.70, 1.80, 1.50, 1.50, 1.60, 1.60, 1.30, 1.30) + twelveToSeventeen[42.80] = arrayOf(1.40, 1.40, 1.50, 1.70, 1.80, 1.80, 2.00, 2.00, 2.00, 1.80, 1.80, 1.50, 1.50, 1.50, 1.60, 1.70, 1.80, 1.90, 1.60, 1.60, 1.70, 1.70, 1.50, 1.50) + twelveToSeventeen[47.30] = arrayOf(1.50, 1.50, 1.70, 1.70, 2.00, 2.00, 2.20, 2.30, 2.20, 2.00, 1.80, 1.60, 1.60, 1.60, 1.80, 2.00, 2.10, 1.90, 1.80, 1.80, 2.00, 2.00, 1.60, 1.60) + } + + private fun closest(map: TreeMap>, key: Double): Array? { + val low = map.floorEntry(key) + val high = map.ceilingEntry(key) + var res: Array? = null + if (low != null && high != null) { + res = if (Math.abs(key - low.key) < Math.abs(key - high.key)) + low.value + else + high.value + } else if (low != null || high != null) { + res = if (low != null) low.value else high.value + } + return res + } + + fun arrayToJson(b: Array): JSONArray { + val basals = JSONArray() + for (i in 0..23) { + val time = String.format(Locale.ENGLISH, "%02d:00", i) + basals.put(JSONObject().put("time", time).put("value", b[i].toString())) + } + return basals + } + + fun singleValueArray(value: Double, sample: Array): JSONArray { + val array = JSONArray() + array.put(JSONObject().put("time", "00:00").put("value", value + sample[0])) + array.put(JSONObject().put("time", "06:00").put("value", value + sample[1])) + array.put(JSONObject().put("time", "09:00").put("value", value + sample[2])) + array.put(JSONObject().put("time", "11:00").put("value", value + sample[3])) + array.put(JSONObject().put("time", "14:00").put("value", value + sample[4])) + array.put(JSONObject().put("time", "16:00").put("value", value + sample[5])) + array.put(JSONObject().put("time", "19:00").put("value", value + sample[6])) + return array + } +} \ No newline at end of file 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 6fafdc4b6d2..abac9e08c09 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java +++ b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import java.util.Date; +import java.util.List; import java.util.Objects; import info.nightscout.androidaps.Constants; @@ -19,10 +20,11 @@ import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.T; @DatabaseTable(tableName = DatabaseHelper.DATABASE_BGREADINGS) public class BgReading implements DataPointWithLabelInterface { - private static Logger log = LoggerFactory.getLogger(L.DATABASE); + private static Logger log = LoggerFactory.getLogger(L.GLUCOSE); @DatabaseField(id = true) public long date; @@ -73,9 +75,10 @@ public String valueToUnitsToString(String units) { public String directionToSymbol() { String symbol = ""; - if (direction == null) { - symbol = "??"; - } else if (direction.compareTo("DoubleDown") == 0) { + if (direction == null) + direction = calculateDirection(); + + if (direction.compareTo("DoubleDown") == 0) { symbol = "\u21ca"; } else if (direction.compareTo("SingleDown") == 0) { symbol = "\u2193"; @@ -95,18 +98,13 @@ public String directionToSymbol() { return symbol; } - public static boolean isSlopeNameInvalid(String direction) { - if (direction.compareTo("NOT_COMPUTABLE") == 0 || + private static boolean isSlopeNameInvalid(String direction) { + return direction.compareTo("NOT_COMPUTABLE") == 0 || direction.compareTo("NOT COMPUTABLE") == 0 || direction.compareTo("OUT_OF_RANGE") == 0 || direction.compareTo("OUT OF RANGE") == 0 || direction.compareTo("NONE") == 0 || - direction.compareTo("NotComputable") == 0 - ) { - return true; - } else { - return false; - } + direction.compareTo("NotComputable") == 0; } @@ -123,7 +121,8 @@ public String toString() { public boolean isDataChanging(BgReading other) { if (date != other.date) { - log.error("Comparing different"); + if (L.isEnabled(L.GLUCOSE)) + log.error("Comparing different"); return false; } if (value != other.value) @@ -133,7 +132,8 @@ public boolean isDataChanging(BgReading other) { public boolean isEqual(BgReading other) { if (date != other.date) { - log.error("Comparing different"); + if (L.isEnabled(L.GLUCOSE)) + log.error("Comparing different"); return false; } if (value != other.value) @@ -149,7 +149,8 @@ public boolean isEqual(BgReading other) { public void copyFrom(BgReading other) { if (date != other.date) { - log.error("Copying different"); + if (L.isEnabled(L.GLUCOSE)) + log.error("Copying different"); return; } value = other.value; @@ -181,8 +182,7 @@ public double getX() { @Override public double getY() { - String units = ProfileFunctions.getInstance().getProfileUnits(); - return valueToUnits(units); + return valueToUnits(ProfileFunctions.getSystemUnits()); } @Override @@ -215,9 +215,9 @@ public float getSize() { @Override public int getColor() { - String units = ProfileFunctions.getInstance().getProfileUnits(); - Double lowLine = OverviewPlugin.getPlugin().determineLowLine(units); - Double highLine = OverviewPlugin.getPlugin().determineHighLine(units); + String units = ProfileFunctions.getSystemUnits(); + Double lowLine = OverviewPlugin.INSTANCE.determineLowLine(); + Double highLine = OverviewPlugin.INSTANCE.determineHighLine(); int color = MainApp.gc(R.color.inrange); if (isPrediction()) return getPredectionColor(); @@ -246,4 +246,53 @@ private boolean isPrediction() { return isaCOBPrediction || isCOBPrediction || isIOBPrediction || isUAMPrediction || isZTPrediction; } + + // Copied from xDrip+ + String calculateDirection() { + // Rework to get bgreaings from internal DB and calculate on that base + + List bgReadingsList = MainApp.getDbHelper().getAllBgreadingsDataFromTime(this.date - T.mins(10).msecs(), false); + if (bgReadingsList == null || bgReadingsList.size() < 2) + return "NONE"; + BgReading current = bgReadingsList.get(1); + BgReading previous = bgReadingsList.get(0); + + if (bgReadingsList.get(1).date < bgReadingsList.get(0).date) { + current = bgReadingsList.get(0); + previous = bgReadingsList.get(1); + } + + double slope; + + // Avoid division by 0 + if (current.date == previous.date) + slope = 0; + else + slope = (previous.value - current.value) / (previous.date - current.date); + + if (L.isEnabled(L.GLUCOSE)) + log.debug("Slope is :" + slope + " delta " + (previous.value - current.value) + " date difference " + (current.date - previous.date)); + + double slope_by_minute = slope * 60000; + String arrow = "NONE"; + + if (slope_by_minute <= (-3.5)) { + arrow = "DoubleDown"; + } else if (slope_by_minute <= (-2)) { + arrow = "SingleDown"; + } else if (slope_by_minute <= (-1)) { + arrow = "FortyFiveDown"; + } else if (slope_by_minute <= (1)) { + arrow = "Flat"; + } else if (slope_by_minute <= (2)) { + arrow = "FortyFiveUp"; + } else if (slope_by_minute <= (3.5)) { + arrow = "SingleUp"; + } else if (slope_by_minute <= (40)) { + arrow = "DoubleUp"; + } + if (L.isEnabled(L.GLUCOSE)) + log.debug("Direction set to: " + arrow); + return arrow; + } } 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 f0e70c0a25e..c2dc6f737e6 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java @@ -93,12 +93,22 @@ public double getHoursFromStart() { return (System.currentTimeMillis() - date) / (60 * 60 * 1000.0); } - public String age() { + public String age(boolean useShortText) { Map diff = computeDiff(date, System.currentTimeMillis()); - if (OverviewFragment.shorttextmode) - return diff.get(TimeUnit.DAYS) + "d" + diff.get(TimeUnit.HOURS) + "h"; - else - return diff.get(TimeUnit.DAYS) + " " + MainApp.gs(R.string.days) + " " + diff.get(TimeUnit.HOURS) + " " + MainApp.gs(R.string.hours); + + String days = " " + MainApp.gs(R.string.days) + " "; + String hours = " " + MainApp.gs(R.string.hours) + " "; + + if (useShortText) { + days = MainApp.gs(R.string.shortday); + hours = MainApp.gs(R.string.shorthour); + } + + return diff.get(TimeUnit.DAYS) + days + diff.get(TimeUnit.HOURS) + hours; + } + + public String age() { + return age(OverviewFragment.shorttextmode); } public boolean isOlderThan(double hours) { @@ -157,7 +167,7 @@ public double getX() { @Override public double getY() { - String units = ProfileFunctions.getInstance().getProfileUnits(); + String units = ProfileFunctions.getSystemUnits(); if (eventType.equals(MBG)) { double mbg = 0d; try { 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 1f071a2f180..9fe6d615075 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -3,7 +3,8 @@ import android.content.Context; import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; import com.j256.ormlite.dao.CloseableIterator; @@ -21,6 +22,8 @@ import java.sql.SQLException; import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -29,7 +32,8 @@ import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.data.OverlappingIntervals; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.data.NonOverlappingIntervals; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.events.EventCareportalEventChange; @@ -48,11 +52,13 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.history.PumpHistory; import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRNSHistorySync; import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.pump.insight.database.InsightBolusID; import info.nightscout.androidaps.plugins.pump.insight.database.InsightHistoryOffset; import info.nightscout.androidaps.plugins.pump.insight.database.InsightPumpID; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistory; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.utils.JsonHelper; import info.nightscout.androidaps.utils.PercentageSplitter; @@ -82,8 +88,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public static final String DATABASE_INSIGHT_HISTORY_OFFSETS = "InsightHistoryOffsets"; public static final String DATABASE_INSIGHT_BOLUS_IDS = "InsightBolusIDs"; public static final String DATABASE_INSIGHT_PUMP_IDS = "InsightPumpIDs"; + public static final String DATABASE_POD_HISTORY = "PodHistory"; - private static final int DATABASE_VERSION = 11; + private static final int DATABASE_VERSION = 12; public static Long earliestDataChange = null; @@ -131,6 +138,8 @@ public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) TableUtils.createTableIfNotExists(connectionSource, InsightHistoryOffset.class); TableUtils.createTableIfNotExists(connectionSource, InsightBolusID.class); TableUtils.createTableIfNotExists(connectionSource, InsightPumpID.class); + //TableUtils.dropTable(connectionSource, PodHistory.class, true); + TableUtils.createTableIfNotExists(connectionSource, PodHistory.class); database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_BOLUS_IDS + "\", " + System.currentTimeMillis() + " " + "WHERE NOT EXISTS (SELECT 1 FROM sqlite_sequence WHERE name = \"" + DATABASE_INSIGHT_BOLUS_IDS + "\")"); database.execSQL("INSERT INTO sqlite_sequence (name, seq) SELECT \"" + DATABASE_INSIGHT_PUMP_IDS + "\", " + System.currentTimeMillis() + " " + @@ -190,15 +199,6 @@ public int getNewVersion() { return newVersion; } - /** - * Close the database connections and clear any cached DAOs. - */ - @Override - public void close() { - super.close(); - } - - public long size(String database) { return DatabaseUtils.queryNumEntries(getReadableDatabase(), database); } @@ -216,6 +216,7 @@ public void resetDatabases() { TableUtils.dropTable(connectionSource, CareportalEvent.class, true); TableUtils.dropTable(connectionSource, ProfileSwitch.class, true); TableUtils.dropTable(connectionSource, TDD.class, true); + TableUtils.dropTable(connectionSource, PodHistory.class, true); TableUtils.createTableIfNotExists(connectionSource, TempTarget.class); TableUtils.createTableIfNotExists(connectionSource, BgReading.class); TableUtils.createTableIfNotExists(connectionSource, DanaRHistoryRecord.class); @@ -225,6 +226,7 @@ public void resetDatabases() { TableUtils.createTableIfNotExists(connectionSource, CareportalEvent.class); TableUtils.createTableIfNotExists(connectionSource, ProfileSwitch.class); TableUtils.createTableIfNotExists(connectionSource, TDD.class); + TableUtils.createTableIfNotExists(connectionSource, PodHistory.class); updateEarliestDataChange(0); } catch (SQLException e) { log.error("Unhandled exception", e); @@ -240,7 +242,7 @@ public void resetDatabases() { new java.util.TimerTask() { @Override public void run() { - MainApp.bus().post(new EventRefreshOverview("resetDatabases")); + RxBus.INSTANCE.send(new EventRefreshOverview("resetDatabases")); } }, 3000 @@ -359,6 +361,10 @@ private Dao getDaoInsightHistoryOffset() throws SQ return getDao(InsightHistoryOffset.class); } + private Dao getDaoPodHistory() throws SQLException { + return getDao(PodHistory.class); + } + public static long roundDateToSec(long date) { long rounded = date - date % 1000; if (rounded != date) @@ -409,7 +415,7 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing EventNewBg"); - MainApp.bus().post(new EventNewBG(bgReading)); + RxBus.INSTANCE.send(new EventNewBG(bgReading)); scheduledBgPost = null; } } @@ -434,7 +440,7 @@ public static BgReading lastBg() { return null; for (int i = 0; i < bgList.size(); i++) - if (bgList.get(i).value > 39) + if (bgList.get(i).value >= 39) return bgList.get(i); return null; } @@ -534,15 +540,29 @@ public List getTDDs() { return tddList; } + public List getTDDsForLastXDays(int days) { + List tddList; + GregorianCalendar gc = new GregorianCalendar(); + gc.add(Calendar.DAY_OF_YEAR, (-1) * days); - // ------------- DbRequests handling ------------------- - - public void create(DbRequest dbr) { try { - getDaoDbRequest().create(dbr); + QueryBuilder queryBuilder = getDaoTDD().queryBuilder(); + queryBuilder.orderBy("date", false); + Where where = queryBuilder.where(); + where.ge("date", gc.getTimeInMillis()); + 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) throws SQLException { + getDaoDbRequest().create(dbr); } public int delete(DbRequest dbr) { @@ -596,7 +616,7 @@ public CloseableIterator getDbRequestInterator() { } } - // -------------------- TREATMENT HANDLING ------------------- + // -------------------- TEMPTARGET HANDLING ------------------- public static void updateEarliestDataChange(long newDate) { if (earliestDataChange == null) { @@ -627,6 +647,23 @@ public List getTemptargetsDataFromTime(long mills, boolean ascending return new ArrayList(); } + public List getTemptargetsDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTempTargets = getDaoTempTargets(); + List tempTargets; + QueryBuilder queryBuilder = daoTempTargets.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempTargets = daoTempTargets.query(preparedQuery); + return tempTargets; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + public boolean createOrUpdate(TempTarget tempTarget) { try { TempTarget old; @@ -699,7 +736,7 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing EventTempTargetChange"); - MainApp.bus().post(new EventTempTargetChange()); + RxBus.INSTANCE.send(new EventTempTargetChange()); scheduledTemTargetPost = null; } } @@ -734,8 +771,8 @@ public void createTemptargetFromJsonIfNotExists(JSONObject trJson) { TempTarget tempTarget = new TempTarget() .date(trJson.getLong("mills")) .duration(JsonHelper.safeGetInt(trJson, "duration")) - .low(Profile.toMgdl(trJson.getDouble("targetBottom"), units)) - .high(Profile.toMgdl(trJson.getDouble("targetTop"), units)) + .low(Profile.toMgdl(JsonHelper.safeGetDouble(trJson, "targetBottom"), units)) + .high(Profile.toMgdl(JsonHelper.safeGetDouble(trJson, "targetTop"), units)) .reason(JsonHelper.safeGetString(trJson, "reason", "")) ._id(trJson.getString("_id")) .source(Source.NIGHTSCOUT); @@ -852,6 +889,31 @@ public boolean createOrUpdate(TemporaryBasal tempBasal) { log.debug("TEMPBASAL: Already exists from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); return false; } + + // search by date (in case its standard record that has become pump record) + QueryBuilder queryBuilder2 = getDaoTemporaryBasal().queryBuilder(); + Where where2 = queryBuilder2.where(); + where2.eq("date", tempBasal.date); + PreparedQuery preparedQuery2 = queryBuilder2.prepare(); + List trList2 = getDaoTemporaryBasal().query(preparedQuery2); + + if (trList2.size() > 0) { + old = trList2.get(0); + + old.copyFromPump(tempBasal); + old.source = Source.PUMP; + + if (L.isEnabled(L.DATABASE)) + log.debug("TEMPBASAL: Updated record with Pump Data : " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); + + getDaoTemporaryBasal().update(old); + + updateEarliestDataChange(tempBasal.date); + scheduleTemporaryBasalChange(); + + return false; + } + getDaoTemporaryBasal().create(tempBasal); if (L.isEnabled(L.DATABASE)) log.debug("TEMPBASAL: New record from: " + Source.getString(tempBasal.source) + " " + tempBasal.toString()); @@ -950,15 +1012,31 @@ public List getTemporaryBasalsDataFromTime(long mills, boolean a return new ArrayList(); } + public List getTemporaryBasalsDataFromTime(long from, long to, boolean ascending) { + try { + List tempbasals; + QueryBuilder queryBuilder = getDaoTemporaryBasal().queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + tempbasals = getDaoTemporaryBasal().query(preparedQuery); + return tempbasals; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList(); + } + private static void scheduleTemporaryBasalChange() { class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing EventTempBasalChange"); - MainApp.bus().post(new EventReloadTempBasalData()); - MainApp.bus().post(new EventTempBasalChange()); + RxBus.INSTANCE.send(new EventReloadTempBasalData()); + RxBus.INSTANCE.send(new EventTempBasalChange()); if (earliestDataChange != null) - MainApp.bus().post(new EventNewHistoryData(earliestDataChange)); + RxBus.INSTANCE.send(new EventNewHistoryData(earliestDataChange)); earliestDataChange = null; scheduledTemBasalsPost = null; } @@ -1076,6 +1154,29 @@ public TemporaryBasal findTempBasalById(String _id) { return null; } + + public TemporaryBasal findTempBasalByPumpId(Long pumpId) { + try { + QueryBuilder queryBuilder = null; + queryBuilder = getDaoTemporaryBasal().queryBuilder(); + queryBuilder.orderBy("date", false); + Where where = queryBuilder.where(); + where.eq("pumpId", pumpId); + PreparedQuery preparedQuery = queryBuilder.prepare(); + List list = getDaoTemporaryBasal().query(preparedQuery); + + if (list.size() > 0) + return list.get(0); + else + return null; + + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + + // ------------ ExtendedBolus handling --------------- public boolean createOrUpdate(ExtendedBolus extendedBolus) { @@ -1268,9 +1369,9 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing EventExtendedBolusChange"); - MainApp.bus().post(new EventReloadTreatmentData(new EventExtendedBolusChange())); + RxBus.INSTANCE.send(new EventReloadTreatmentData(new EventExtendedBolusChange())); if (earliestDataChange != null) - MainApp.bus().post(new EventNewHistoryData(earliestDataChange)); + RxBus.INSTANCE.send(new EventNewHistoryData(earliestDataChange)); earliestDataChange = null; scheduledExtendedBolusPost = null; } @@ -1323,7 +1424,7 @@ public CareportalEvent getLastCareportalEvent(String event) { QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); queryBuilder.orderBy("date", false); Where where = queryBuilder.where(); - where.eq("eventType", event); + where.eq("eventType", event).and().isNotNull("json"); queryBuilder.limit(1L); PreparedQuery preparedQuery = queryBuilder.prepare(); careportalEvents = getDaoCareportalEvents().query(preparedQuery); @@ -1343,10 +1444,10 @@ public List getCareportalEventsFromTime(long mills, boolean asc QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); queryBuilder.orderBy("date", ascending); Where where = queryBuilder.where(); - where.ge("date", mills); + where.ge("date", mills).and().isNotNull("json").and().isNotNull("eventType"); PreparedQuery preparedQuery = queryBuilder.prepare(); careportalEvents = getDaoCareportalEvents().query(preparedQuery); - preprocessOpenAPSOfflineEvents(careportalEvents); + careportalEvents = preprocessOpenAPSOfflineEvents(careportalEvents); return careportalEvents; } catch (SQLException e) { log.error("Unhandled exception", e); @@ -1354,14 +1455,33 @@ public List getCareportalEventsFromTime(long mills, boolean asc return new ArrayList<>(); } - public void preprocessOpenAPSOfflineEvents(List list) { - OverlappingIntervals offlineEvents = new OverlappingIntervals(); + public List getCareportalEvents(long start, long end, boolean ascending) { + try { + List careportalEvents; + QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", start, end).and().isNotNull("json").and().isNotNull("eventType"); + PreparedQuery preparedQuery = queryBuilder.prepare(); + careportalEvents = getDaoCareportalEvents().query(preparedQuery); + careportalEvents = preprocessOpenAPSOfflineEvents(careportalEvents); + return careportalEvents; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + + public List preprocessOpenAPSOfflineEvents(List list) { + NonOverlappingIntervals offlineEvents = new NonOverlappingIntervals(); + List other = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { CareportalEvent event = list.get(i); - if (!event.eventType.equals(CareportalEvent.OPENAPSOFFLINE)) continue; - offlineEvents.add(event); + if (event.eventType.equals(CareportalEvent.OPENAPSOFFLINE)) offlineEvents.add(event); + else other.add(event); } - + other.addAll(offlineEvents.getList()); + return other; } public List getCareportalEventsFromTime(long mills, String type, boolean ascending) { @@ -1370,10 +1490,10 @@ public List getCareportalEventsFromTime(long mills, String type QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); queryBuilder.orderBy("date", ascending); Where where = queryBuilder.where(); - where.ge("date", mills).and().eq("eventType", type); + where.ge("date", mills).and().eq("eventType", type).and().isNotNull("json"); PreparedQuery preparedQuery = queryBuilder.prepare(); careportalEvents = getDaoCareportalEvents().query(preparedQuery); - preprocessOpenAPSOfflineEvents(careportalEvents); + careportalEvents = preprocessOpenAPSOfflineEvents(careportalEvents); return careportalEvents; } catch (SQLException e) { log.error("Unhandled exception", e); @@ -1386,9 +1506,11 @@ public List getCareportalEvents(boolean ascending) { List careportalEvents; QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.isNotNull("json").and().isNotNull("eventType"); PreparedQuery preparedQuery = queryBuilder.prepare(); careportalEvents = getDaoCareportalEvents().query(preparedQuery); - preprocessOpenAPSOfflineEvents(careportalEvents); + careportalEvents = preprocessOpenAPSOfflineEvents(careportalEvents); return careportalEvents; } catch (SQLException e) { log.error("Unhandled exception", e); @@ -1457,7 +1579,7 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing scheduleCareportalEventChange"); - MainApp.bus().post(new EventCareportalEventChange()); + RxBus.INSTANCE.send(new EventCareportalEventChange()); scheduledCareportalEventPost = null; } } @@ -1473,15 +1595,23 @@ public void run() { // ---------------- ProfileSwitch handling --------------- - public List getProfileSwitchData(boolean ascending) { + public List getProfileSwitchData(long from, boolean ascending) { try { Dao daoProfileSwitch = getDaoProfileSwitch(); List profileSwitches; QueryBuilder queryBuilder = daoProfileSwitch.queryBuilder(); queryBuilder.orderBy("date", ascending); queryBuilder.limit(100L); + Where where = queryBuilder.where(); + where.ge("date", from); PreparedQuery preparedQuery = queryBuilder.prepare(); profileSwitches = daoProfileSwitch.query(preparedQuery); + //add last one without duration + ProfileSwitch last = getLastProfileSwitchWithoutDuration(); + if (last != null) { + if (!profileSwitches.contains(last)) + profileSwitches.add(last); + } return profileSwitches; } catch (SQLException e) { log.error("Unhandled exception", e); @@ -1489,6 +1619,28 @@ public List getProfileSwitchData(boolean ascending) { return new ArrayList<>(); } + @Nullable + private ProfileSwitch getLastProfileSwitchWithoutDuration() { + try { + Dao daoProfileSwitch = getDaoProfileSwitch(); + List profileSwitches; + QueryBuilder queryBuilder = daoProfileSwitch.queryBuilder(); + queryBuilder.orderBy("date", false); + queryBuilder.limit(1L); + Where where = queryBuilder.where(); + where.eq("durationInMinutes", 0); + PreparedQuery preparedQuery = queryBuilder.prepare(); + profileSwitches = daoProfileSwitch.query(preparedQuery); + if (profileSwitches.size() > 0) + return profileSwitches.get(0); + else + return null; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return null; + } + public List getProfileSwitchEventsFromTime(long mills, boolean ascending) { try { Dao daoProfileSwitch = getDaoProfileSwitch(); @@ -1507,6 +1659,24 @@ public List getProfileSwitchEventsFromTime(long mills, boolean as return new ArrayList<>(); } + public List getProfileSwitchEventsFromTime(long from, long to, boolean ascending) { + try { + Dao daoProfileSwitch = getDaoProfileSwitch(); + List profileSwitches; + QueryBuilder queryBuilder = daoProfileSwitch.queryBuilder(); + queryBuilder.orderBy("date", ascending); + queryBuilder.limit(100L); + Where where = queryBuilder.where(); + where.between("date", from, to); + PreparedQuery preparedQuery = queryBuilder.prepare(); + profileSwitches = daoProfileSwitch.query(preparedQuery); + return profileSwitches; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + public boolean createOrUpdate(ProfileSwitch profileSwitch) { try { ProfileSwitch old; @@ -1582,8 +1752,8 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATABASE)) log.debug("Firing EventProfileNeedsUpdate"); - MainApp.bus().post(new EventReloadProfileSwitchData()); - MainApp.bus().post(new EventProfileNeedsUpdate()); + RxBus.INSTANCE.send(new EventReloadProfileSwitchData()); + RxBus.INSTANCE.send(new EventProfileNeedsUpdate()); scheduledProfileSwitchEventPost = null; } } @@ -1753,4 +1923,34 @@ public InsightPumpID getPumpStoppedEvent(String pumpSerial, long before) { } // ---------------- Food handling --------------- + + // ---------------- PodHistory handling --------------- + + public void createOrUpdate(PodHistory podHistory) { + try { + getDaoPodHistory().createOrUpdate(podHistory); + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + } + + + public List getPodHistoryFromTime(long from, boolean ascending) { + try { + Dao daoPodHistory = getDaoPodHistory(); + List podHistories; + QueryBuilder queryBuilder = daoPodHistory.queryBuilder(); + queryBuilder.orderBy("date", ascending); + //queryBuilder.limit(100L); + Where where = queryBuilder.where(); + where.ge("date", from); + PreparedQuery preparedQuery = queryBuilder.prepare(); + podHistories = daoPodHistory.query(preparedQuery); + return podHistories; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/db/DbObjectBase.java b/app/src/main/java/info/nightscout/androidaps/db/DbObjectBase.java new file mode 100644 index 00000000000..a0c7f4bd7b6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/db/DbObjectBase.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.db; + +public interface DbObjectBase { + + long getDate(); + + long getPumpId(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/DbRequest.java b/app/src/main/java/info/nightscout/androidaps/db/DbRequest.java index f287e169cef..6cd0491907e 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DbRequest.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DbRequest.java @@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.utils.DateUtil; /** * Created by mike on 27.02.2016. @@ -40,66 +41,55 @@ public DbRequest() { } // dbAdd - public DbRequest(String action, String collection, String nsClientID, JSONObject data) { + public DbRequest(String action, String collection, JSONObject json) { this.action = action; this.collection = collection; - this.data = data.toString(); - this.nsClientID = nsClientID; + this.nsClientID = "" + DateUtil.now(); + try { + json.put("NSCLIENT_ID", nsClientID); + } catch (JSONException e) { + e.printStackTrace(); + } + this.data = json.toString(); this._id = ""; } // dbUpdate, dbUpdateUnset - public DbRequest(String action, String collection, String nsClientID, String _id, JSONObject data) { + public DbRequest(String action, String collection, String _id, JSONObject json) { this.action = action; this.collection = collection; - this.data = data.toString(); - this.nsClientID = nsClientID; + this.nsClientID = "" + DateUtil.now(); + try { + json.put("NSCLIENT_ID", nsClientID); + } catch (JSONException e) { + e.printStackTrace(); + } + this.data = json.toString(); this._id = _id; } // dbRemove - public DbRequest(String action, String collection, String nsClientID, String _id) { + public DbRequest(String action, String collection, + String _id) { + JSONObject json = new JSONObject(); this.action = action; this.collection = collection; - this.data = new JSONObject().toString(); - this.nsClientID = nsClientID; - this._id = _id; - } - - public String hash() { - return Hashing.sha1().hashString(action + collection + _id + data.toString(), Charsets.UTF_8).toString(); - } - - public JSONObject toJSON() { - JSONObject object = new JSONObject(); + this.nsClientID = "" + DateUtil.now(); try { - object.put("action", action); - object.put("collection", collection); - object.put("data", new JSONObject(data)); - if (_id != null) object.put("_id", _id); - if (nsClientID != null) object.put("nsClientID", nsClientID); + json.put("NSCLIENT_ID", nsClientID); } catch (JSONException e) { - log.error("Unhandled exception", e); + e.printStackTrace(); } - return object; + this.data = json.toString(); + this._id = _id; } - public static DbRequest fromJSON(JSONObject jsonObject) { - DbRequest result = new DbRequest(); - try { - if (jsonObject.has("action")) - result.action = jsonObject.getString("action"); - if (jsonObject.has("collection")) - result.collection = jsonObject.getString("collection"); - if (jsonObject.has("data")) - result.data = jsonObject.getJSONObject("data").toString(); - if (jsonObject.has("_id")) - result._id = jsonObject.getString("_id"); - if (jsonObject.has("nsClientID")) - result.nsClientID = jsonObject.getString("nsClientID"); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return result; + public String log() { + return + "\nnsClientID:" + nsClientID + + "\naction:" + action + + "\ncollection:" + collection + + "\ndata:" + data + + "\n_id:" + _id; } } 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 2f11b752c87..1632751d555 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java @@ -18,12 +18,14 @@ import info.nightscout.androidaps.Constants; 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.logging.L; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; @@ -219,7 +221,7 @@ public IobTotal iobCalc(long time) { IobTotal result = new IobTotal(time); InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); - int realDuration = getDurationToTime(time); + double realDuration = getDurationToTime(time); if (realDuration > 0) { double dia_ago = time - dia * 60 * 60 * 1000; @@ -247,6 +249,56 @@ public IobTotal iobCalc(long time) { return result; } + public IobTotal iobCalc(long time, Profile profile, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { + IobTotal result = new IobTotal(time); + InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); + + double realDuration = getDurationToTime(time); + double netBasalAmount = 0d; + + double sensitivityRatio = lastAutosensResult.ratio; + double normalTarget = 100; + + if (exercise_mode && isTempTarget && profile.getTargetMgdl() >= normalTarget + 5) { + // 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 + double c = half_basal_exercise_target - normalTarget; + sensitivityRatio = c / (c + profile.getTargetMgdl() - normalTarget); + } + + if (realDuration > 0) { + double netBasalRate; + double dia_ago = time - dia * 60 * 60 * 1000; + int aboutFiveMinIntervals = (int) Math.ceil(realDuration / 5d); + double spacing = realDuration / aboutFiveMinIntervals; + + for (long j = 0L; j < aboutFiveMinIntervals; j++) { + // find middle of the interval + long calcdate = (long) (date + j * spacing * 60 * 1000 + 0.5d * spacing * 60 * 1000); + + double basalRate = profile.getBasal(calcdate); + double basalRateCorrection = basalRate * (sensitivityRatio - 1); + + + netBasalRate = absoluteRate() - basalRateCorrection; + + if (calcdate > dia_ago && calcdate <= time) { + double tempBolusSize = netBasalRate * spacing / 60d; + + Treatment tempBolusPart = new Treatment(); + tempBolusPart.insulin = tempBolusSize; + tempBolusPart.date = calcdate; + + Iob aIOB = insulinInterface.iobCalcForTreatment(tempBolusPart, time, dia); + result.iob += aIOB.iobContrib; + result.activity += aIOB.activityContrib; + result.extendedBolusInsulin += tempBolusPart.insulin; + } + } + } + return result; + } + public int getRealDuration() { return getDurationToTime(System.currentTimeMillis()); } @@ -273,8 +325,8 @@ public String toStringShort() { } public String toStringMedium() { - return "E " + DecimalFormatter.to2Decimal(absoluteRate()) + "U/h (" - + getRealDuration() + "/" + durationInMinutes + ") "; + return DecimalFormatter.to2Decimal(absoluteRate()) + "U/h " + + getRealDuration() + "/" + durationInMinutes + "'"; } public String toStringTotal() { 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 0e589f05958..62b963e9ef8 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java @@ -1,7 +1,9 @@ package info.nightscout.androidaps.db; import android.graphics.Color; -import android.support.annotation.Nullable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; @@ -18,11 +20,13 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.interfaces.Interval; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.T; @@ -78,12 +82,12 @@ public ProfileSwitch profile(Profile profile) { return this; } - public ProfileSwitch source(int source) { + public ProfileSwitch source(int source) { this.source = source; return this; } - public ProfileSwitch duration(int duration) { + public ProfileSwitch duration(int duration) { this.durationInMinutes = duration; return this; } @@ -107,7 +111,7 @@ public Profile getProfileObject() { */ public String getCustomizedName() { String name = profileName; - if(LocalProfilePlugin.LOCAL_PROFILE.equals(name)){ + if (LocalProfilePlugin.LOCAL_PROFILE.equals(name)) { name = DecimalFormatter.to2Decimal(getProfileObject().percentageBasalSum()) + "U "; } if (isCPP) { @@ -156,7 +160,7 @@ public void copyFrom(ProfileSwitch t) { // -------- Interval interface --------- - Long cuttedEnd = null; + private Long cuttedEnd = null; public long durationInMsec() { return durationInMinutes * 60 * 1000L; @@ -212,16 +216,17 @@ public boolean isEndingEvent() { @Override public boolean isValid() { - boolean isValid = getProfileObject() != null && getProfileObject().isValid(DateUtil.dateAndTimeString(date)); - if (!isValid) + ProfileSwitch active = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now()); + long activeProfileSwitchDate = active != null ? active.date : -1L; + if (!isValid && date == activeProfileSwitchDate) createNotificationInvalidProfile(DateUtil.dateAndTimeString(date)); return isValid; } - public void createNotificationInvalidProfile(String detail) { + private void createNotificationInvalidProfile(String detail) { Notification notification = new Notification(Notification.ZERO_VALUE_IN_PROFILE, String.format(MainApp.gs(R.string.zerovalueinprofile), detail), Notification.LOW, 5); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } public static boolean isEvent5minBack(List list, long time, boolean zeroDurationOnly) { @@ -290,6 +295,7 @@ public int getColor() { return Color.CYAN; } + @NonNull 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 index 9ca849b7b60..be81e36ba34 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/TDD.java +++ b/app/src/main/java/info/nightscout/androidaps/db/TDD.java @@ -6,9 +6,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Objects; - +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.utils.DateUtil; /** * Created by mike on 20.09.2017. @@ -45,4 +47,24 @@ public TDD(long date, double bolus, double basal, double total){ this.basal = basal; this.total = total; } + + + @Override + public String toString() { + return "TDD [" + + "date=" + date + + "date(str)=" + DateTimeUtil.toStringFromTimeInMillis(date) + + ", bolus=" + bolus + + ", basal=" + basal + + ", total=" + total + + ']'; + } + + public String toText() { + return MainApp.gs(R.string.tddformat, DateUtil.dateStringShort(date), total, bolus, basal); + } + + public String toText(int days) { + return MainApp.gs(R.string.tddformat, String.format("%d ", days) + MainApp.gs(R.string.days), total, bolus, basal); + } } 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 a496af802bd..ef60d029118 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java +++ b/app/src/main/java/info/nightscout/androidaps/db/TempTarget.java @@ -9,6 +9,9 @@ import java.util.Objects; 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.interfaces.Interval; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.utils.DateUtil; @@ -191,4 +194,11 @@ public String toString() { '}'; } + public String friendlyDescription(String units) { + return Profile.toTargetRangeString(low, high, Constants.MGDL, units) + + units + + "@" + MainApp.gs(R.string.mins, durationInMinutes) + + (reason != null && !reason.equals("") ? "(" + reason + ")" : ""); + } + } 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 336332790a9..cf0eeab462a 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/db/TemporaryBasal.java @@ -17,6 +17,7 @@ import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; @@ -27,7 +28,7 @@ */ @DatabaseTable(tableName = DatabaseHelper.DATABASE_TEMPORARYBASALS) -public class TemporaryBasal implements Interval { +public class TemporaryBasal implements Interval, DbObjectBase { private static Logger log = LoggerFactory.getLogger(L.DATABASE); @DatabaseField(id = true) @@ -156,6 +157,14 @@ public void copyFrom(TemporaryBasal t) { netExtendedRate = t.netExtendedRate; } + public void copyFromPump(TemporaryBasal t) { + durationInMinutes = t.durationInMinutes; + isAbsolute = t.isAbsolute; + percentRate = t.percentRate; + absoluteRate = t.absoluteRate; + pumpId = t.pumpId; + } + // -------- Interval interface --------- Long cuttedEnd = null; @@ -233,7 +242,7 @@ public IobTotal iobCalc(long time, Profile profile) { double netBasalAmount = 0d; if (realDuration > 0) { - double netBasalRate = 0d; + double netBasalRate; double dia = profile.getDia(); double dia_ago = time - dia * 60 * 60 * 1000; int aboutFiveMinIntervals = (int) Math.ceil(realDuration / 5d); @@ -243,10 +252,8 @@ public IobTotal iobCalc(long time, Profile profile) { // find middle of the interval long calcdate = (long) (date + j * tempBolusSpacing * 60 * 1000 + 0.5d * tempBolusSpacing * 60 * 1000); - Double basalRate = profile.getBasal(calcdate); + double basalRate = profile.getBasal(calcdate); - if (basalRate == null) - continue; if (isAbsolute) { netBasalRate = absoluteRate - basalRate; } else { @@ -276,6 +283,73 @@ public IobTotal iobCalc(long time, Profile profile) { return result; } + public IobTotal iobCalc(long time, Profile profile, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { + + if (isFakeExtended) { + log.error("iobCalc should only be called on Extended boluses separately"); + return new IobTotal(time); + } + + IobTotal result = new IobTotal(time); + InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); + + double realDuration = getDurationToTime(time); + double netBasalAmount = 0d; + + double sensitivityRatio = lastAutosensResult.ratio; + double normalTarget = 100; + + if (exercise_mode && isTempTarget && profile.getTargetMgdl() >= normalTarget + 5) { + // 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 + double c = half_basal_exercise_target - normalTarget; + sensitivityRatio = c / (c + profile.getTargetMgdl() - normalTarget); + } + + if (realDuration > 0) { + double netBasalRate; + double dia = profile.getDia(); + double dia_ago = time - dia * 60 * 60 * 1000; + int aboutFiveMinIntervals = (int) Math.ceil(realDuration / 5d); + double tempBolusSpacing = realDuration / aboutFiveMinIntervals; + + for (long j = 0L; j < aboutFiveMinIntervals; j++) { + // find middle of the interval + long calcdate = (long) (date + j * tempBolusSpacing * 60 * 1000 + 0.5d * tempBolusSpacing * 60 * 1000); + + double basalRate = profile.getBasal(calcdate); + basalRate *= sensitivityRatio; + + if (isAbsolute) { + netBasalRate = absoluteRate - basalRate; + } else { + double abs = percentRate / 100d * profile.getBasal(calcdate); + netBasalRate = abs - basalRate; + } + + if (calcdate > dia_ago && calcdate <= time) { + double tempBolusSize = netBasalRate * tempBolusSpacing / 60d; + netBasalAmount += tempBolusSize; + + Treatment tempBolusPart = new Treatment(); + tempBolusPart.insulin = tempBolusSize; + tempBolusPart.date = calcdate; + + Iob aIOB = insulinInterface.iobCalcForTreatment(tempBolusPart, time, dia); + result.basaliob += aIOB.iobContrib; + result.activity += aIOB.activityContrib; + result.netbasalinsulin += tempBolusPart.insulin; + if (tempBolusPart.insulin > 0) { + result.hightempinsulin += tempBolusPart.insulin; + } + } + result.netRatio = netBasalRate; // ratio at the end of interval + } + } + result.netInsulin = netBasalAmount; + return result; + } + public int getRealDuration() { return getDurationToTime(System.currentTimeMillis()); } @@ -331,8 +405,10 @@ public String toStringFull() { if (isFakeExtended) { Profile profile = ProfileFunctions.getInstance().getProfile(); + if (profile == null) + return "null"; Double currentBasalRate = profile.getBasal(); - double rate = (currentBasalRate == null) ? 0d : (currentBasalRate + netExtendedRate); + double rate = currentBasalRate + netExtendedRate; return getCalcuatedPercentageIfNeeded() + DecimalFormatter.to2Decimal(rate) + "U/h (" + DecimalFormatter.to2Decimal(netExtendedRate) + "E) @" + DateUtil.timeString(date) + " " + getRealDuration() + "/" + durationInMinutes + "'"; @@ -350,12 +426,14 @@ public String toStringFull() { public String toStringShort() { if (isAbsolute || isFakeExtended) { - double rate = 0d; + double rate; if (isFakeExtended) { Profile profile = ProfileFunctions.getInstance().getProfile(); - Double currentBasalRate = profile.getBasal(); - rate = (currentBasalRate == null) ? 0d : (currentBasalRate + netExtendedRate); - } else if (isAbsolute) { + if (profile == null) + return "null"; + double currentBasalRate = profile.getBasal(); + rate = currentBasalRate + netExtendedRate; + } else { rate = absoluteRate; } @@ -375,24 +453,25 @@ public String toStringShort() { } private String getCalcuatedPercentageIfNeeded() { + Profile profile = ProfileFunctions.getInstance().getProfile(); + + if (profile == null) + return "null"; + if (isAbsolute || isFakeExtended) { - double rate = 0d; + double rate; if (isFakeExtended) { - Profile profile = ProfileFunctions.getInstance().getProfile(); - Double currentBasalRate = profile.getBasal(); - rate = (currentBasalRate == null) ? 0d : (currentBasalRate + netExtendedRate); - } else if (isAbsolute) { + double currentBasalRate = profile.getBasal(); + rate = currentBasalRate + netExtendedRate; + } else { rate = absoluteRate; } if (SP.getBoolean(R.string.key_danar_visualizeextendedaspercentage, false) && SP.getBoolean(R.string.key_danar_useextended, false)) { - Profile profile = ProfileFunctions.getInstance().getProfile(); - if (profile != null) { - double basal = profile.getBasal(); - if (basal != 0) { - return Math.round(rate * 100d / basal) + "% "; - } + double basal = profile.getBasal(); + if (basal != 0) { + return Math.round(rate * 100d / basal) + "% "; } } } @@ -400,14 +479,18 @@ private String getCalcuatedPercentageIfNeeded() { } public String toStringVeryShort() { + Profile profile = ProfileFunctions.getInstance().getProfile(); + + if (profile == null) + return "null"; + if (isAbsolute || isFakeExtended) { - double rate = 0d; + double rate; if (isFakeExtended) { - Profile profile = ProfileFunctions.getInstance().getProfile(); - Double currentBasalRate = profile.getBasal(); - rate = (currentBasalRate == null) ? 0d : (currentBasalRate + netExtendedRate); - } else if (isAbsolute) { + double currentBasalRate = profile.getBasal(); + rate = currentBasalRate + netExtendedRate; + } else { rate = absoluteRate; } return DecimalFormatter.to2Decimal(rate) + "U/h "; @@ -416,4 +499,13 @@ public String toStringVeryShort() { } } + @Override + public long getDate() { + return this.date; + } + + @Override + public long getPumpId() { + return this.pumpId; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/BolusProgressDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/BolusProgressDialog.kt new file mode 100644 index 00000000000..3f629add062 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/BolusProgressDialog.kt @@ -0,0 +1,159 @@ +package info.nightscout.androidaps.dialogs + +import android.app.Activity +import android.os.Bundle +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.BolusProgressHelperActivity +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning +import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress +import info.nightscout.androidaps.utils.FabricPrivacy +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.dialog_bolusprogress.* +import org.slf4j.LoggerFactory + +class BolusProgressDialog : DialogFragment() { + private val log = LoggerFactory.getLogger(L.UI) + private val disposable = CompositeDisposable() + + companion object { + private val DEFAULT_STATE = MainApp.gs(R.string.waitingforpump) + @JvmField + var bolusEnded = false + @JvmField + var stopPressed = false + } + + private var running = true + private var amount = 0.0 + private var state: String? = null + private var helpActivity: BolusProgressHelperActivity? = null + + fun setInsulin(amount: Double): BolusProgressDialog { + this.amount = amount + bolusEnded = false + return this + } + + fun setHelperActivity(activity: BolusProgressHelperActivity): BolusProgressDialog { + helpActivity = activity + return this + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = false + dialog?.setCanceledOnTouchOutside(false) + return inflater.inflate(R.layout.dialog_bolusprogress, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + overview_bolusprogress_title.text = String.format(MainApp.gs(R.string.overview_bolusprogress_goingtodeliver), amount) + overview_bolusprogress_stop.setOnClickListener { + if (L.isEnabled(L.UI)) log.debug("Stop bolus delivery button pressed") + stopPressed = true + overview_bolusprogress_stoppressed.visibility = View.VISIBLE + overview_bolusprogress_stop.visibility = View.INVISIBLE + ConfigBuilderPlugin.getPlugin().commandQueue.cancelAllBoluses() + } + overview_bolusprogress_progressbar.max = 100 + state = savedInstanceState?.getString("state", DEFAULT_STATE) ?: DEFAULT_STATE + overview_bolusprogress_status.text = state + stopPressed = false + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onResume() { + super.onResume() + if (L.isEnabled(L.UI)) log.debug("onResume") + if (!ConfigBuilderPlugin.getPlugin().commandQueue.bolusInQueue()) + bolusEnded = true + + if (bolusEnded) dismiss() + else running = true + + disposable.add(toObservable(EventPumpStatusChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ overview_bolusprogress_status.text = it.getStatus() }) { FabricPrivacy.logException(it) } + ) + disposable.add(toObservable(EventDismissBolusProgressIfRunning::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ if (running) dismiss() }) { FabricPrivacy.logException(it) } + ) + disposable.add(toObservable(EventOverviewBolusProgress::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (L.isEnabled(L.UI)) log.debug("Status: " + it.status + " Percent: " + it.percent) + overview_bolusprogress_status.text = it.status + overview_bolusprogress_progressbar.progress = it.percent + if (it.percent == 100) { + overview_bolusprogress_stop.visibility = View.INVISIBLE + scheduleDismiss() + } + state = it.status + }) { FabricPrivacy.logException(it) } + ) + } + + override fun dismiss() { + if (L.isEnabled(L.UI)) log.debug("dismiss") + try { + super.dismiss() + } catch (e: IllegalStateException) { + // dialog not running yet. onResume will try again. Set bolusEnded to make extra + // sure onResume will catch this + bolusEnded = true + log.error("Unhandled exception", e) + } + helpActivity?.finish() + } + + override fun onPause() { + super.onPause() + if (L.isEnabled(L.UI)) log.debug("onPause") + running = false + disposable.clear() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putString("state", state) + } + + private fun scheduleDismiss() { + if (L.isEnabled(L.UI)) log.debug("scheduleDismiss") + Thread(Runnable { + SystemClock.sleep(5000) + bolusEnded = true + val activity: Activity? = activity + activity?.runOnUiThread { + if (running) { + if (L.isEnabled(L.UI)) log.debug("executing") + try { + dismiss() + } catch (e: Exception) { + log.error("Unhandled exception", e) + } + } + } + }).start() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt new file mode 100644 index 00000000000..9503988dce7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CalibrationDialog.kt @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +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.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.XdripCalibrations +import kotlinx.android.synthetic.main.dialog_calibration.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* + +class CalibrationDialog : DialogFragmentWithDate() { + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_calibration_bg", overview_calibration_bg.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_calibration, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val units = ProfileFunctions.getSystemUnits() + val bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData()?.glucose + ?: 0.0, units) + if (units == Constants.MMOL) + overview_calibration_bg.setParams(savedInstanceState?.getDouble("overview_calibration_bg") + ?: bg, 2.0, 30.0, 0.1, DecimalFormat("0.0"), false, ok) + else + overview_calibration_bg.setParams(savedInstanceState?.getDouble("overview_calibration_bg") + ?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, ok) + overview_calibration_units.text = if (units == Constants.MMOL) MainApp.gs(R.string.mmol) else MainApp.gs(R.string.mgdl) + } + + override fun submit() :Boolean { + val units = ProfileFunctions.getSystemUnits() + val unitLabel = if (units == Constants.MMOL) MainApp.gs(R.string.mmol) else MainApp.gs(R.string.mgdl) + val actions: LinkedList = LinkedList() + val bg = overview_calibration_bg.value + actions.add(MainApp.gs(R.string.treatments_wizard_bg_label) + ": " + Profile.toCurrentUnitsString(bg) + " " + unitLabel) + if (bg > 0) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.overview_calibration), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + log.debug("USER ENTRY: CALIBRATION $bg") + XdripCalibrations.confirmAndSendCalibration(bg, context) + }) + } + } else + activity?.let { activity -> + OKDialog.show(activity, MainApp.gs(R.string.overview_calibration), MainApp.gs(R.string.no_action_selected)) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt new file mode 100644 index 00000000000..1f0c6643c19 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CarbsDialog.kt @@ -0,0 +1,228 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +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.CareportalEvent +import info.nightscout.androidaps.db.DatabaseHelper +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.db.TempTarget +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.treatments.CarbsGenerator +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import kotlinx.android.synthetic.main.dialog_carbs.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* +import kotlin.math.max + +class CarbsDialog : DialogFragmentWithDate() { + + companion object { + private const val FAV1_DEFAULT = 5 + private const val FAV2_DEFAULT = 10 + private const val FAV3_DEFAULT = 20 + } + + private val maxCarbs = MainApp.getConstraintChecker().maxCarbsAllowed.value().toDouble() + + private val textWatcher: TextWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) { + validateInputs() + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + } + + private fun validateInputs() { + val time = overview_carbs_time.value.toInt() + if (time > 12 * 60 || time < -12 * 60) { + overview_carbs_time.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.constraintapllied)) + } + if (overview_carbs_duration.value > 10) { + overview_carbs_duration.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.constraintapllied)) + } + if (overview_carbs_carbs.value.toInt() > maxCarbs) { + overview_carbs_carbs.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.carbsconstraintapplied)) + } + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_carbs_time", overview_carbs_time.value) + savedInstanceState.putDouble("overview_carbs_duration", overview_carbs_duration.value) + savedInstanceState.putDouble("overview_carbs_carbs", overview_carbs_carbs.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_carbs, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + overview_carbs_time.setParams(savedInstanceState?.getDouble("overview_carbs_time") + ?: 0.0, -12 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, ok, textWatcher) + + overview_carbs_duration.setParams(savedInstanceState?.getDouble("overview_carbs_duration") + ?: 0.0, 0.0, 10.0, 1.0, DecimalFormat("0"), false, ok, textWatcher) + + overview_carbs_carbs.setParams(savedInstanceState?.getDouble("overview_carbs_carbs") + ?: 0.0, 0.0, maxCarbs, 1.0, DecimalFormat("0"), false, ok, textWatcher) + + overview_carbs_plus1.text = toSignedString(SP.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT)) + overview_carbs_plus1.setOnClickListener { + overview_carbs_carbs.value = max(0.0, overview_carbs_carbs.value + + SP.getInt(R.string.key_carbs_button_increment_1, FAV1_DEFAULT)) + validateInputs() + } + + overview_carbs_plus2.text = toSignedString(SP.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT)) + overview_carbs_plus2.setOnClickListener { + overview_carbs_carbs.value = max(0.0, overview_carbs_carbs.value + + SP.getInt(R.string.key_carbs_button_increment_2, FAV2_DEFAULT)) + validateInputs() + } + + overview_carbs_plus3.text = toSignedString(SP.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT)) + overview_carbs_plus3.setOnClickListener { + overview_carbs_carbs.value = max(0.0, overview_carbs_carbs.value + + SP.getInt(R.string.key_carbs_button_increment_3, FAV3_DEFAULT)) + validateInputs() + } + + DatabaseHelper.actualBg()?.let { bgReading -> + if (bgReading.value < 72) + overview_carbs_hypo_tt.setChecked(true) + } + overview_carbs_hypo_tt.setOnClickListener { + overview_carbs_activity_tt.isChecked = false + overview_carbs_eating_soon_tt.isChecked = false + } + overview_carbs_activity_tt.setOnClickListener { + overview_carbs_hypo_tt.isChecked = false + overview_carbs_eating_soon_tt.isChecked = false + } + overview_carbs_eating_soon_tt.setOnClickListener { + overview_carbs_hypo_tt.isChecked = false + overview_carbs_activity_tt.isChecked = false + } + } + + private fun toSignedString(value: Int): String { + return if (value > 0) "+$value" else value.toString() + } + + override fun submit(): Boolean { + val carbs = overview_carbs_carbs.value.toInt() + val carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(Constraint(carbs)).value() + val units = ProfileFunctions.getSystemUnits() + val activityTTDuration = DefaultValueHelper.determineActivityTTDuration() + val activityTT = DefaultValueHelper.determineActivityTT() + val eatingSoonTTDuration = DefaultValueHelper.determineEatingSoonTTDuration() + val eatingSoonTT = DefaultValueHelper.determineEatingSoonTT() + val hypoTTDuration = DefaultValueHelper.determineHypoTTDuration() + val hypoTT = DefaultValueHelper.determineHypoTT() + val actions: LinkedList = LinkedList() + val unitLabel = if (units == Constants.MMOL) MainApp.gs(R.string.mmol) else MainApp.gs(R.string.mgdl) + + val activitySelected = overview_carbs_activity_tt.isChecked + if (activitySelected) + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(activityTT) + " " + unitLabel + " (" + activityTTDuration + " " + MainApp.gs(R.string.unit_minute_short) + ")") + val eatingSoonSelected = overview_carbs_eating_soon_tt.isChecked + if (eatingSoonSelected) + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + eatingSoonTTDuration + " " + MainApp.gs(R.string.unit_minute_short) + ")") + val hypoSelected = overview_carbs_hypo_tt.isChecked + if (hypoSelected) + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(hypoTT) + " " + unitLabel + " (" + hypoTTDuration + " " + MainApp.gs(R.string.unit_minute_short) + ")") + + val timeOffset = overview_carbs_time.value.toInt() + val time = eventTime + timeOffset * 1000 * 60 + if (timeOffset != 0) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(time)) + val duration = overview_carbs_duration.value.toInt() + if (duration > 0) + actions.add(MainApp.gs(R.string.duration) + ": " + duration + MainApp.gs(R.string.shorthour)) + if (carbsAfterConstraints > 0) { + actions.add(MainApp.gs(R.string.carbs) + ": " + "" + MainApp.gs(R.string.format_carbs, carbsAfterConstraints) + "") + if (carbsAfterConstraints != carbs) + actions.add("" + MainApp.gs(R.string.carbsconstraintapplied) + "") + } + val notes = notes.text.toString() + if (notes.isNotEmpty()) + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes) + + if (eventTimeChanged) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(eventTime)) + + if (carbsAfterConstraints > 0 || activitySelected || eatingSoonSelected || hypoSelected) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.carbs), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + if (activitySelected) { + log.debug("USER ENTRY: TEMPTARGET ACTIVITY $activityTT duration: $activityTTDuration") + val tempTarget = TempTarget() + .date(eventTime) + .duration(activityTTDuration) + .reason(MainApp.gs(R.string.activity)) + .source(Source.USER) + .low(Profile.toMgdl(activityTT, ProfileFunctions.getSystemUnits())) + .high(Profile.toMgdl(activityTT, ProfileFunctions.getSystemUnits())) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } else if (eatingSoonSelected) { + log.debug("USER ENTRY: TEMPTARGET EATING SOON $eatingSoonTT duration: $eatingSoonTTDuration") + val tempTarget = TempTarget() + .date(eventTime) + .duration(eatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(eatingSoonTT, ProfileFunctions.getSystemUnits())) + .high(Profile.toMgdl(eatingSoonTT, ProfileFunctions.getSystemUnits())) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } else if (hypoSelected) { + log.debug("USER ENTRY: TEMPTARGET HYPO $hypoTT duration: $hypoTTDuration") + val tempTarget = TempTarget() + .date(eventTime) + .duration(hypoTTDuration) + .reason(MainApp.gs(R.string.hypo)) + .source(Source.USER) + .low(Profile.toMgdl(hypoTT, ProfileFunctions.getSystemUnits())) + .high(Profile.toMgdl(hypoTT, ProfileFunctions.getSystemUnits())) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } + if (carbsAfterConstraints > 0) { + if (duration == 0) { + log.debug("USER ENTRY: CARBS $carbsAfterConstraints time: $time") + CarbsGenerator.createCarb(carbsAfterConstraints, time, CareportalEvent.CARBCORRECTION, notes) + } else { + log.debug("USER ENTRY: CARBS $carbsAfterConstraints time: $time duration: $duration") + CarbsGenerator.generateCarbs(carbsAfterConstraints, time, duration, notes) + NSUpload.uploadEvent(CareportalEvent.NOTE, time - 2000, MainApp.gs(R.string.generated_ecarbs_note, carbsAfterConstraints, duration, timeOffset)) + } + } + }, null) + } + } else + activity?.let { activity -> + OKDialog.show(activity, MainApp.gs(R.string.carbs), MainApp.gs(R.string.no_action_selected)) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt new file mode 100644 index 00000000000..da4a091b1d7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/CareDialog.kt @@ -0,0 +1,190 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.annotation.StringRes +import com.google.common.base.Joiner +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.CareportalEvent +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.Translator +import kotlinx.android.synthetic.main.dialog_care.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import org.json.JSONObject +import java.text.DecimalFormat +import java.util.* + +class CareDialog : DialogFragmentWithDate() { + + enum class EventType { + BGCHECK, + SENSOR_INSERT, + BATTERY_CHANGE, + NOTE, + EXERCISE + } + + private var options: EventType = EventType.BGCHECK + @StringRes + private var event: Int = R.string.none + + fun setOptions(options: EventType, @StringRes event: Int): CareDialog { + this.options = options + this.event = event + return this + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("actions_care_bg", actions_care_bg.value) + savedInstanceState.putDouble("actions_care_duration", actions_care_duration.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_care, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + actions_care_icon.setImageResource(when (options) { + EventType.BGCHECK -> R.drawable.icon_cp_bgcheck + EventType.SENSOR_INSERT -> R.drawable.icon_cp_cgm_insert + EventType.BATTERY_CHANGE -> R.drawable.icon_cp_pump_battery + EventType.NOTE -> R.drawable.icon_cp_note + EventType.EXERCISE -> R.drawable.icon_cp_exercise + }) + actions_care_title.text = MainApp.gs(when (options) { + EventType.BGCHECK -> R.string.careportal_bgcheck + EventType.SENSOR_INSERT -> R.string.careportal_cgmsensorinsert + EventType.BATTERY_CHANGE -> R.string.careportal_pumpbatterychange + EventType.NOTE -> R.string.careportal_note + EventType.EXERCISE -> R.string.careportal_exercise + }) + + when (options) { + EventType.BGCHECK -> { + action_care_duration_layout.visibility = View.GONE + } + EventType.SENSOR_INSERT, + EventType.BATTERY_CHANGE -> { + action_care_bg_layout.visibility = View.GONE + actions_care_bgsource.visibility = View.GONE + action_care_duration_layout.visibility = View.GONE + } + EventType.NOTE, + EventType.EXERCISE -> { + action_care_bg_layout.visibility = View.GONE + actions_care_bgsource.visibility = View.GONE + } + } + + val bg = Profile.fromMgdlToUnits(GlucoseStatus.getGlucoseStatusData()?.glucose + ?: 0.0, ProfileFunctions.getSystemUnits()) + val bgTextWatcher: TextWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (actions_care_sensor.isChecked) actions_care_meter.isChecked = true + } + } + + if (ProfileFunctions.getSystemUnits() == Constants.MMOL) { + actions_care_bgunits.text = MainApp.gs(R.string.mmol) + actions_care_bg.setParams(savedInstanceState?.getDouble("actions_care_bg") + ?: bg, 2.0, 30.0, 0.1, DecimalFormat("0.0"), false, ok, bgTextWatcher) + } else { + actions_care_bgunits.text = MainApp.gs(R.string.mgdl) + actions_care_bg.setParams(savedInstanceState?.getDouble("actions_care_bg") + ?: bg, 36.0, 500.0, 1.0, DecimalFormat("0"), false, ok, bgTextWatcher) + } + actions_care_duration.setParams(savedInstanceState?.getDouble("actions_care_duration") + ?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, ok) + if (options == EventType.NOTE) + notes_layout?.visibility = View.VISIBLE // independent to preferences + } + + override fun submit(): Boolean { + val enteredBy = SP.getString("careportal_enteredby", "") + val unitResId = if (ProfileFunctions.getSystemUnits() == Constants.MGDL) R.string.mgdl else R.string.mmol + + val json = JSONObject() + val actions: LinkedList = LinkedList() + if (options == EventType.BGCHECK) { + val type = + when { + actions_care_meter.isChecked -> "Finger" + actions_care_sensor.isChecked -> "Sensor" + else -> "Manual" + } + actions.add(MainApp.gs(R.string.careportal_newnstreatment_glucosetype) + ": " + Translator.translate(type)) + actions.add(MainApp.gs(R.string.treatments_wizard_bg_label) + ": " + Profile.toCurrentUnitsString(actions_care_bg.value) + " " + MainApp.gs(unitResId)) + json.put("glucose", actions_care_bg.value) + json.put("glucoseType", type) + } + if (options == EventType.NOTE || options == EventType.EXERCISE) { + actions.add(MainApp.gs(R.string.careportal_newnstreatment_duration_label) + ": " + MainApp.gs(R.string.format_mins, actions_care_duration.value.toInt())) + json.put("duration", actions_care_duration.value.toInt()) + } + val notes = notes.text.toString() + if (notes.isNotEmpty()) { + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes) + json.put("notes", notes) + } + eventTime -= eventTime % 1000 + + if (eventTimeChanged) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(eventTime)) + + json.put("created_at", DateUtil.toISOString(eventTime)) + json.put("mills", eventTime) + json.put("eventType", when (options) { + EventType.BGCHECK -> CareportalEvent.BGCHECK + EventType.SENSOR_INSERT -> CareportalEvent.SENSORCHANGE + EventType.BATTERY_CHANGE -> CareportalEvent.PUMPBATTERYCHANGE + EventType.NOTE -> CareportalEvent.NOTE + EventType.EXERCISE -> CareportalEvent.EXERCISE + }) + json.put("units", ProfileFunctions.getSystemUnits()) + if (enteredBy.isNotEmpty()) + json.put("enteredBy", enteredBy) + + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(event), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + val careportalEvent = CareportalEvent() + careportalEvent.date = eventTime + careportalEvent.source = Source.USER + careportalEvent.eventType = when (options) { + EventType.BGCHECK -> CareportalEvent.BGCHECK + EventType.SENSOR_INSERT -> CareportalEvent.SENSORCHANGE + EventType.BATTERY_CHANGE -> CareportalEvent.PUMPBATTERYCHANGE + EventType.NOTE -> CareportalEvent.NOTE + EventType.EXERCISE -> CareportalEvent.EXERCISE + } + careportalEvent.json = json.toString() + log.debug("USER ENTRY: CAREPORTAL ${careportalEvent.eventType} json: ${careportalEvent.json}") + MainApp.getDbHelper().createOrUpdate(careportalEvent) + NSUpload.uploadCareportalEntryToNS(json) + }, null) + } + return true + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/DialogFragmentWithDate.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/DialogFragmentWithDate.kt new file mode 100644 index 00000000000..ecaea0bc819 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/DialogFragmentWithDate.kt @@ -0,0 +1,124 @@ +package info.nightscout.androidaps.dialogs + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import android.os.Bundle +import android.text.format.DateFormat +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.toVisibility +import kotlinx.android.synthetic.main.datetime.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import org.slf4j.LoggerFactory +import java.util.* + +abstract class DialogFragmentWithDate : DialogFragment() { + val log = LoggerFactory.getLogger(DialogFragmentWithDate::class.java) + + var eventTime = DateUtil.now() + var eventTimeChanged = false + + //one shot guards + private var okClicked: Boolean = false + + companion object { + private var seconds: Int = (Math.random() * 59.0).toInt() + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putLong("eventTime", eventTime) + savedInstanceState.putBoolean("eventTimeChanged", eventTimeChanged) + } + + fun onCreateViewGeneral() { + dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + eventTime = savedInstanceState?.getLong("eventTime") ?: DateUtil.now() + eventTimeChanged = savedInstanceState?.getBoolean("eventTimeChanged") ?: false + overview_eventdate?.text = DateUtil.dateString(eventTime) + overview_eventtime?.text = DateUtil.timeString(eventTime) + + // create an OnDateSetListener + val dateSetListener = DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + val cal = Calendar.getInstance() + cal.timeInMillis = eventTime + cal.set(Calendar.YEAR, year) + cal.set(Calendar.MONTH, monthOfYear) + cal.set(Calendar.DAY_OF_MONTH, dayOfMonth) + eventTime = cal.timeInMillis + eventTimeChanged = true + overview_eventdate?.text = DateUtil.dateString(eventTime) + } + + overview_eventdate?.setOnClickListener { + context?.let { + val cal = Calendar.getInstance() + cal.timeInMillis = eventTime + DatePickerDialog(it, dateSetListener, + cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH), + cal.get(Calendar.DAY_OF_MONTH) + ).show() + } + } + + // create an OnTimeSetListener + val timeSetListener = TimePickerDialog.OnTimeSetListener { _, hour, minute -> + val cal = Calendar.getInstance() + cal.timeInMillis = eventTime + cal.set(Calendar.HOUR_OF_DAY, hour) + cal.set(Calendar.MINUTE, minute) + cal.set(Calendar.SECOND, seconds++) // randomize seconds to prevent creating record of the same time, if user choose time manually + eventTime = cal.timeInMillis + eventTimeChanged = true + overview_eventtime?.text = DateUtil.timeString(eventTime) + } + + overview_eventtime?.setOnClickListener { + context?.let { + val cal = Calendar.getInstance() + cal.timeInMillis = eventTime + TimePickerDialog(it, timeSetListener, + cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE), + DateFormat.is24HourFormat(context) + ).show() + } + } + + notes_layout?.visibility = SP.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() + + ok.setOnClickListener { + synchronized(okClicked) { + if (okClicked) { + log.debug("guarding: ok already clicked") + } else { + okClicked = true + if (submit()) dismiss() + else okClicked = false + } + } + } + cancel.setOnClickListener { dismiss() } + } + + abstract fun submit(): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ErrorDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ErrorDialog.kt new file mode 100644 index 00000000000..f3f71003dce --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ErrorDialog.kt @@ -0,0 +1,109 @@ +package info.nightscout.androidaps.dialogs + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +import info.nightscout.androidaps.services.AlarmSoundService +import kotlinx.android.synthetic.main.dialog_error.* +import org.slf4j.LoggerFactory +import java.util.* + +class ErrorDialog : DialogFragment() { + private val log = LoggerFactory.getLogger(ErrorDialog::class.java) + + var helperActivity: ErrorHelperActivity? = null + var status: String = "" + var title: String = "" + var sound: Int = 0 + var clipboardContent: String = "" + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + savedInstanceState?.let { bundle -> + bundle.getString("status")?.let { status = it } + bundle.getString("title")?.let { title = it } + sound = bundle.getInt("sound", R.raw.error) + } + log.debug("Error dialog displayed") + return inflater.inflate(R.layout.dialog_error, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + error_title.text = title + overview_error_ok.setOnClickListener { + log.debug("USER ENTRY: Error dialog ok button pressed") + dismiss() + } + overview_error_mute.setOnClickListener { + log.debug("USER ENTRY: Error dialog mute button pressed") + stopAlarm() + } + copyToClipboard() + startAlarm() + } + + private fun copyToClipboard() { + if (clipboardContent.length > 0) { + val clipboard = MainApp.instance().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(UUID.randomUUID().toString(), clipboardContent) + clipboard.primaryClip = clip + } + } + + override fun onSaveInstanceState(bundle: Bundle) { + super.onSaveInstanceState(bundle) + bundle.putString("status", status) + bundle.putString("title", title) + bundle.putInt("sound", sound) + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onResume() { + super.onResume() + overview_error_status.text = status + } + + override fun dismiss() { + super.dismissAllowingStateLoss() + helperActivity?.finish() + stopAlarm() + } + + private fun startAlarm() { + if (sound != 0) { + val alarm = Intent(MainApp.instance().applicationContext, AlarmSoundService::class.java) + alarm.putExtra("soundid", sound) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + MainApp.instance().startForegroundService(alarm) + } else { + MainApp.instance().startService(alarm) + } + } + } + + private fun stopAlarm() = + MainApp.instance().stopService(Intent(MainApp.instance().applicationContext, AlarmSoundService::class.java)) +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt new file mode 100644 index 00000000000..dee77051fd9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ExtendedBolusDialog.kt @@ -0,0 +1,83 @@ +package info.nightscout.androidaps.dialogs + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SafeParse +import kotlinx.android.synthetic.main.dialog_extendedbolus.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* +import kotlin.math.abs + +class ExtendedBolusDialog : DialogFragmentWithDate() { + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("actions_extendedbolus_insulin", actions_extendedbolus_insulin.value) + savedInstanceState.putDouble("actions_extendedbolus_duration", actions_extendedbolus_duration.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_extendedbolus, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val pumpDescription = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription ?: return + + val maxInsulin = MainApp.getConstraintChecker().maxExtendedBolusAllowed.value() + val extendedStep = pumpDescription.extendedBolusStep + actions_extendedbolus_insulin.setParams(savedInstanceState?.getDouble("actions_extendedbolus_insulin") + ?: extendedStep, extendedStep, maxInsulin, extendedStep, DecimalFormat("0.00"), false, ok) + + val extendedDurationStep = pumpDescription.extendedBolusDurationStep + val extendedMaxDuration = pumpDescription.extendedBolusMaxDuration + actions_extendedbolus_duration.setParams(savedInstanceState?.getDouble("actions_extendedbolus_duration") + ?: extendedDurationStep, extendedDurationStep, extendedMaxDuration, extendedDurationStep, DecimalFormat("0"), false, ok) + } + + override fun submit(): Boolean { + val insulin = SafeParse.stringToDouble(actions_extendedbolus_insulin.text) + val durationInMinutes = SafeParse.stringToInt(actions_extendedbolus_duration.text) + val actions: LinkedList = LinkedList() + val insulinAfterConstraint = MainApp.getConstraintChecker().applyExtendedBolusConstraints(Constraint(insulin)).value() + actions.add(MainApp.gs(R.string.formatinsulinunits, insulinAfterConstraint)) + actions.add(MainApp.gs(R.string.duration) + ": " + MainApp.gs(R.string.format_mins, durationInMinutes)) + if (abs(insulinAfterConstraint - insulin) > 0.01) + actions.add("" + MainApp.gs(R.string.constraintapllied) + "") + + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.extended_bolus), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + log.debug("USER ENTRY: EXTENDED BOLUS $insulinAfterConstraint duration: $durationInMinutes") + ConfigBuilderPlugin.getPlugin().commandQueue.extendedBolus(insulinAfterConstraint, durationInMinutes, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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) + } + } + }) + }, null) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt new file mode 100644 index 00000000000..a840384de04 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/FillDialog.kt @@ -0,0 +1,172 @@ +package info.nightscout.androidaps.dialogs + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +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.general.nsclient.NSUpload +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.* +import kotlinx.android.synthetic.main.dialog_fill.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import org.json.JSONException +import org.json.JSONObject +import java.util.* +import kotlin.math.abs + +class FillDialog : DialogFragmentWithDate() { + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("fill_insulinamount", fill_insulinamount.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_fill, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val maxInsulin = MainApp.getConstraintChecker().maxBolusAllowed.value() + val bolusStep = ConfigBuilderPlugin.getPlugin().activePump!!.pumpDescription.bolusStep + fill_insulinamount.setParams(savedInstanceState?.getDouble("fill_insulinamount") + ?: 0.0, 0.0, maxInsulin, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), true, ok) + val amount1 = SP.getDouble("fill_button1", 0.3) + if (amount1 > 0) { + fill_preset_button1.visibility = View.VISIBLE + fill_preset_button1.text = DecimalFormatter.toPumpSupportedBolus(amount1) // + "U"); + fill_preset_button1.setOnClickListener { fill_insulinamount.value = amount1 } + } else { + fill_preset_button1.visibility = View.GONE + } + val amount2 = SP.getDouble("fill_button2", 0.0) + if (amount2 > 0) { + fill_preset_button2.visibility = View.VISIBLE + fill_preset_button2.text = DecimalFormatter.toPumpSupportedBolus(amount2) // + "U"); + fill_preset_button2.setOnClickListener { fill_insulinamount.value = amount2 } + } else { + fill_preset_button2.visibility = View.GONE + } + val amount3 = SP.getDouble("fill_button3", 0.0) + if (amount3 > 0) { + fill_preset_button3.visibility = View.VISIBLE + fill_preset_button3.text = DecimalFormatter.toPumpSupportedBolus(amount3) // + "U"); + fill_preset_button3.setOnClickListener { fill_insulinamount.value = amount3 } + } else { + fill_preset_button3.visibility = View.GONE + } + + } + + override fun submit(): Boolean { + val insulin = SafeParse.stringToDouble(fill_insulinamount.text) + val actions: LinkedList = LinkedList() + + val insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(insulin)).value() + if (insulinAfterConstraints > 0) { + actions.add(MainApp.gs(R.string.fillwarning)) + actions.add("") + actions.add(MainApp.gs(R.string.bolus) + ": " + "" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + MainApp.gs(R.string.insulin_unit_shortname) + "") + if (abs(insulinAfterConstraints - insulin) > 0.01) + actions.add(MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints)) + } + val siteChange = fill_catheter_change.isChecked + if (siteChange) + actions.add("" + "" + MainApp.gs(R.string.record_pump_site_change) + "") + val insulinChange = fill_cartridge_change.isChecked + if (insulinChange) + actions.add("" + "" + MainApp.gs(R.string.record_insulin_cartridge_change) + "") + val notes = notes.text.toString() + if (notes.isNotEmpty()) + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes) + eventTime -= eventTime % 1000 + + if (eventTimeChanged) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(eventTime)) + + if (insulinAfterConstraints > 0 || fill_catheter_change.isChecked || fill_cartridge_change.isChecked) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.primefill), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + if (insulinAfterConstraints > 0) { + log.debug("USER ENTRY: PRIME BOLUS $insulinAfterConstraints") + requestPrimeBolus(insulinAfterConstraints, notes) + } + if (siteChange) { + log.debug("USER ENTRY: SITE CHANGE") + generateCareportalEvent(CareportalEvent.SITECHANGE, eventTime, notes) + } + if (insulinChange) { + // add a second for case of both checked + log.debug("USER ENTRY: INSULIN CHANGE") + generateCareportalEvent(CareportalEvent.INSULINCHANGE, eventTime + 1000, notes) + } + }, null) + } + } else { + activity?.let { activity -> + OKDialog.show(activity, MainApp.gs(R.string.primefill), MainApp.gs(R.string.no_action_selected)) + } + } + dismiss() + return true + } + + private fun requestPrimeBolus(insulin: Double, notes: String) { + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.insulin = insulin + detailedBolusInfo.context = context + detailedBolusInfo.source = Source.USER + detailedBolusInfo.isValid = false // do not count it in IOB (for pump history) + detailedBolusInfo.notes = notes + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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) + } + } + }) + } + + private fun generateCareportalEvent(eventType: String, time: Long, notes: String) { + val careportalEvent = CareportalEvent() + careportalEvent.source = Source.USER + careportalEvent.date = time + careportalEvent.json = generateJson(eventType, time, notes).toString() + careportalEvent.eventType = eventType + MainApp.getDbHelper().createOrUpdate(careportalEvent) + NSUpload.uploadEvent(eventType, time, notes) + } + + private fun generateJson(careportalEvent: String, time: Long, notes: String): JSONObject { + val data = JSONObject() + try { + data.put("eventType", careportalEvent) + data.put("created_at", DateUtil.toISOString(time)) + data.put("mills", time) + data.put("enteredBy", SP.getString("careportal_enteredby", MainApp.gs(R.string.app_name))) + if (notes.isNotEmpty()) data.put("notes", notes) + } catch (ignored: JSONException) { + } + return data + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt new file mode 100644 index 00000000000..42bc395b8b2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/InsulinDialog.kt @@ -0,0 +1,196 @@ +package info.nightscout.androidaps.dialogs + +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +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.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.* +import kotlinx.android.synthetic.main.dialog_insulin.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* +import kotlin.math.abs +import kotlin.math.max + +class InsulinDialog : DialogFragmentWithDate() { + + companion object { + private const val PLUS1_DEFAULT = 0.5 + private const val PLUS2_DEFAULT = 1.0 + private const val PLUS3_DEFAULT = 2.0 + } + + private val maxInsulin = MainApp.getConstraintChecker().maxBolusAllowed.value() + + private val textWatcher: TextWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) { + validateInputs() + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + } + + private fun validateInputs() { + if (abs(overview_insulin_time.value.toInt()) > 12 * 60) { + overview_insulin_time.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.constraintapllied)) + } + if (overview_insulin_amount.value > maxInsulin) { + overview_insulin_amount.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.bolusconstraintapplied)) + } + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_insulin_time", overview_insulin_time.value) + savedInstanceState.putDouble("overview_insulin_amount", overview_insulin_amount.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_insulin, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + overview_insulin_time.setParams(savedInstanceState?.getDouble("overview_insulin_time") + ?: 0.0, -12 * 60.0, 12 * 60.0, 5.0, DecimalFormat("0"), false, ok, textWatcher) + overview_insulin_amount.setParams(savedInstanceState?.getDouble("overview_insulin_amount") + ?: 0.0, 0.0, maxInsulin, ConfigBuilderPlugin.getPlugin().activePump!!.pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, ok, textWatcher) + + overview_insulin_plus05.text = toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT)) + overview_insulin_plus05.setOnClickListener { + overview_insulin_amount.value = max(0.0, overview_insulin_amount.value + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_1), PLUS1_DEFAULT)) + validateInputs() + } + overview_insulin_plus10.text = toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT)) + overview_insulin_plus10.setOnClickListener { + overview_insulin_amount.value = max(0.0, overview_insulin_amount.value + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_2), PLUS2_DEFAULT)) + validateInputs() + } + overview_insulin_plus20.text = toSignedString(SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT)) + overview_insulin_plus20.setOnClickListener { + overview_insulin_amount.value = Math.max(0.0, overview_insulin_amount.value + + SP.getDouble(MainApp.gs(R.string.key_insulin_button_increment_3), PLUS3_DEFAULT)) + validateInputs() + } + + overview_insulin_time_layout.visibility = View.GONE + overview_insulin_record_only.setOnCheckedChangeListener { _, isChecked: Boolean -> + overview_insulin_time_layout.visibility = isChecked.toVisibility() + } + } + + private fun toSignedString(value: Double): String { + val formatted = DecimalFormatter.toPumpSupportedBolus(value) + return if (value > 0) "+$formatted" else formatted + } + + override fun submit(): Boolean { + val pumpDescription = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription + ?: return false + val insulin = SafeParse.stringToDouble(overview_insulin_amount.text) + val insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(insulin)).value() + val actions: LinkedList = LinkedList() + val units = ProfileFunctions.getSystemUnits() + val unitLabel = if (units == Constants.MMOL) MainApp.gs(R.string.mmol) else MainApp.gs(R.string.mgdl) + val recordOnlyChecked = overview_insulin_record_only.isChecked + val eatingSoonChecked = overview_insulin_start_eating_soon_tt.isChecked + + if (insulinAfterConstraints > 0) { + actions.add(MainApp.gs(R.string.bolus) + ": " + "" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + MainApp.gs(R.string.insulin_unit_shortname) + "") + if (recordOnlyChecked) + actions.add("" + MainApp.gs(R.string.bolusrecordedonly) + "") + if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) + actions.add(MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints)) + } + val eatingSoonTTDuration = DefaultValueHelper.determineEatingSoonTTDuration() + val eatingSoonTT = DefaultValueHelper.determineEatingSoonTT() + if (eatingSoonChecked) + actions.add(MainApp.gs(R.string.temptargetshort) + ": " + "" + DecimalFormatter.to1Decimal(eatingSoonTT) + " " + unitLabel + " (" + eatingSoonTTDuration + " " + MainApp.gs(R.string.unit_minute_short) + ")") + + val timeOffset = overview_insulin_time.value.toInt() + val time = DateUtil.now() + T.mins(timeOffset.toLong()).msecs() + if (timeOffset != 0) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(time)) + + val notes = notes.text.toString() + if (notes.isNotEmpty()) + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes) + + if (insulinAfterConstraints > 0 || eatingSoonChecked) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.bolus), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + if (eatingSoonChecked) { + log.debug("USER ENTRY: TEMPTARGET EATING SOON $eatingSoonTT duration: $eatingSoonTTDuration") + val tempTarget = TempTarget() + .date(System.currentTimeMillis()) + .duration(eatingSoonTTDuration) + .reason(MainApp.gs(R.string.eatingsoon)) + .source(Source.USER) + .low(Profile.toMgdl(eatingSoonTT, ProfileFunctions.getSystemUnits())) + .high(Profile.toMgdl(eatingSoonTT, ProfileFunctions.getSystemUnits())) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } + if (insulinAfterConstraints > 0) { + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS + detailedBolusInfo.insulin = insulinAfterConstraints + detailedBolusInfo.context = context + detailedBolusInfo.source = Source.USER + detailedBolusInfo.notes = notes + if (recordOnlyChecked) { + log.debug("USER ENTRY: BOLUS RECORD ONLY $insulinAfterConstraints") + detailedBolusInfo.date = time + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, false) + } else { + log.debug("USER ENTRY: BOLUS $insulinAfterConstraints") + detailedBolusInfo.date = DateUtil.now() + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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 + activity?.let { activity -> + OKDialog.show(activity, MainApp.gs(R.string.bolus), MainApp.gs(R.string.no_action_selected)) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt new file mode 100644 index 00000000000..f594564a781 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileSwitchDialog.kt @@ -0,0 +1,106 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import com.google.common.base.Joiner +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import kotlinx.android.synthetic.main.dialog_profileswitch.* +import kotlinx.android.synthetic.main.notes.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* + +class ProfileSwitchDialog : DialogFragmentWithDate() { + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_profileswitch_duration", overview_profileswitch_duration.value) + savedInstanceState.putDouble("overview_profileswitch_percentage", overview_profileswitch_percentage.value) + savedInstanceState.putDouble("overview_profileswitch_timeshift", overview_profileswitch_timeshift.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_profileswitch, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + overview_profileswitch_duration.setParams(savedInstanceState?.getDouble("overview_profileswitch_duration") + ?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, ok) + overview_profileswitch_percentage.setParams(savedInstanceState?.getDouble("overview_profileswitch_percentage") + ?: 100.0, Constants.CPP_MIN_PERCENTAGE.toDouble(), Constants.CPP_MAX_PERCENTAGE.toDouble(), 1.0, DecimalFormat("0"), false, ok) + overview_profileswitch_timeshift.setParams(savedInstanceState?.getDouble("overview_profileswitch_timeshift") + ?: 0.0, Constants.CPP_MIN_TIMESHIFT.toDouble(), Constants.CPP_MAX_TIMESHIFT.toDouble(), 1.0, DecimalFormat("0"), false, ok) + + // profile + context?.let { context -> + val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile + ?: return + val profileList = profileStore.getProfileList() + val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) + overview_profileswitch_profile.adapter = adapter + // set selected to actual profile + for (p in profileList.indices) + if (profileList[p] == ProfileFunctions.getInstance().getProfileName(false)) + overview_profileswitch_profile.setSelection(p) + } ?: return + + TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(DateUtil.now())?.let { ps -> + if (ps.isCPP) { + overview_profileswitch_reuselayout.visibility = View.VISIBLE + overview_profileswitch_reusebutton.text = MainApp.gs(R.string.reuse) + " " + ps.percentage + "% " + ps.timeshift + "h" + overview_profileswitch_reusebutton.setOnClickListener { + overview_profileswitch_percentage.value = ps.percentage.toDouble() + overview_profileswitch_timeshift.value = ps.timeshift.toDouble() + } + } else { + overview_profileswitch_reuselayout.visibility = View.GONE + } + } + } + + override fun submit(): Boolean { + val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile + ?: return false + + val actions: LinkedList = LinkedList() + val duration = overview_profileswitch_duration.value + if (duration > 0) + actions.add(MainApp.gs(R.string.duration) + ": " + MainApp.gs(R.string.format_hours, duration)) + val profile = overview_profileswitch_profile.selectedItem.toString() + actions.add(MainApp.gs(R.string.profile) + ": " + profile) + val percent = overview_profileswitch_percentage.value.toInt() + if (percent != 100) + actions.add(MainApp.gs(R.string.percent) + ": " + percent + "%") + val timeShift = overview_profileswitch_timeshift.value.toInt() + if (timeShift != 0) + actions.add(MainApp.gs(R.string.careportal_newnstreatment_timeshift_label) + ": " + MainApp.gs(R.string.format_hours, timeShift.toDouble())) + val notes = notes.text.toString() + if (notes.isNotEmpty()) + actions.add(MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + notes) + if (eventTimeChanged) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(eventTime)) + + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.careportal_profileswitch), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + log.debug("USER ENTRY: PROFILE SWITCH $profile percent: $percent timeshift: $timeShift duration: $duration") + ProfileFunctions.doProfileSwitch(profileStore, profile, duration.toInt(), percent, timeShift, eventTime) + }) + } + return true + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileViewerDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileViewerDialog.kt new file mode 100644 index 00000000000..ef3b19d4d82 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/ProfileViewerDialog.kt @@ -0,0 +1,108 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +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.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import kotlinx.android.synthetic.main.close.* +import kotlinx.android.synthetic.main.dialog_profileviewer.* +import org.json.JSONObject + +class ProfileViewerDialog : DialogFragment() { + private var time: Long = 0 + + enum class Mode(val i: Int) { + RUNNING_PROFILE(1), + CUSTOM_PROFILE(2) + } + + private var mode: Mode = Mode.RUNNING_PROFILE + private var customProfileJson: String = "" + private var customProfileName: String = "" + private var customProfileUnits: String = Constants.MGDL + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + // load data from bundle + (savedInstanceState ?: arguments)?.let { bundle -> + time = bundle.getLong("time", 0) + mode = Mode.values()[bundle.getInt("mode", Mode.RUNNING_PROFILE.ordinal)] + customProfileJson = bundle.getString("customProfile", "") + customProfileUnits = bundle.getString("customProfileUnits", Constants.MGDL) + customProfileName = bundle.getString("customProfileName", "") + } + + dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + return inflater.inflate(R.layout.dialog_profileviewer, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + close.setOnClickListener { dismiss() } + + val profile: Profile? + val profileName: String? + val date: String? + when (mode) { + Mode.RUNNING_PROFILE -> { + profile = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(time)?.profileObject + profileName = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(time)?.customizedName + date = DateUtil.dateAndTimeString(TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(time)?.date + ?: 0) + profileview_datelayout.visibility = View.VISIBLE + } + + Mode.CUSTOM_PROFILE -> { + profile = Profile(JSONObject(customProfileJson), customProfileUnits) + profileName = customProfileName + date = "" + profileview_datelayout.visibility = View.GONE + } + } + profileview_noprofile.visibility = View.VISIBLE + + profile?.let { + profileview_units.text = it.units + profileview_dia.text = MainApp.gs(R.string.format_hours, it.dia) + profileview_activeprofile.text = profileName + profileview_date.text = date + profileview_ic.text = it.icList + profileview_isf.text = it.isfList + profileview_basal.text = it.basalList + profileview_target.text = it.targetList + basal_graph.show(it) + + profileview_noprofile.visibility = View.GONE + profileview_invalidprofile.visibility = if (it.isValid("ProfileViewDialog")) View.GONE else View.VISIBLE + } + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + + override fun onSaveInstanceState(bundle: Bundle) { + super.onSaveInstanceState(bundle) + bundle.putLong("time", time) + bundle.putInt("mode", mode.ordinal) + bundle.putString("customProfile", customProfileJson) + bundle.putString("customProfileName", customProfileName) + bundle.putString("customProfileUnits", customProfileUnits) + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt new file mode 100644 index 00000000000..d821aec56b4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempBasalDialog.kt @@ -0,0 +1,117 @@ +package info.nightscout.androidaps.dialogs + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +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.ProfileFunctions +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SafeParse +import kotlinx.android.synthetic.main.dialog_tempbasal.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* +import kotlin.math.abs + +class TempBasalDialog : DialogFragmentWithDate() { + private var isPercentPump = true + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("actions_tempbasal_duration", actions_tempbasal_duration.value) + savedInstanceState.putDouble("actions_tempbasal_basalpercentinput", actions_tempbasal_basalpercentinput.value) + savedInstanceState.putDouble("actions_tempbasal_basalabsoluteinput", actions_tempbasal_basalabsoluteinput.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_tempbasal, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val pumpDescription = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription ?: return + val profile = ProfileFunctions.getInstance().profile ?: return + + val maxTempPercent = pumpDescription.maxTempPercent.toDouble() + val tempPercentStep = pumpDescription.tempPercentStep.toDouble() + + actions_tempbasal_basalpercentinput.setParams(savedInstanceState?.getDouble("actions_tempbasal_basalpercentinput") + ?: 100.0, 0.0, maxTempPercent, tempPercentStep, DecimalFormat("0"), true, ok) + + actions_tempbasal_basalabsoluteinput.setParams(savedInstanceState?.getDouble("actions_tempbasal_basalabsoluteinput") + ?: profile.basal, 0.0, pumpDescription.maxTempAbsolute, pumpDescription.tempAbsoluteStep, DecimalFormat("0.00"), true, ok) + + val tempDurationStep = pumpDescription.tempDurationStep.toDouble() + val tempMaxDuration = pumpDescription.tempMaxDuration.toDouble() + actions_tempbasal_duration.setParams(savedInstanceState?.getDouble("actions_tempbasal_duration") + ?: tempDurationStep, tempDurationStep, tempMaxDuration, tempDurationStep, DecimalFormat("0"), false, ok) + + isPercentPump = pumpDescription.tempBasalStyle and PumpDescription.PERCENT == PumpDescription.PERCENT + if (isPercentPump) { + actions_tempbasal_percent_layout.visibility = View.VISIBLE + actions_tempbasal_absolute_layout.visibility = View.GONE + } else { + actions_tempbasal_percent_layout.visibility = View.GONE + actions_tempbasal_absolute_layout.visibility = View.VISIBLE + } + } + + override fun submit(): Boolean { + var percent = 0 + var absolute = 0.0 + val durationInMinutes = SafeParse.stringToInt(actions_tempbasal_duration.text) + val profile = ProfileFunctions.getInstance().profile ?: return false + val actions: LinkedList = LinkedList() + if (isPercentPump) { + val basalPercentInput = SafeParse.stringToInt(actions_tempbasal_basalpercentinput.text) + percent = MainApp.getConstraintChecker().applyBasalPercentConstraints(Constraint(basalPercentInput), profile).value() + actions.add(MainApp.gs(R.string.pump_tempbasal_label)+ ": $percent%") + actions.add(MainApp.gs(R.string.duration) + ": " + MainApp.gs(R.string.format_mins, durationInMinutes)) + if (percent != basalPercentInput) actions.add(MainApp.gs(R.string.constraintapllied)) + } else { + val basalAbsoluteInput = SafeParse.stringToDouble(actions_tempbasal_basalabsoluteinput.text) + absolute = MainApp.getConstraintChecker().applyBasalConstraints(Constraint(basalAbsoluteInput), profile).value() + actions.add(MainApp.gs(R.string.pump_tempbasal_label)+ ": " + MainApp.gs(R.string.pump_basebasalrate, absolute)) + actions.add(MainApp.gs(R.string.duration) + ": " + MainApp.gs(R.string.format_mins, durationInMinutes)) + if (abs(absolute - basalAbsoluteInput) > 0.01) + actions.add("" + MainApp.gs(R.string.constraintapllied) + "") + } + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.pump_tempbasal_label), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + val callback: Callback = object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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) + } + } + } + if (isPercentPump) { + log.debug("USER ENTRY: TEMP BASAL $percent% duration: $durationInMinutes") + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalPercent(percent, durationInMinutes, true, profile, callback) + } else { + log.debug("USER ENTRY: TEMP BASAL $absolute duration: $durationInMinutes") + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalAbsolute(absolute, durationInMinutes, true, profile, callback) + } + }) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt new file mode 100644 index 00000000000..5b1dd486921 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TempTargetDialog.kt @@ -0,0 +1,153 @@ +package info.nightscout.androidaps.dialogs + +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 com.google.common.base.Joiner +import com.google.common.collect.Lists +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.Source +import info.nightscout.androidaps.db.TempTarget +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DefaultValueHelper +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SP +import kotlinx.android.synthetic.main.dialog_temptarget.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* + +class TempTargetDialog : DialogFragmentWithDate() { + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_temptarget_duration", overview_temptarget_duration.value) + savedInstanceState.putDouble("overview_temptarget_temptarget", overview_temptarget_temptarget.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_temptarget, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + overview_temptarget_duration.setParams(savedInstanceState?.getDouble("overview_temptarget_duration") + ?: 0.0, 0.0, Constants.MAX_PROFILE_SWITCH_DURATION, 10.0, DecimalFormat("0"), false, ok) + + if (ProfileFunctions.getSystemUnits() == Constants.MMOL) + overview_temptarget_temptarget.setParams( + savedInstanceState?.getDouble("overview_temptarget_temptarget") + ?: Constants.MIN_TT_MMOL, + Constants.MIN_TT_MMOL, Constants.MAX_TT_MMOL, 0.1, DecimalFormat("0.0"), false, ok) + else + overview_temptarget_temptarget.setParams( + savedInstanceState?.getDouble("overview_temptarget_temptarget") + ?: Constants.MIN_TT_MGDL, + Constants.MIN_TT_MGDL, Constants.MAX_TT_MGDL, 1.0, DecimalFormat("0"), false, ok) + + val units = ProfileFunctions.getSystemUnits() + overview_temptarget_units.text = if (units == Constants.MMOL) MainApp.gs(R.string.mmol) else MainApp.gs(R.string.mgdl) + // temp target + context?.let { context -> + val reasonList: List = Lists.newArrayList( + MainApp.gs(R.string.manual), + MainApp.gs(R.string.cancel), + MainApp.gs(R.string.eatingsoon), + MainApp.gs(R.string.activity), + MainApp.gs(R.string.hypo) + ) + val adapterReason = ArrayAdapter(context, R.layout.spinner_centered, reasonList) + overview_temptarget_reason.adapter = adapterReason + overview_temptarget_reason.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { + val defaultDuration: Double + val defaultTarget: Double + when (reasonList[position]) { + MainApp.gs(R.string.eatingsoon) -> { + defaultDuration = DefaultValueHelper.determineEatingSoonTTDuration().toDouble() + defaultTarget = DefaultValueHelper.determineEatingSoonTT() + } + + MainApp.gs(R.string.activity) -> { + defaultDuration = DefaultValueHelper.determineActivityTTDuration().toDouble() + defaultTarget = DefaultValueHelper.determineActivityTT() + } + + MainApp.gs(R.string.hypo) -> { + defaultDuration = DefaultValueHelper.determineHypoTTDuration().toDouble() + defaultTarget = DefaultValueHelper.determineHypoTT() + } + + MainApp.gs(R.string.cancel) -> { + defaultDuration = 0.0 + defaultTarget = 0.0 + } + + else -> { + defaultDuration = overview_temptarget_duration.value + defaultTarget = overview_temptarget_temptarget.value + } + } + overview_temptarget_temptarget.value = defaultTarget + overview_temptarget_duration.value = defaultDuration + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + } + } + + override fun submit(): Boolean { + val actions: LinkedList = LinkedList() + val reason = overview_temptarget_reason.selectedItem.toString() + val unitResId = if (ProfileFunctions.getSystemUnits() == Constants.MGDL) R.string.mgdl else R.string.mmol + val target = overview_temptarget_temptarget.value + val duration = overview_temptarget_duration.value.toInt() + if (target != 0.0 && duration != 0) { + actions.add(MainApp.gs(R.string.reason) + ": " + reason) + actions.add(MainApp.gs(R.string.nsprofileview_target_label) + ": " + Profile.toCurrentUnitsString(target) + " " + MainApp.gs(unitResId)) + actions.add(MainApp.gs(R.string.duration) + ": " + MainApp.gs(R.string.format_mins, duration)) + } else { + actions.add(MainApp.gs(R.string.stoptemptarget)) + } + if (eventTimeChanged) + actions.add(MainApp.gs(R.string.time) + ": " + DateUtil.dateAndTimeString(eventTime)) + + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.careportal_temporarytarget), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + log.debug("USER ENTRY: TEMP TARGET $target duration: $duration") + if (target == 0.0 || duration == 0) { + val tempTarget = TempTarget() + .date(eventTime) + .duration(0) + .low(0.0).high(0.0) + .source(Source.USER) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } else { + val tempTarget = TempTarget() + .date(eventTime) + .duration(duration.toInt()) + .reason(reason) + .source(Source.USER) + .low(Profile.toMgdl(target, ProfileFunctions.getSystemUnits())) + .high(Profile.toMgdl(target, ProfileFunctions.getSystemUnits())) + TreatmentsPlugin.getPlugin().addToHistoryTempTarget(tempTarget) + } + if (duration == 10) SP.putBoolean(R.string.key_objectiveusetemptarget, true) + }) + } + return true + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt new file mode 100644 index 00000000000..565a65f54f2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/TreatmentDialog.kt @@ -0,0 +1,133 @@ +package info.nightscout.androidaps.dialogs + +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.common.base.Joiner +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +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.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SafeParse +import info.nightscout.androidaps.utils.ToastUtils +import kotlinx.android.synthetic.main.dialog_treatment.* +import kotlinx.android.synthetic.main.okcancel.* +import java.text.DecimalFormat +import java.util.* +import kotlin.math.abs + +class TreatmentDialog : DialogFragmentWithDate() { + private var maxCarbs = MainApp.getConstraintChecker().maxCarbsAllowed.value().toDouble() + private var maxInsulin = MainApp.getConstraintChecker().maxBolusAllowed.value() + + private val textWatcher: TextWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + validateInputs() + } + } + + private fun validateInputs() { + if (SafeParse.stringToInt(overview_treatment_carbs.text) > maxCarbs) { + overview_treatment_carbs.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.carbsconstraintapplied)) + } + if (SafeParse.stringToDouble(overview_treatment_insulin.text) > maxInsulin) { + overview_treatment_insulin.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.bolusconstraintapplied)) + } + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("overview_treatment_carbs", overview_treatment_carbs.value) + savedInstanceState.putDouble("overview_treatment_insulin", overview_treatment_insulin.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + onCreateViewGeneral() + return inflater.inflate(R.layout.dialog_treatment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val pumpDescription = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription ?: return + overview_treatment_carbs.setParams(savedInstanceState?.getDouble("overview_treatment_carbs") + ?: 0.0, 0.0, maxCarbs, 1.0, DecimalFormat("0"), false, ok, textWatcher) + overview_treatment_insulin.setParams(savedInstanceState?.getDouble("overview_treatment_insulin") + ?: 0.0, 0.0, maxInsulin, pumpDescription.bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, ok, textWatcher) + } + + override fun submit(): Boolean { + val pumpDescription = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription + ?: return false + val insulin = SafeParse.stringToDouble(overview_treatment_insulin.text) + val carbs = SafeParse.stringToInt(overview_treatment_carbs.text) + val recordOnlyChecked = overview_treatment_record_only.isChecked + val actions: LinkedList = LinkedList() + val insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(insulin)).value() + val carbsAfterConstraints = MainApp.getConstraintChecker().applyCarbsConstraints(Constraint(carbs)).value() + + if (insulinAfterConstraints > 0) { + actions.add(MainApp.gs(R.string.bolus) + ": " + "" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + MainApp.gs(R.string.insulin_unit_shortname) + "") + if (recordOnlyChecked) + actions.add("" + MainApp.gs(R.string.bolusrecordedonly) + "") + if (abs(insulinAfterConstraints - insulin) > pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) + actions.add(MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), insulin, insulinAfterConstraints)) + } + if (carbsAfterConstraints > 0) { + actions.add(MainApp.gs(R.string.carbs) + ": " + "" + MainApp.gs(R.string.format_carbs, carbsAfterConstraints) + "") + if (carbsAfterConstraints != carbs) + actions.add("" + MainApp.gs(R.string.carbsconstraintapplied) + "") + } + if (insulinAfterConstraints > 0 || carbsAfterConstraints > 0) { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.overview_treatment_label), HtmlHelper.fromHtml(Joiner.on("
").join(actions)), Runnable { + log.debug("USER ENTRY: BOLUS insulin $insulin carbs: $carbs") + val detailedBolusInfo = DetailedBolusInfo() + if (insulinAfterConstraints == 0.0) detailedBolusInfo.eventType = CareportalEvent.CARBCORRECTION + if (carbsAfterConstraints == 0) detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS + detailedBolusInfo.insulin = insulinAfterConstraints + detailedBolusInfo.carbs = carbsAfterConstraints.toDouble() + detailedBolusInfo.context = context + detailedBolusInfo.source = Source.USER + if (!(recordOnlyChecked && (detailedBolusInfo.insulin > 0 || pumpDescription.storesCarbInfo))) { + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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(detailedBolusInfo, false) + }) + } + } else + activity?.let { activity -> + OKDialog.show(activity, MainApp.gs(R.string.overview_treatment_label), MainApp.gs(R.string.no_action_selected)) + } + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt new file mode 100644 index 00000000000..c84bd4a0b67 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dialogs/WizardDialog.kt @@ -0,0 +1,340 @@ +package info.nightscout.androidaps.dialogs + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.WindowManager +import android.widget.AdapterView +import android.widget.AdapterView.OnItemSelectedListener +import android.widget.ArrayAdapter +import android.widget.CompoundButton +import androidx.fragment.app.DialogFragment +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.BgReading +import info.nightscout.androidaps.db.DatabaseHelper +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.dialog_wizard.* +import org.slf4j.LoggerFactory +import java.text.DecimalFormat +import java.util.* +import kotlin.math.abs + +class WizardDialog : DialogFragment() { + private val log = LoggerFactory.getLogger(WizardDialog::class.java) + + private var wizard: BolusWizard? = null + + //one shot guards + private var okClicked: Boolean = false + + private val textWatcher = object : TextWatcher { + override fun afterTextChanged(s: Editable) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + calculateInsulin() + } + } + + private var disposable: CompositeDisposable = CompositeDisposable() + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onSaveInstanceState(savedInstanceState: Bundle) { + super.onSaveInstanceState(savedInstanceState) + savedInstanceState.putDouble("treatments_wizard_bg_input", treatments_wizard_bg_input.value) + savedInstanceState.putDouble("treatments_wizard_carbs_input", treatments_wizard_carbs_input.value) + savedInstanceState.putDouble("treatments_wizard_correction_input", treatments_wizard_correction_input.value) + savedInstanceState.putDouble("treatments_wizard_carb_time_input", treatments_wizard_carb_time_input.value) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + dialog?.window?.requestFeature(Window.FEATURE_NO_TITLE) + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = true + dialog?.setCanceledOnTouchOutside(false) + + return inflater.inflate(R.layout.dialog_wizard, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + loadCheckedStates() + processCobCheckBox() + treatments_wizard_sbcheckbox.visibility = SP.getBoolean(R.string.key_usesuperbolus, false).toVisibility() + treatments_wizard_notes_layout.visibility = SP.getBoolean(R.string.key_show_notes_entry_dialogs, false).toVisibility() + + val maxCarbs = MainApp.getConstraintChecker().maxCarbsAllowed.value() + val maxCorrection = MainApp.getConstraintChecker().maxBolusAllowed.value() + + treatments_wizard_bg_input.setParams(savedInstanceState?.getDouble("treatments_wizard_bg_input") + ?: 0.0, 0.0, 500.0, 0.1, DecimalFormat("0.0"), false, ok, textWatcher) + treatments_wizard_carbs_input.setParams(savedInstanceState?.getDouble("treatments_wizard_carbs_input") + ?: 0.0, 0.0, maxCarbs.toDouble(), 1.0, DecimalFormat("0"), false, ok, textWatcher) + val bolusStep = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.bolusStep + ?: 0.1 + treatments_wizard_correction_input.setParams(savedInstanceState?.getDouble("treatments_wizard_correction_input") + ?: 0.0, -maxCorrection, maxCorrection, bolusStep, DecimalFormatter.pumpSupportedBolusFormat(), false, ok, textWatcher) + treatments_wizard_carb_time_input.setParams(savedInstanceState?.getDouble("treatments_wizard_carb_time_input") + ?: 0.0, -60.0, 60.0, 5.0, DecimalFormat("0"), false, ok, textWatcher) + initDialog() + + treatments_wizard_percent_used.text = MainApp.gs(R.string.format_percent, SP.getInt(R.string.key_boluswizard_percentage, 100)) + // ok button + ok.setOnClickListener { + if (okClicked) { + log.debug("guarding: ok already clicked") + } else { + okClicked = true + calculateInsulin() + context?.let { context -> + wizard?.confirmAndExecute(context) + } + } + dismiss() + } + // cancel button + cancel.setOnClickListener { dismiss() } + // checkboxes + treatments_wizard_bgcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_ttcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_cobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_basaliobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_bolusiobcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_bgtrendcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + treatments_wizard_sbcheckbox.setOnCheckedChangeListener(::onCheckedChanged) + + val showCalc = SP.getBoolean(MainApp.gs(R.string.key_wizard_calculation_visible), false) + treatments_wizard_delimiter.visibility = showCalc.toVisibility() + treatments_wizard_resulttable.visibility = showCalc.toVisibility() + treatments_wizard_calculationcheckbox.isChecked = showCalc + treatments_wizard_calculationcheckbox.setOnCheckedChangeListener { _, isChecked -> + run { + SP.putBoolean(MainApp.gs(R.string.key_wizard_calculation_visible), isChecked) + treatments_wizard_delimiter.visibility = isChecked.toVisibility() + treatments_wizard_resulttable.visibility = isChecked.toVisibility() + } + } + // profile spinner + treatments_wizard_profile.onItemSelectedListener = object : OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.noprofileselected)) + ok.visibility = View.GONE + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + calculateInsulin() + ok.visibility = View.VISIBLE + } + } + // bus + disposable.add(RxBus + .toObservable(EventAutosensCalculationFinished::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + activity?.runOnUiThread { calculateInsulin() } + }, { + FabricPrivacy.logException(it) + }) + ) + + } + + override fun onDestroyView() { + super.onDestroyView() + disposable.clear() + } + + private fun onCheckedChanged(buttonView: CompoundButton, @Suppress("UNUSED_PARAMETER") state: Boolean) { + saveCheckedStates() + treatments_wizard_ttcheckbox.isEnabled = treatments_wizard_bgcheckbox.isChecked && TreatmentsPlugin.getPlugin().tempTargetFromHistory != null + if (buttonView.id == treatments_wizard_cobcheckbox.id) + processCobCheckBox() + calculateInsulin() + } + + private fun processCobCheckBox() { + if (treatments_wizard_cobcheckbox.isChecked) { + treatments_wizard_bolusiobcheckbox.isEnabled = false + treatments_wizard_basaliobcheckbox.isEnabled = false + treatments_wizard_bolusiobcheckbox.isChecked = true + treatments_wizard_basaliobcheckbox.isChecked = true + } else { + treatments_wizard_bolusiobcheckbox.isEnabled = true + treatments_wizard_basaliobcheckbox.isEnabled = true + } + } + + private fun saveCheckedStates() { + SP.putBoolean(MainApp.gs(R.string.key_wizard_include_cob), treatments_wizard_cobcheckbox.isChecked) + SP.putBoolean(MainApp.gs(R.string.key_wizard_include_trend_bg), treatments_wizard_bgtrendcheckbox.isChecked) + } + + private fun loadCheckedStates() { + treatments_wizard_bgtrendcheckbox.isChecked = SP.getBoolean(MainApp.gs(R.string.key_wizard_include_trend_bg), false) + treatments_wizard_cobcheckbox.isChecked = SP.getBoolean(MainApp.gs(R.string.key_wizard_include_cob), false) + } + + private fun initDialog() { + val profile = ProfileFunctions.getInstance().profile + val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile + + if (profile == null || profileStore == null) { + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.noprofile)) + dismiss() + return + } + + val profileList: ArrayList + profileList = profileStore.getProfileList() + profileList.add(0, MainApp.gs(R.string.active)) + context?.let { context -> + val adapter = ArrayAdapter(context, R.layout.spinner_centered, profileList) + treatments_wizard_profile.adapter = adapter + } ?: return + + val units = ProfileFunctions.getSystemUnits() + treatments_wizard_bgunits.text = units + if (units == Constants.MGDL) + treatments_wizard_bg_input.setStep(1.0) + else + treatments_wizard_bg_input.setStep(0.1) + + // Set BG if not old + val lastBg = DatabaseHelper.actualBg() + + if (lastBg != null) { + treatments_wizard_bg_input.value = lastBg.valueToUnits(units) + } else { + treatments_wizard_bg_input.value = 0.0 + } + treatments_wizard_ttcheckbox.isEnabled = TreatmentsPlugin.getPlugin().tempTargetFromHistory != null + + // IOB calculation + TreatmentsPlugin.getPlugin().updateTotalIOBTreatments() + val bolusIob = TreatmentsPlugin.getPlugin().lastCalculationTreatments.round() + TreatmentsPlugin.getPlugin().updateTotalIOBTempBasals() + val basalIob = TreatmentsPlugin.getPlugin().lastCalculationTempBasals.round() + + treatments_wizard_bolusiobinsulin.text = StringUtils.formatInsulin(-bolusIob.iob) + treatments_wizard_basaliobinsulin.text = StringUtils.formatInsulin(-basalIob.basaliob) + + calculateInsulin() + + treatments_wizard_percent_used.visibility = (SP.getInt(R.string.key_boluswizard_percentage, 100) != 100).toVisibility() + } + + private fun calculateInsulin() { + val profileStore = ConfigBuilderPlugin.getPlugin().activeProfileInterface?.profile + if (treatments_wizard_profile.selectedItem == null || profileStore == null) + return // not initialized yet + var profileName = treatments_wizard_profile.selectedItem.toString() + val specificProfile: Profile? + if (profileName == MainApp.gs(R.string.active)) { + specificProfile = ProfileFunctions.getInstance().profile + profileName = ProfileFunctions.getInstance().profileName + } else + specificProfile = profileStore.getSpecificProfile(profileName) + + if (specificProfile == null) return + + // Entered values + var bg = SafeParse.stringToDouble(treatments_wizard_bg_input.text) + val carbs = SafeParse.stringToInt(treatments_wizard_carbs_input.text) + val correction = SafeParse.stringToDouble(treatments_wizard_correction_input.text) + val carbsAfterConstraint = MainApp.getConstraintChecker().applyCarbsConstraints(Constraint(carbs)).value() + if (abs(carbs - carbsAfterConstraint) > 0.01) { + treatments_wizard_carbs_input.value = 0.0 + ToastUtils.showToastInUiThread(MainApp.instance().applicationContext, MainApp.gs(R.string.carbsconstraintapplied)) + return + } + + bg = if (treatments_wizard_bgcheckbox.isChecked) bg else 0.0 + val tempTarget = if (treatments_wizard_ttcheckbox.isChecked) TreatmentsPlugin.getPlugin().tempTargetFromHistory else null + + // COB + var cob = 0.0 + if (treatments_wizard_cobcheckbox.isChecked) { + val cobInfo = IobCobCalculatorPlugin.getPlugin().getCobInfo(false, "Wizard COB") + cobInfo.displayCob?.let { cob = it } + } + + val carbTime = SafeParse.stringToInt(treatments_wizard_carb_time_input.text) + + wizard = BolusWizard(specificProfile, profileName, tempTarget, carbsAfterConstraint, cob, bg, correction, + SP.getInt(R.string.key_boluswizard_percentage, 100).toDouble(), + treatments_wizard_bgcheckbox.isChecked, + treatments_wizard_cobcheckbox.isChecked, + treatments_wizard_bolusiobcheckbox.isChecked, + treatments_wizard_basaliobcheckbox.isChecked, + treatments_wizard_sbcheckbox.isChecked, + treatments_wizard_ttcheckbox.isChecked, + treatments_wizard_bgtrendcheckbox.isChecked, + treatment_wizard_notes.text.toString(), carbTime) + + wizard?.let { wizard -> + treatments_wizard_bg.text = String.format(MainApp.gs(R.string.format_bg_isf), BgReading().value(Profile.toMgdl(bg, ProfileFunctions.getSystemUnits())).valueToUnitsToString(ProfileFunctions.getSystemUnits()), wizard.sens) + treatments_wizard_bginsulin.text = StringUtils.formatInsulin(wizard.insulinFromBG) + + treatments_wizard_carbs.text = String.format(MainApp.gs(R.string.format_carbs_ic), carbs.toDouble(), wizard.ic) + treatments_wizard_carbsinsulin.text = StringUtils.formatInsulin(wizard.insulinFromCarbs) + + treatments_wizard_bolusiobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromBolusIOB) + treatments_wizard_basaliobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromBasalsIOB) + + treatments_wizard_correctioninsulin.text = StringUtils.formatInsulin(wizard.insulinFromCorrection) + + // Superbolus + treatments_wizard_sb.text = if (treatments_wizard_sbcheckbox.isChecked) MainApp.gs(R.string.twohours) else "" + treatments_wizard_sbinsulin.text = StringUtils.formatInsulin(wizard.insulinFromSuperBolus) + + // Trend + if (treatments_wizard_bgtrendcheckbox.isChecked && wizard.glucoseStatus != null) { + treatments_wizard_bgtrend.text = ((if (wizard.trend > 0) "+" else "") + + Profile.toUnitsString(wizard.trend * 3, wizard.trend * 3 / Constants.MMOLL_TO_MGDL, ProfileFunctions.getSystemUnits()) + + " " + ProfileFunctions.getSystemUnits()) + } else { + treatments_wizard_bgtrend.text = "" + } + treatments_wizard_bgtrendinsulin.text = StringUtils.formatInsulin(wizard.insulinFromTrend) + + // COB + if (treatments_wizard_cobcheckbox.isChecked) { + treatments_wizard_cob.text = String.format(MainApp.gs(R.string.format_cob_ic), cob, wizard.ic) + treatments_wizard_cobinsulin.text = StringUtils.formatInsulin(wizard.insulinFromCOB) + } else { + treatments_wizard_cob.text = "" + treatments_wizard_cobinsulin.text = "" + } + + if (wizard.calculatedTotalInsulin > 0.0 || carbsAfterConstraint > 0.0) { + val insulinText = if (wizard.calculatedTotalInsulin > 0.0) MainApp.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin) else "" + val carbsText = if (carbsAfterConstraint > 0.0) MainApp.gs(R.string.format_carbs, carbsAfterConstraint) else "" + treatments_wizard_total.text = MainApp.gs(R.string.result_insulin_carbs, insulinText, carbsText) + ok.visibility = View.VISIBLE + } else { + treatments_wizard_total.text = MainApp.gs(R.string.missing_carbs, wizard.carbsEquivalent.toInt()) + ok.visibility = View.INVISIBLE + } + } + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/Event.java b/app/src/main/java/info/nightscout/androidaps/events/Event.java deleted file mode 100644 index 864d55d6f74..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/Event.java +++ /dev/null @@ -1,16 +0,0 @@ -package info.nightscout.androidaps.events; - -import org.apache.commons.lang3.builder.ReflectionToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -/** Base class for all events posted on the event bus. */ -public abstract class Event { - static { - ReflectionToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE); - } - - @Override - public String toString() { - return ReflectionToStringBuilder.toString(this); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/Event.kt b/app/src/main/java/info/nightscout/androidaps/events/Event.kt new file mode 100644 index 00000000000..a44f65e8368 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/Event.kt @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.events + +import org.apache.commons.lang3.builder.ReflectionToStringBuilder +import org.apache.commons.lang3.builder.ToStringStyle + +/** Base class for all events posted on the event bus. */ +abstract class Event { + + override fun toString(): String { + return ReflectionToStringBuilder.toString(this) + } + + companion object { + init { + ReflectionToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.java deleted file mode 100644 index 2dfbf9ae354..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.java +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.events; - -/** Base class for events to update the UI, mostly a specific tab. */ -public class EventAcceptOpenLoopChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.kt new file mode 100644 index 00000000000..552564edfc7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventAcceptOpenLoopChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventAcceptOpenLoopChange : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.java b/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.java deleted file mode 100644 index 9ce91a9a397..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.java +++ /dev/null @@ -1,7 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 07.07.2016. - */ -public class EventAppExit extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.kt b/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.kt new file mode 100644 index 00000000000..640b586f5fa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventAppExit.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventAppExit : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java b/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java deleted file mode 100644 index 17262cfb851..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 23.01.2018. - */ - -public class EventAppInitialized extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.kt b/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.kt new file mode 100644 index 00000000000..293f9698f25 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventAppInitialized : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.java b/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.java deleted file mode 100644 index cb727758bba..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.java +++ /dev/null @@ -1,21 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by adrian on 07/02/17. - */ - -public class EventBolusRequested extends Event { - private double amount; - - public EventBolusRequested (double amount){ - this.amount = amount; - } - - public double getAmount() { - return amount; - } - - public void setAmount(double amount) { - this.amount = amount; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt b/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt new file mode 100644 index 00000000000..a528ef1656f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventBolusRequested.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventBolusRequested(var amount: Double) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.java deleted file mode 100644 index 9b47ed39cbb..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 25.05.2017. - */ - -public class EventCareportalEventChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.kt new file mode 100644 index 00000000000..e7d52d86c01 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventCareportalEventChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventCareportalEventChange : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.java b/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.java deleted file mode 100644 index bcd9061133d..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.java +++ /dev/null @@ -1,13 +0,0 @@ -package info.nightscout.androidaps.events; - -public class EventChargingState { - - public boolean isCharging = false; - - public EventChargingState() {} - - public EventChargingState(boolean isCharging) { - this.isCharging = isCharging; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.kt b/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.kt new file mode 100644 index 00000000000..f9ff60a71de --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventChargingState.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventChargingState(val isCharging: Boolean) : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.java deleted file mode 100644 index ad5f558fe85..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 17.02.2017. - */ - -public class EventConfigBuilderChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.kt new file mode 100644 index 00000000000..b674374fad5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventConfigBuilderChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventConfigBuilderChange : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventCustomActionsChanged.kt b/app/src/main/java/info/nightscout/androidaps/events/EventCustomActionsChanged.kt new file mode 100644 index 00000000000..d75bf612ce5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventCustomActionsChanged.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventCustomActionsChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java b/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java deleted file mode 100644 index e52761dc583..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.java +++ /dev/null @@ -1,8 +0,0 @@ -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/EventCustomCalculationFinished.kt b/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.kt new file mode 100644 index 00000000000..f6092b395d0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventCustomCalculationFinished.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventCustomCalculationFinished : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.java deleted file mode 100644 index 8881b0ecc14..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 15.05.2017. - */ - -public class EventExtendedBolusChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.kt new file mode 100644 index 00000000000..4ed0ca5ffe3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventExtendedBolusChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventExtendedBolusChange : EventLoop() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java b/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java deleted file mode 100644 index 0d07cd6c61e..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventFeatureRunning.java +++ /dev/null @@ -1,36 +0,0 @@ -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/EventFoodDatabaseChanged.java b/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java deleted file mode 100644 index 48e6be3a14c..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 20.09.2017. - */ - -public class EventFoodDatabaseChanged extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.kt b/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.kt new file mode 100644 index 00000000000..83402c3cb61 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventFoodDatabaseChanged.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventFoodDatabaseChanged : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.java b/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.java deleted file mode 100644 index f2bef1d3d07..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 13.12.2016. - */ - -public class EventInitializationChanged extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.kt b/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.kt new file mode 100644 index 00000000000..33ab0062c5c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventInitializationChanged.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventInitializationChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventLocationChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventLocationChange.kt new file mode 100644 index 00000000000..fee6c9f800d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventLocationChange.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.events + +import android.location.Location + +class EventLocationChange(var location: Location) : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventLoop.java b/app/src/main/java/info/nightscout/androidaps/events/EventLoop.java deleted file mode 100644 index d694d52537e..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventLoop.java +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.events; - -/** Supeclass for all events concerned with input or output into or from the LoopPlugin. */ -public abstract class EventLoop extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventLoop.kt b/app/src/main/java/info/nightscout/androidaps/events/EventLoop.kt new file mode 100644 index 00000000000..dd28e2323b0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventLoop.kt @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.events + +/** Supeclass for all events concerned with input or output into or from the LoopPlugin. */ +abstract class EventLoop : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java deleted file mode 100644 index 546d6f86247..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.java +++ /dev/null @@ -1,17 +0,0 @@ -package info.nightscout.androidaps.events; - - -import info.nightscout.androidaps.utils.StringUtils; - -public class EventNetworkChange extends Event { - - public boolean mobileConnected = false; - public boolean wifiConnected = false; - - public String ssid = ""; - public boolean roaming = false; - - public String getSsid() { - return StringUtils.removeSurroundingQuotes(ssid); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt new file mode 100644 index 00000000000..62c8bdd13e8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.events + +import info.nightscout.androidaps.utils.StringUtils + +class EventNetworkChange : Event() { + + var mobileConnected = false + var wifiConnected = false + + var ssid = "" + var roaming = false + + fun connectedSsid(): String { + return StringUtils.removeSurroundingQuotes(ssid) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java deleted file mode 100644 index dc4d434e0ab..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java +++ /dev/null @@ -1,17 +0,0 @@ -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/EventNewBG.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.kt new file mode 100644 index 00000000000..08c05407c93 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.events + +import info.nightscout.androidaps.db.BgReading + +class EventNewBG(val bgReading: BgReading?) : EventLoop() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.java b/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.java deleted file mode 100644 index f26a310b6b3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.java +++ /dev/null @@ -1,7 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 04.06.2016. - */ -public class EventNewBasalProfile extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.kt new file mode 100644 index 00000000000..2ffa5a97244 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNewBasalProfile.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventNewBasalProfile : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java b/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java deleted file mode 100644 index 90b6f5681b6..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.java +++ /dev/null @@ -1,35 +0,0 @@ -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/EventNsFood.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.kt new file mode 100644 index 00000000000..2f34e76c85f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNsFood.kt @@ -0,0 +1,19 @@ +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. + */ + +class EventNsFood(val mode: Int, val payload: Bundle) : Event() { + companion object { + val ADD = 0 + val UPDATE = 1 + val REMOVE = 2 + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java b/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java deleted file mode 100644 index 2c5ba6c9c07..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.java +++ /dev/null @@ -1,36 +0,0 @@ -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/EventNsTreatment.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.kt new file mode 100644 index 00000000000..149894c2218 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNsTreatment.kt @@ -0,0 +1,21 @@ +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. + */ + +class EventNsTreatment(val mode: Int, val payload: JSONObject) : Event() { + companion object { + val ADD = 0 + val UPDATE = 1 + val REMOVE = 2 + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.java deleted file mode 100644 index f23d4e802a8..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.java +++ /dev/null @@ -1,25 +0,0 @@ -package info.nightscout.androidaps.events; - -import info.nightscout.androidaps.MainApp; - -/** - * Created by mike on 19.06.2016. - */ -public class EventPreferenceChange extends Event { - public String changedKey; - public EventPreferenceChange(String key) { - changedKey = key; - } - - public EventPreferenceChange(int resourceID) { - changedKey = MainApp.gs(resourceID); - } - - public boolean isChanged(int id) { - return changedKey.equals(MainApp.gs(id)); - } - - public boolean isChanged(String id) { - return changedKey.equals(id); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt new file mode 100644 index 00000000000..d224d75df1e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt @@ -0,0 +1,19 @@ +package info.nightscout.androidaps.events + +import info.nightscout.androidaps.MainApp + +class EventPreferenceChange : Event { + private var changedKey: String? = null + + constructor(key: String) { + changedKey = key + } + + constructor(resourceID: Int) { + changedKey = MainApp.gs(resourceID) + } + + fun isChanged(id: Int): Boolean { + return changedKey == MainApp.gs(id) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.java b/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.java deleted file mode 100644 index 9e3f3b08c75..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 02.06.2017. - */ - -public class EventProfileNeedsUpdate extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.kt b/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.kt new file mode 100644 index 00000000000..2baf1db9452 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventProfileNeedsUpdate.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventProfileNeedsUpdate : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.java b/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.java deleted file mode 100644 index 0b2d933c12b..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.java +++ /dev/null @@ -1,4 +0,0 @@ -package info.nightscout.androidaps.events; - -public class EventProfileStoreChanged extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.kt b/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.kt new file mode 100644 index 00000000000..0e839ca2d35 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventProfileStoreChanged.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventProfileStoreChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.java b/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.java deleted file mode 100644 index 6729a4e7031..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.java +++ /dev/null @@ -1,63 +0,0 @@ -package info.nightscout.androidaps.events; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; - -/** - * Created by mike on 19.02.2017. - */ - -public class EventPumpStatusChanged extends Event { - public static final int CONNECTING = 0; - public static final int CONNECTED = 1; - public static final int HANDSHAKING = 2; - public static final int PERFORMING = 3; - public static final int DISCONNECTING = 4; - public static final int DISCONNECTED = 5; - - public int sStatus = DISCONNECTED; - public int sSecondsElapsed = 0; - public String sPerfomingAction = ""; - - public static String error = ""; - - public EventPumpStatusChanged(int status) { - sStatus = status; - sSecondsElapsed = 0; - error = ""; - } - - public EventPumpStatusChanged(int status, int secondsElapsed) { - sStatus = status; - sSecondsElapsed = secondsElapsed; - error = ""; - } - - public EventPumpStatusChanged(int status, String error) { - sStatus = status; - sSecondsElapsed = 0; - this.error = error; - } - - public EventPumpStatusChanged(String action) { - sStatus = PERFORMING; - sSecondsElapsed = 0; - sPerfomingAction = action; - } - - public String textStatus() { - if (sStatus == CONNECTING) - return String.format(MainApp.gs(R.string.danar_history_connectingfor), sSecondsElapsed); - else if (sStatus == HANDSHAKING) - return MainApp.gs(R.string.handshaking); - else if (sStatus == CONNECTED) - return MainApp.gs(R.string.connected); - else if (sStatus == PERFORMING) - return sPerfomingAction; - else if (sStatus == DISCONNECTING) - return MainApp.gs(R.string.disconnecting); - else if (sStatus == DISCONNECTED) - return ""; - return ""; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.kt b/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.kt new file mode 100644 index 00000000000..3d25bc1ca55 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventPumpStatusChanged.kt @@ -0,0 +1,62 @@ +package info.nightscout.androidaps.events + +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R + +class EventPumpStatusChanged : EventStatus { + + enum class Status { + CONNECTING, + CONNECTED, + HANDSHAKING, + PERFORMING, + DISCONNECTING, + DISCONNECTED + } + + var sStatus: Status = Status.DISCONNECTED + var sSecondsElapsed = 0 + var sPerfomingAction = "" + var error = "" + + constructor(status: Status) { + sStatus = status + sSecondsElapsed = 0 + error = "" + } + + constructor(status: Status, secondsElapsed: Int) { + sStatus = status + sSecondsElapsed = secondsElapsed + error = "" + } + + constructor(status: Status, error: String) { + sStatus = status + sSecondsElapsed = 0 + this.error = error + } + + constructor(action: String) { + sStatus = Status.PERFORMING + sSecondsElapsed = 0 + sPerfomingAction = action + } + + // status for startup wizard + override fun getStatus(): String { + if (sStatus == Status.CONNECTING) + return String.format(MainApp.gs(R.string.danar_history_connectingfor), sSecondsElapsed) + else if (sStatus == Status.HANDSHAKING) + return MainApp.gs(R.string.handshaking) + else if (sStatus == Status.CONNECTED) + return MainApp.gs(R.string.connected) + else if (sStatus == Status.PERFORMING) + return sPerfomingAction + else if (sStatus == Status.DISCONNECTING) + return MainApp.gs(R.string.disconnecting) + else if (sStatus == Status.DISCONNECTED) + return "" + return "" + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventRebuildTabs.kt b/app/src/main/java/info/nightscout/androidaps/events/EventRebuildTabs.kt new file mode 100644 index 00000000000..aa0db3467ab --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventRebuildTabs.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventRebuildTabs @JvmOverloads constructor(var recreate: Boolean = false) : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventRefreshGui.java b/app/src/main/java/info/nightscout/androidaps/events/EventRefreshGui.java deleted file mode 100644 index 390ad8ea4ff..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventRefreshGui.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 13.06.2016. - */ -public class EventRefreshGui extends Event { - public boolean recreate = false; - public EventRefreshGui(boolean recreate) { - this.recreate = recreate; - } - public EventRefreshGui(){ - this(false); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.java b/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.java deleted file mode 100644 index 2ba78fa9ec3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.java +++ /dev/null @@ -1,13 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 16.06.2017. - */ - -public class EventRefreshOverview extends Event { - public String from; - - public EventRefreshOverview(String from) { - this.from = from; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.kt b/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.kt new file mode 100644 index 00000000000..533a25dd40f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventRefreshOverview.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventRefreshOverview(var from: String) : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.java b/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.java deleted file mode 100644 index 212e8856d95..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 12.06.2017. - */ - -public class EventReloadProfileSwitchData extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt b/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt new file mode 100644 index 00000000000..6f6d848b5ec --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventReloadProfileSwitchData.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventReloadProfileSwitchData : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.java b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.java deleted file mode 100644 index 80125cbb4a3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 29.05.2017. - */ - -public class EventReloadTempBasalData extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.kt b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.kt new file mode 100644 index 00000000000..fa8f7208963 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTempBasalData.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventReloadTempBasalData : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.java b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.java deleted file mode 100644 index 0ba9b95ad76..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.java +++ /dev/null @@ -1,13 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 29.05.2017. - */ - -public class EventReloadTreatmentData extends Event { - public Object next; - - public EventReloadTreatmentData(Object next) { - this.next = next; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.kt b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.kt new file mode 100644 index 00000000000..1f8b2938b9b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventReloadTreatmentData.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventReloadTreatmentData(var next: Event) : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventStatus.kt b/app/src/main/java/info/nightscout/androidaps/events/EventStatus.kt new file mode 100644 index 00000000000..193c3b1fdb4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventStatus.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.events + +// pass string to startup wizard +abstract class EventStatus :Event() { + abstract fun getStatus() : String +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.java deleted file mode 100644 index 73660bb00ec..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.java +++ /dev/null @@ -1,7 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 05.06.2016. - */ -public class EventTempBasalChange extends EventLoop { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.kt new file mode 100644 index 00000000000..3f3ecf732e6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTempBasalChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventTempBasalChange : EventLoop() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.java deleted file mode 100644 index 4e3bf5c5f81..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.events; - -/** - * Created by mike on 13.01.2017. - */ - -public class EventTempTargetChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.kt new file mode 100644 index 00000000000..c108d6589c5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTempTargetChange.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.events + +class EventTempTargetChange : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java deleted file mode 100644 index 990f28a388c..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java +++ /dev/null @@ -1,17 +0,0 @@ -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/events/EventTreatmentChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.kt new file mode 100644 index 00000000000..9cbc9d15632 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.events + +import info.nightscout.androidaps.plugins.treatments.Treatment + +class EventTreatmentChange(val treatment: Treatment?) : EventLoop() diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.java b/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.java deleted file mode 100644 index 3471d2e851d..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.java +++ /dev/null @@ -1,5 +0,0 @@ -package info.nightscout.androidaps.events; - -/** Base class for events to update the UI, mostly a specific tab. */ -public abstract class EventUpdateGui extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.kt new file mode 100644 index 00000000000..cc21e784b98 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventUpdateGui.kt @@ -0,0 +1,4 @@ +package info.nightscout.androidaps.events + +/** Base class for events to update the UI, mostly a specific tab. */ +abstract class EventUpdateGui : Event() 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 e2f3460701e..7d32e37ff09 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/APSInterface.java @@ -6,8 +6,9 @@ * Created by mike on 10.06.2016. */ public interface APSInterface { - public APSResult getLastAPSResult(); - public long getLastAPSRun(); + APSResult getLastAPSResult(); - public void invoke(String initiator, boolean tempBasalFallback); + long getLastAPSRun(); + + void invoke(String initiator, boolean tempBasalFallback); } 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 f1c79dd5cbe..14b2d549af1 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/ConstraintsInterface.java @@ -61,6 +61,6 @@ default Constraint applyCarbsConstraints(Constraint carbs) { default Constraint applyMaxIOBConstraints(Constraint maxIob) { return maxIob; - }; + } } 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 01be2965390..5d4ec01fd99 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginBase.java @@ -1,16 +1,26 @@ package info.nightscout.androidaps.interfaces; import android.os.SystemClock; -import android.support.v4.app.FragmentActivity; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import androidx.fragment.app.FragmentActivity; + +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.EventConfigBuilderChange; +import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.configBuilder.EventConfigBuilderUpdateGui; import info.nightscout.androidaps.queue.CommandQueue; +import info.nightscout.androidaps.utils.OKDialog; +import info.nightscout.androidaps.utils.SP; /** * Created by mike on 09.06.2016. @@ -38,17 +48,42 @@ public PluginBase(PluginDescription pluginDescription) { // Default always calls invoke // Plugins that have special constraints if they get switched to may override this method - public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity activity) { - pluginSwitcher.invoke(); + public void switchAllowed(boolean newState, FragmentActivity activity, PluginType type) { + performPluginSwitch(newState, type); + } + + protected void confirmPumpPluginActivation(boolean newState, FragmentActivity activity, PluginType type) { + if (type == PluginType.PUMP) { + boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); + if (allowHardwarePump || activity == null) { + performPluginSwitch(newState, type); + } else { + OKDialog.showConfirmation(activity, MainApp.gs(R.string.allow_hardware_pump_text), () -> { + performPluginSwitch(newState, type); + SP.putBoolean("allow_hardware_pump", true); + if (L.isEnabled(L.PUMP)) + log.debug("First time HW pump allowed!"); + }, () -> { + RxBus.INSTANCE.send(new EventConfigBuilderUpdateGui()); + if (L.isEnabled(L.PUMP)) + log.debug("User does not allow switching to HW pump!"); + }); + } + } else { + performPluginSwitch(newState, type); + } } -// public PluginType getType() { -// return mainType; -// } - -// public String getFragmentClass() { -// return fragmentClass; -// } + public void performPluginSwitch(boolean enabled, PluginType type) { + setPluginEnabled(type, enabled); + setFragmentVisible(type, enabled); + ConfigBuilderPlugin.getPlugin().processOnEnabledCategoryChanged(this, getType()); + ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxEnabled"); + RxBus.INSTANCE.send(new EventRebuildTabs()); + RxBus.INSTANCE.send(new EventConfigBuilderChange()); + RxBus.INSTANCE.send(new EventConfigBuilderUpdateGui()); + ConfigBuilderPlugin.getPlugin().logPluginStatus(); + } public String getName() { if (pluginDescription.pluginName == -1) @@ -80,10 +115,6 @@ 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; @@ -143,7 +174,7 @@ public void setFragmentVisible(PluginType type, boolean fragmentVisible) { } public boolean isFragmentVisible() { - if (pluginDescription.alwayVisible) + if (pluginDescription.alwaysVisible) return true; if (pluginDescription.neverVisible) return false; @@ -183,4 +214,10 @@ protected void onStop() { protected void onStateChange(PluginType type, State oldState, State newState) { } + + public void preprocessPreferences(@NotNull final PreferenceFragment preferenceFragment) { + } + + public void updatePreferenceSummary(@NotNull final Preference pref) { + } } \ 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 index 1634fc672db..2929d3f21b1 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.java @@ -3,7 +3,7 @@ public class PluginDescription { PluginType mainType = PluginType.GENERAL; String fragmentClass = null; - public boolean alwayVisible = false; + public boolean alwaysVisible = false; public boolean neverVisible = false; public boolean alwaysEnabled = false; boolean showInList = true; @@ -11,7 +11,6 @@ public class PluginDescription { int shortName = -1; int description = -1; int preferencesId = -1; - int advancedPreferencesId = -1; public boolean enableByDefault = false; public boolean visibleByDefault = false; @@ -30,8 +29,8 @@ public PluginDescription alwaysEnabled(boolean alwaysEnabled) { return this; } - public PluginDescription alwayVisible(boolean alwayVisible) { - this.alwayVisible = alwayVisible; + public PluginDescription alwaysVisible(boolean alwaysVisible) { + this.alwaysVisible = alwaysVisible; return this; } @@ -60,11 +59,6 @@ public PluginDescription preferencesId(int preferencesId) { return this; } - public PluginDescription advancedPreferencesId(int advancedPreferencesId) { - this.advancedPreferencesId = advancedPreferencesId; - return this; - } - public PluginDescription enableByDefault(boolean enableByDefault) { this.enableByDefault = enableByDefault; return this; diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java index e3b368fe866..5fb3b459b62 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/ProfileInterface.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.interfaces; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.data.ProfileStore; @@ -10,6 +10,5 @@ public interface ProfileInterface { @Nullable ProfileStore getProfile(); - String getUnits(); String getProfileName(); } 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 e4a02883e85..0b83944d946 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java @@ -55,6 +55,8 @@ public PumpDescription () { public boolean supportsTDDs; public boolean needsManualTDDLoad; + public boolean hasFixedUnreachableAlert; + public boolean hasCustomUnreachableAlertCheck; public void resetSettings() { isBolusCapable = true; @@ -87,6 +89,9 @@ public void resetSettings() { supportsTDDs = false; needsManualTDDLoad = true; + + hasFixedUnreachableAlert = false; + hasCustomUnreachableAlertCheck = false; } public void setPumpDescription(PumpType pumpType) { @@ -134,6 +139,9 @@ public void setPumpDescription(PumpType pumpType) { needsManualTDDLoad = pumpCapability.hasCapability(PumpCapability.ManualTDDLoad); is30minBasalRatesCapable = pumpCapability.hasCapability(PumpCapability.BasalRate30min); + + hasFixedUnreachableAlert = pumpType.getHasFixedUnreachableAlert(); + hasCustomUnreachableAlertCheck = pumpType.getHasCustomUnreachableAlertCheck(); } } 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 3690312cf8f..3b17aef1f3e 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -7,8 +7,11 @@ import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.utils.TimeChangeType; /** * Created by mike on 04.06.2016. @@ -16,21 +19,30 @@ public interface PumpInterface { boolean isInitialized(); // true if pump status has been read and is ready to accept commands + boolean isSuspended(); // true if suspended (not delivering insulin) + boolean isBusy(); // if true pump is not ready to accept commands right now + boolean isConnected(); // true if BT connection is established + boolean isConnecting(); // true if BT connection is in progress + boolean isHandshakeInProgress(); // true if BT is connected but initial handshake is still in progress + void finishHandshaking(); // set initial handshake completed void connect(String reason); + void disconnect(String reason); + void stopConnecting(); void getPumpStatus(); // Upload to pump new basal profile PumpEnactResult setNewBasalProfile(Profile profile); + boolean isThisProfileSet(Profile profile); long lastDataTime(); @@ -42,18 +54,29 @@ public interface PumpInterface { int getBatteryLevel(); // in percent as integer PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo); + void stopBolusDelivering(); + 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 PumpEnactResult cancelTempBasal(boolean enforceNew); + PumpEnactResult cancelExtendedBolus(); // Status to be passed to NS JSONObject getJSONStatus(Profile profile, String profileName); - String deviceID(); + + ManufacturerType manufacturer(); + + PumpType model(); + + String serialNumber(); // Pump capabilities PumpDescription getPumpDescription(); @@ -65,10 +88,22 @@ public interface PumpInterface { PumpEnactResult loadTDDs(); - public boolean canHandleDST(); + boolean canHandleDST(); List getCustomActions(); void executeCustomAction(CustomActionType customActionType); + /** + * This method will be called when time or Timezone changes, and pump driver can then do a specific action (for + * example update clock on pump). + * @param timeChangeType + */ + void timezoneOrDSTChanged(TimeChangeType timeChangeType); + + /* Only used for pump types where hasCustomUnreachableAlertCheck=true */ + default boolean isUnreachableAlertTimeoutExceeded(long alertTimeoutMilliseconds) { + return false; + } + } 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 0ef71dac2a7..cbdd5d696a3 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java @@ -5,6 +5,7 @@ import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.NonOverlappingIntervals; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; @@ -25,12 +26,13 @@ public interface TreatmentsInterface { IobTotal getLastCalculationTreatments(); IobTotal getCalculationToTimeTreatments(long time); IobTotal getLastCalculationTempBasals(); - IobTotal getCalculationToTimeTempBasals(long time, Profile profile); + IobTotal getCalculationToTimeTempBasals(long time); MealData getMealData(); List getTreatmentsFromHistory(); - List getTreatments5MinBackFromHistory(long time); + List getCarbTreatments5MinBackFromHistory(long time); + List getTreatmentsFromHistoryAfterTimestamp(long timestamp); long getLastBolusTime(); // real basals (not faked by extended bolus) @@ -42,7 +44,7 @@ public interface TreatmentsInterface { // basal that can be faked by extended boluses boolean isTempBasalInProgress(); TemporaryBasal getTempBasalFromHistory(long time); - Intervals getTemporaryBasalsFromHistory(); + NonOverlappingIntervals getTemporaryBasalsFromHistory(); boolean isInHistoryExtendedBoluslInProgress(); ExtendedBolus getExtendedBolusFromHistory(long time); @@ -63,4 +65,4 @@ public interface TreatmentsInterface { long oldestDataAvailable(); -} +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/logging/L.java b/app/src/main/java/info/nightscout/androidaps/logging/L.java index f685bbd4247..25b6750844f 100644 --- a/app/src/main/java/info/nightscout/androidaps/logging/L.java +++ b/app/src/main/java/info/nightscout/androidaps/logging/L.java @@ -77,6 +77,7 @@ public static void resetToDefaults() { public static final String CORE = "CORE"; public static final String AUTOSENS = "AUTOSENS"; + public static final String AUTOMATION = "AUTOMATION"; public static final String EVENTS = "EVENTS"; public static final String GLUCOSE = "GLUCOSE"; public static final String BGSOURCE = "BGSOURCE"; @@ -87,6 +88,7 @@ public static void resetToDefaults() { public static final String DATAFOOD = "DATAFOOD"; public static final String DATATREATMENTS = "DATATREATMENTS"; public static final String NSCLIENT = "NSCLIENT"; + public static final String TIDEPOOL = "TIDEPOOL"; public static final String CONSTRAINTS = "CONSTRAINTS"; public static final String PUMP = "PUMP"; public static final String PUMPQUEUE = "PUMPQUEUE"; @@ -96,11 +98,13 @@ public static void resetToDefaults() { public static final String PROFILE = "PROFILE"; public static final String CONFIGBUILDER = "CONFIGBUILDER"; public static final String UI = "UI"; + public static final String LOCATION = "LOCATION"; public static final String SMS = "SMS"; private static void initialize() { logElements = new ArrayList<>(); logElements.add(new LogElement(APS, true)); + logElements.add(new LogElement(AUTOMATION, true)); logElements.add(new LogElement(AUTOSENS, false)); logElements.add(new LogElement(BGSOURCE, true)); logElements.add(new LogElement(GLUCOSE, false)); @@ -112,8 +116,10 @@ private static void initialize() { logElements.add(new LogElement(DATASERVICE, true)); logElements.add(new LogElement(DATATREATMENTS, true)); logElements.add(new LogElement(EVENTS, false, true)); + logElements.add(new LogElement(LOCATION, true)); logElements.add(new LogElement(NOTIFICATION, true)); logElements.add(new LogElement(NSCLIENT, true)); + logElements.add(new LogElement(TIDEPOOL, true)); logElements.add(new LogElement(OVERVIEW, true)); logElements.add(new LogElement(PROFILE, true)); logElements.add(new LogElement(PUMP, true)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java index b3b0c091d56..7e568af2d9c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/APSResult.java @@ -247,7 +247,7 @@ public List getPredictions() { } } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return array; } @@ -280,7 +280,7 @@ public long getLatestPredictionsTime() { } } } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return latest; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java index 69afe3d8e76..26d7359efef 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java @@ -381,13 +381,11 @@ public JSONObject mongoRecord() { try { if (device != null) record.put("device", device); if (pump != null) record.put("pump", pump); - if (suggested != null) { - JSONObject openaps = new JSONObject(); - if (enacted != null) openaps.put("enacted", enacted); - if (suggested != null) openaps.put("suggested", suggested); - if (iob != null) openaps.put("iob", iob); - record.put("openaps", openaps); - } + JSONObject openaps = new JSONObject(); + if (enacted != null) openaps.put("enacted", enacted); + if (suggested != null) openaps.put("suggested", suggested); + if (iob != null) openaps.put("iob", iob); + record.put("openaps", openaps); if (uploaderBattery != 0) record.put("uploaderBattery", uploaderBattery); if (created_at != null) record.put("created_at", created_at); } catch (JSONException e) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.java deleted file mode 100644 index 951631eb0c5..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.java +++ /dev/null @@ -1,145 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.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.squareup.otto.Subscribe; - -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.aps.loop.events.EventLoopSetLastRunGui; -import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.utils.FabricPrivacy; - -public class LoopFragment extends SubscriberFragment { - @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; - @BindView(R.id.loop_constraints) - TextView constraintsView; - @BindView(R.id.loop_tbrsetbypump) - TextView tbrSetByPumpView; - @BindView(R.id.loop_smbsetbypump) - TextView smbSetByPumpView; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.loop_fragment, container, false); - unbinder = ButterKnife.bind(this, view); - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } - return null; - } - - @OnClick(R.id.loop_run) - void onRunClick() { - lastRunView.setText(MainApp.gs(R.string.executing)); - new Thread(() -> LoopPlugin.getPlugin().invoke("Loop button", true)).start(); - } - - @Subscribe - public void onStatusEvent(final EventLoopUpdateGui ev) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventLoopSetLastRunGui ev) { - clearGUI(); - final Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - synchronized (LoopFragment.this) { - if (lastRunView != null) lastRunView.setText(ev.text); - } - }); - } - - - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - synchronized (LoopFragment.this) { - if (!isBound()) return; - 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); - } - } - }); - } - - void clearGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - synchronized (LoopFragment.this) { - if (isBound()) { - requestView.setText(""); - constraintsProcessedView.setText(""); - sourceView.setText(""); - lastRunView.setText(""); - lastEnactView.setText(""); - tbrSetByPumpView.setText(""); - smbSetByPumpView.setText(""); - } - } - }); - } - - boolean isBound() { - return requestView != null - && constraintsProcessedView != null - && sourceView != null - && lastRunView != null - && lastEnactView != null - && tbrSetByPumpView != null - && smbSetByPumpView != null - && constraintsView != null - && runNowButton != null; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt new file mode 100644 index 00000000000..5625fd978fd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopFragment.kt @@ -0,0 +1,115 @@ +package info.nightscout.androidaps.plugins.aps.loop + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui +import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.HtmlHelper +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.plusAssign +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.loop_fragment.* + +class LoopFragment : Fragment() { + + private var disposable: CompositeDisposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.loop_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + loop_run.setOnClickListener { + loop_lastrun.text = MainApp.gs(R.string.executing) + Thread { LoopPlugin.getPlugin().invoke("Loop button", true) }.start() + } + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable += RxBus + .toObservable(EventLoopUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateGUI() + }, { + FabricPrivacy.logException(it) + }) + + disposable += RxBus + .toObservable(EventLoopSetLastRunGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + clearGUI() + loop_lastrun?.text = it.text + }, { FabricPrivacy.logException(it) }) + + updateGUI() + SP.putBoolean(R.string.key_objectiveuseloop, true) + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + fun updateGUI() { + if (loop_request == null) return + LoopPlugin.lastRun?.let { + loop_request?.text = it.request?.toSpanned() ?: "" + loop_constraintsprocessed?.text = it.constraintsProcessed?.toSpanned() ?: "" + loop_source?.text = it.source ?: "" + loop_lastrun?.text = it.lastAPSRun?.let { lastRun -> DateUtil.dateAndTimeString(lastRun.time) } + ?: "" + loop_smbrequest_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBRequest) + loop_smbexecution_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBEnact) + loop_tbrrequest_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastTBRRequest) + loop_tbrexecution_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastTBREnact) + + loop_tbrsetbypump?.text = it.tbrSetByPump?.let { tbrSetByPump -> HtmlHelper.fromHtml(tbrSetByPump.toHtml()) } + ?: "" + loop_smbsetbypump?.text = it.smbSetByPump?.let { smbSetByPump -> HtmlHelper.fromHtml(smbSetByPump.toHtml()) } + ?: "" + + val constraints = + it.constraintsProcessed?.let { constraintsProcessed -> + val allConstraints = Constraint(0.0) + constraintsProcessed.rateConstraint?.let { rateConstraint -> allConstraints.copyReasons(rateConstraint) } + constraintsProcessed.smbConstraint?.let { smbConstraint -> allConstraints.copyReasons(smbConstraint) } + allConstraints.mostLimitedReasons + } ?: "" + loop_constraints?.text = constraints + } + } + + @Synchronized + private fun clearGUI() { + loop_request?.text = "" + loop_constraints?.text = "" + loop_constraintsprocessed?.text = "" + loop_source?.text = "" + loop_lastrun?.text = "" + loop_smbrequest_time?.text = "" + loop_smbexecution_time?.text = "" + loop_tbrrequest_time?.text = "" + loop_tbrexecution_time?.text = "" + loop_tbrsetbypump?.text = "" + loop_smbsetbypump?.text = "" + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java index 92378a1d6b5..4710cf9b642 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java @@ -10,11 +10,12 @@ 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.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,26 +48,30 @@ import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopSetLastRunGui; import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui; import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.commands.Command; +import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; -import info.nightscout.androidaps.utils.ToastUtils; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 05.08.2016. */ public class LoopPlugin extends PluginBase { private static Logger log = LoggerFactory.getLogger(L.APS); + private CompositeDisposable disposable = new CompositeDisposable(); private static final String CHANNEL_ID = "AndroidAPS-Openloop"; @@ -82,9 +87,9 @@ public static LoopPlugin getPlugin() { return loopPlugin; } - private long loopSuspendedTill = 0L; // end of manual loop suspend - private boolean isSuperBolus = false; - private boolean isDisconnected = false; + private long loopSuspendedTill; // end of manual loop suspend + private boolean isSuperBolus; + private boolean isDisconnected; public class LastRun { public APSResult request = null; @@ -93,8 +98,11 @@ public class LastRun { public PumpEnactResult smbSetByPump = null; public String source = null; public Date lastAPSRun = null; - public Date lastEnact = null; - public Date lastOpenModeAccept; + public long lastTBREnact = 0; + public long lastSMBEnact = 0; + public long lastTBRRequest = 0; + public long lastSMBRequest = 0; + public long lastOpenModeAccept; } static public LastRun lastRun = null; @@ -115,9 +123,39 @@ public LoopPlugin() { @Override protected void onStart() { - MainApp.bus().register(this); createNotificationChannel(); super.onStart(); + disposable.add(RxBus.INSTANCE + .toObservable(EventTempTargetChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + invoke("EventTempTargetChange", true); + }, FabricPrivacy::logException) + ); + /** + * 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. + *

+ */ + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + // Autosens calculation not triggered by a new BG + if (!(event.getCause() instanceof EventNewBG)) return; + + BgReading bgReading = DatabaseHelper.actualBg(); + // BG outdated + if (bgReading == null) return; + // already looped with that value + if (bgReading.date <= lastBgTriggeredRun) return; + + lastBgTriggeredRun = bgReading.date; + invoke("AutosenseCalculation for " + bgReading, true); + }, FabricPrivacy::logException) + ); } private void createNotificationChannel() { @@ -134,8 +172,8 @@ private void createNotificationChannel() { @Override protected void onStop() { + disposable.clear(); super.onStop(); - MainApp.bus().unregister(this); } @Override @@ -144,43 +182,10 @@ public boolean specialEnableCondition() { return pump == null || pump.getPumpDescription().isTempBasalCapable; } - /** - * 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. - *

- */ - @Subscribe - 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; - } - - lastBgTriggeredRun = bgReading.date; - invoke("AutosenseCalculation for " + bgReading, true); - } - public long suspendedTo() { return loopSuspendedTill; } - @Subscribe - public void onStatusEvent(final EventTempTargetChange ev) { - new Thread(() -> invoke("EventTempTargetChange", true)).start(); - } - - public void suspendTo(long endTime) { loopSuspendedTill = endTime; isSuperBolus = false; @@ -278,7 +283,7 @@ public synchronized void invoke(String initiator, boolean allowNotification, boo String message = MainApp.gs(R.string.loopdisabled) + "\n" + loopEnabled.getReasons(); if (L.isEnabled(L.APS)) log.debug(message); - MainApp.bus().post(new EventLoopSetLastRunGui(message)); + RxBus.INSTANCE.send(new EventLoopSetLastRunGui(message)); return; } final PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); @@ -289,10 +294,10 @@ public synchronized void invoke(String initiator, boolean allowNotification, boo Profile profile = ProfileFunctions.getInstance().getProfile(); - if (!ProfileFunctions.getInstance().isProfileValid("Loop")) { + if (profile == null || !ProfileFunctions.getInstance().isProfileValid("Loop")) { if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.noprofileselected)); - MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.gs(R.string.noprofileselected))); + RxBus.INSTANCE.send(new EventLoopSetLastRunGui(MainApp.gs(R.string.noprofileselected))); return; } @@ -307,7 +312,7 @@ public synchronized void invoke(String initiator, boolean allowNotification, boo // Check if we have any result if (result == null) { - MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.gs(R.string.noapsselected))); + RxBus.INSTANCE.send(new EventLoopSetLastRunGui(MainApp.gs(R.string.noapsselected))); return; } @@ -343,20 +348,24 @@ public synchronized void invoke(String initiator, boolean allowNotification, boo lastRun.source = ((PluginBase) usedAPS).getName(); lastRun.tbrSetByPump = null; lastRun.smbSetByPump = null; + lastRun.lastTBREnact = 0; + lastRun.lastTBRRequest = 0; + lastRun.lastSMBEnact = 0; + lastRun.lastSMBRequest = 0; NSUpload.uploadDeviceStatus(); if (isSuspended()) { if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.loopsuspended)); - MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.gs(R.string.loopsuspended))); + RxBus.INSTANCE.send(new EventLoopSetLastRunGui(MainApp.gs(R.string.loopsuspended))); return; } if (pump.isSuspended()) { if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.pumpsuspended)); - MainApp.bus().post(new EventLoopSetLastRunGui(MainApp.gs(R.string.pumpsuspended))); + RxBus.INSTANCE.send(new EventLoopSetLastRunGui(MainApp.gs(R.string.pumpsuspended))); return; } @@ -372,32 +381,35 @@ public synchronized void invoke(String initiator, boolean allowNotification, boo lastRun.tbrSetByPump = waiting; if (resultAfterConstraints.bolusRequested) lastRun.smbSetByPump = waiting; - MainApp.bus().post(new EventLoopUpdateGui()); + RxBus.INSTANCE.send(new EventLoopUpdateGui()); FabricPrivacy.getInstance().logCustom("APSRequest"); applyTBRRequest(resultAfterConstraints, profile, new Callback() { @Override public void run() { if (result.enacted || result.success) { lastRun.tbrSetByPump = result; - lastRun.lastEnact = lastRun.lastAPSRun; + lastRun.lastTBRRequest = lastRun.lastAPSRun.getTime(); + lastRun.lastTBREnact = DateUtil.now(); + RxBus.INSTANCE.send(new EventLoopUpdateGui()); 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; + lastRun.lastSMBRequest = lastRun.lastAPSRun.getTime(); + lastRun.lastSMBEnact = DateUtil.now(); } else { new Thread(() -> { SystemClock.sleep(1000); LoopPlugin.getPlugin().invoke("tempBasalFallback", allowNotification, true); }).start(); } - MainApp.bus().post(new EventLoopUpdateGui()); + RxBus.INSTANCE.send(new EventLoopUpdateGui()); } }); } - MainApp.bus().post(new EventLoopUpdateGui()); + RxBus.INSTANCE.send(new EventLoopUpdateGui()); } }); } else { @@ -414,7 +426,7 @@ public void run() { .setAutoCancel(true) .setPriority(Notification.PRIORITY_HIGH) .setCategory(Notification.CATEGORY_ALARM) - .setVisibility(Notification.VISIBILITY_PUBLIC); + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); if (SP.getBoolean("wearcontrol", false)) { builder.setLocalOnly(true); } @@ -438,7 +450,7 @@ public void run() { (NotificationManager) MainApp.instance().getSystemService(Context.NOTIFICATION_SERVICE); // mId allows you to update the notification later on. mNotificationManager.notify(Constants.notificationID, builder.build()); - MainApp.bus().post(new EventNewOpenLoopNotification()); + RxBus.INSTANCE.send(new EventNewOpenLoopNotification()); // Send to Wear ActionStringHandler.handleInitiate("changeRequest"); @@ -451,7 +463,7 @@ public void run() { } } - MainApp.bus().post(new EventLoopUpdateGui()); + RxBus.INSTANCE.send(new EventLoopUpdateGui()); } finally { if (L.isEnabled(L.APS)) log.debug("invoke end"); @@ -466,16 +478,13 @@ public void acceptChangeRequest() { public void run() { if (result.enacted) { lastRun.tbrSetByPump = result; - lastRun.lastEnact = new Date(); - lastRun.lastOpenModeAccept = new Date(); + lastRun.lastTBRRequest = lastRun.lastAPSRun.getTime(); + lastRun.lastTBREnact = DateUtil.now(); + lastRun.lastOpenModeAccept = DateUtil.now(); NSUpload.uploadDeviceStatus(); - ObjectivesPlugin objectivesPlugin = MainApp.getSpecificPlugin(ObjectivesPlugin.class); - if (objectivesPlugin != null) { - ObjectivesPlugin.getPlugin().manualEnacts++; - ObjectivesPlugin.getPlugin().saveProgress(); - } + SP.incInt(R.string.key_ObjectivesmanualEnacts); } - MainApp.bus().post(new EventAcceptOpenLoopChange()); + RxBus.INSTANCE.send(new EventAcceptOpenLoopChange()); } }); FabricPrivacy.getInstance().logCustom("AcceptTemp"); @@ -644,25 +653,53 @@ public void disconnectPump(int durationInMinutes, Profile profile) { TreatmentsInterface activeTreatments = TreatmentsPlugin.getPlugin(); LoopPlugin.getPlugin().disconnectTo(System.currentTimeMillis() + durationInMinutes * 60 * 1000L); - ConfigBuilderPlugin.getPlugin().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 (pump.getPumpDescription().tempBasalStyle == PumpDescription.ABSOLUTE) { + ConfigBuilderPlugin.getPlugin().getCommandQueue().tempBasalAbsolute(0, durationInMinutes, 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); + } } - } - }); + }); + } else { + ConfigBuilderPlugin.getPlugin().getCommandQueue().tempBasalPercent(0, durationInMinutes, 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); + } + } + }); + } + if (pump.getPumpDescription().isExtendedBolusCapable && activeTreatments.isInHistoryExtendedBoluslInProgress()) { ConfigBuilderPlugin.getPlugin().getCommandQueue().cancelExtended(new Callback() { @Override public void run() { if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.extendedbolusdeliveryerror)); + 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.extendedbolusdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); } } }); } - NSUpload.uploadOpenAPSOffline(durationInMinutes); + createOfflineEvent(durationInMinutes); } public void suspendLoop(int durationInMinutes) { @@ -671,11 +708,32 @@ public void suspendLoop(int durationInMinutes) { @Override public void run() { if (!result.success) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.tempbasaldeliveryerror)); + 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); } } }); - NSUpload.uploadOpenAPSOffline(durationInMinutes); + createOfflineEvent(durationInMinutes); } + public void createOfflineEvent(int durationInMinutes) { + JSONObject data = new JSONObject(); + try { + data.put("eventType", CareportalEvent.OPENAPSOFFLINE); + data.put("duration", durationInMinutes); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + CareportalEvent event = new CareportalEvent(); + event.date = DateUtil.now(); + event.source = Source.USER; + event.eventType = CareportalEvent.OPENAPSOFFLINE; + event.json = data.toString(); + MainApp.getDbHelper().createOrUpdate(event); + NSUpload.uploadOpenAPSOffline(event); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.java deleted file mode 100644 index 0d2a45f5280..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.loop.events; - -import info.nightscout.androidaps.events.EventUpdateGui; - -/** - * Created by mike on 05.08.2016. - */ -public class EventLoopSetLastRunGui extends EventUpdateGui { - public String text = null; - - public EventLoopSetLastRunGui(String text) { - this.text = text; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.kt new file mode 100644 index 00000000000..19c7e92c5ce --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopSetLastRunGui.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.aps.loop.events + +import info.nightscout.androidaps.events.EventUpdateGui + +/** + * Created by mike on 05.08.2016. + */ +class EventLoopSetLastRunGui(val text: String) : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.java deleted file mode 100644 index f746dfb0a09..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.loop.events; - -import info.nightscout.androidaps.events.EventUpdateGui; - -/** - * Created by mike on 05.08.2016. - */ -public class EventLoopUpdateGui extends EventUpdateGui { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.kt new file mode 100644 index 00000000000..89507d85f81 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventLoopUpdateGui.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.aps.loop.events + +import info.nightscout.androidaps.events.EventUpdateGui + +/** + * Created by mike on 05.08.2016. + */ +class EventLoopUpdateGui : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.java deleted file mode 100644 index 6a0a4dc0cf9..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.loop.events; - -import info.nightscout.androidaps.events.Event; - -/** - * Created by mike on 07.08.2016. - */ -public class EventNewOpenLoopNotification extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.kt new file mode 100644 index 00000000000..2933318dd62 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/events/EventNewOpenLoopNotification.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.aps.loop.events + +import info.nightscout.androidaps.events.Event + +class EventNewOpenLoopNotification : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java index 5b5c8cb63d2..9ea2d395671 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.java @@ -16,10 +16,14 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +import javax.annotation.Nullable; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; @@ -59,6 +63,7 @@ public DetermineBasalAdapterAMAJS(ScriptReader scriptReader) { mScriptReader = scriptReader; } + @Nullable public DetermineBasalResultAMA invoke() { if (L.isEnabled(L.APS)) { @@ -190,8 +195,6 @@ public void setData(Profile profile, double autosensDataRatio, boolean tempTargetSet) throws JSONException { - String units = profile.getUnits(); - mProfile = new JSONObject(); mProfile.put("max_iob", maxIob); mProfile.put("dia", Math.min(profile.getDia(), 3d)); @@ -202,7 +205,7 @@ public void setData(Profile profile, 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("sens", profile.getIsfMgdl()); 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); @@ -216,7 +219,7 @@ public void setData(Profile profile, mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); } - if (units.equals(Constants.MMOL)) { + if (ProfileFunctions.getSystemUnits().equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); } @@ -277,7 +280,7 @@ private Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable sco private String readFile(String filename) throws IOException { byte[] bytes = mScriptReader.readFile(filename); - String string = new String(bytes, "UTF-8"); + String string = new String(bytes, StandardCharsets.UTF_8); if (string.startsWith("#!/usr/bin/env node")) { string = string.substring(20); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.java deleted file mode 100644 index 0aadf52cbd2..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.java +++ /dev/null @@ -1,134 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSAMA; - -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.squareup.otto.Subscribe; - -import org.json.JSONArray; -import org.json.JSONException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.JSONFormatter; - -public class OpenAPSAMAFragment extends SubscriberFragment implements View.OnClickListener { - private static Logger log = LoggerFactory.getLogger(L.APS); - - Button run; - TextView lastRunView; - TextView glucoseStatusView; - TextView currentTempView; - TextView iobDataView; - TextView profileView; - TextView mealDataView; - TextView autosensDataView; - TextView resultView; - TextView scriptdebugView; - TextView requestView; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.openapsama_fragment, container, false); - - run = (Button) view.findViewById(R.id.openapsma_run); - run.setOnClickListener(this); - lastRunView = (TextView) view.findViewById(R.id.openapsma_lastrun); - glucoseStatusView = (TextView) view.findViewById(R.id.openapsma_glucosestatus); - currentTempView = (TextView) view.findViewById(R.id.openapsma_currenttemp); - iobDataView = (TextView) view.findViewById(R.id.openapsma_iobdata); - profileView = (TextView) view.findViewById(R.id.openapsma_profile); - mealDataView = (TextView) view.findViewById(R.id.openapsma_mealdata); - autosensDataView = (TextView) view.findViewById(R.id.openapsma_autosensdata); - scriptdebugView = (TextView) view.findViewById(R.id.openapsma_scriptdebugdata); - resultView = (TextView) view.findViewById(R.id.openapsma_result); - requestView = (TextView) view.findViewById(R.id.openapsma_request); - - updateGUI(); - return view; - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.openapsma_run: - OpenAPSAMAPlugin.getPlugin().invoke("OpenAPSAMA button", false); - break; - } - - } - - @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(() -> { - DetermineBasalResultAMA lastAPSResult = OpenAPSAMAPlugin.getPlugin().lastAPSResult; - if (lastAPSResult != null) { - resultView.setText(JSONFormatter.format(lastAPSResult.json)); - requestView.setText(lastAPSResult.toSpanned()); - } - DetermineBasalAdapterAMAJS determineBasalAdapterAMAJS = OpenAPSAMAPlugin.getPlugin().lastDetermineBasalAdapterAMAJS; - if (determineBasalAdapterAMAJS != null) { - glucoseStatusView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getGlucoseStatusParam())); - currentTempView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getCurrentTempParam())); - try { - JSONArray iobArray = new JSONArray(determineBasalAdapterAMAJS.getIobDataParam()); - iobDataView.setText(String.format(MainApp.gs(R.string.array_of_elements), iobArray.length()) + "\n" + JSONFormatter.format(iobArray.getString(0))); - } catch (JSONException e) { - log.error("Unhandled exception", e); - iobDataView.setText("JSONException"); - } - profileView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getProfileParam())); - mealDataView.setText(JSONFormatter.format(determineBasalAdapterAMAJS.getMealDataParam())); - scriptdebugView.setText(determineBasalAdapterAMAJS.getScriptDebug()); - } - if (OpenAPSAMAPlugin.getPlugin().lastAPSRun != 0) { - lastRunView.setText(DateUtil.dateAndTimeFullString(OpenAPSAMAPlugin.getPlugin().lastAPSRun)); - } - if (OpenAPSAMAPlugin.getPlugin().lastAutosensResult != null) { - autosensDataView.setText(JSONFormatter.format(OpenAPSAMAPlugin.getPlugin().lastAutosensResult.json())); - } - }); - } - - void updateResultGUI(final String text) { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - 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/aps/openAPSAMA/OpenAPSAMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt new file mode 100644 index 00000000000..94747a45976 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt @@ -0,0 +1,115 @@ +package info.nightscout.androidaps.plugins.aps.openAPSAMA + +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.JSONFormatter +import info.nightscout.androidaps.utils.plusAssign +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.openapsama_fragment.* +import org.json.JSONArray +import org.json.JSONException +import org.slf4j.LoggerFactory + +class OpenAPSAMAFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.APS) + private var disposable: CompositeDisposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.openapsama_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + openapsma_run.setOnClickListener { + OpenAPSAMAPlugin.getPlugin().invoke("OpenAPSAMA button", false) + } + } + + @Synchronized + override fun onResume() { + super.onResume() + + disposable += RxBus + .toObservable(EventOpenAPSUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateGUI() + }, { + FabricPrivacy.logException(it) + }) + disposable += RxBus + .toObservable(EventOpenAPSUpdateResultGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateResultGUI(it.text) + }, { + FabricPrivacy.logException(it) + }) + + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + private fun updateGUI() { + if (openapsma_result == null) return + OpenAPSAMAPlugin.getPlugin().lastAPSResult?.let { lastAPSResult -> + openapsma_result.text = JSONFormatter.format(lastAPSResult.json) + openapsma_request.text = lastAPSResult.toSpanned() + } + OpenAPSAMAPlugin.getPlugin().lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS -> + openapsma_glucosestatus.text = JSONFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam) + openapsma_currenttemp.text = JSONFormatter.format(determineBasalAdapterAMAJS.currentTempParam) + try { + val iobArray = JSONArray(determineBasalAdapterAMAJS.iobDataParam) + openapsma_iobdata.text = TextUtils.concat(String.format(MainApp.gs(R.string.array_of_elements), iobArray.length()) + "\n", JSONFormatter.format(iobArray.getString(0))) + } catch (e: JSONException) { + log.error("Unhandled exception", e) + openapsma_iobdata.text = "JSONException see log for details" + } + + openapsma_profile.text = JSONFormatter.format(determineBasalAdapterAMAJS.profileParam) + openapsma_mealdata.text = JSONFormatter.format(determineBasalAdapterAMAJS.mealDataParam) + openapsma_scriptdebugdata.text = determineBasalAdapterAMAJS.scriptDebug + } + if (OpenAPSAMAPlugin.getPlugin().lastAPSRun != 0L) { + openapsma_lastrun.text = DateUtil.dateAndTimeFullString(OpenAPSAMAPlugin.getPlugin().lastAPSRun) + } + OpenAPSAMAPlugin.getPlugin().lastAutosensResult?.let { + openapsma_autosensdata.text = JSONFormatter.format(it.json()) + } + } + + private fun updateResultGUI(text: String) { + openapsma_result.text = text + openapsma_glucosestatus.text = "" + openapsma_currenttemp.text = "" + openapsma_iobdata.text = "" + openapsma_profile.text = "" + openapsma_mealdata.text = "" + openapsma_autosensdata.text = "" + openapsma_scriptdebugdata.text = "" + openapsma_request.text = "" + openapsma_lastrun.text = "" + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java index 1f7deebea42..49881c51586 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.java @@ -6,7 +6,6 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; @@ -17,17 +16,20 @@ import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.aps.loop.APSResult; +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.HardLimits; import info.nightscout.androidaps.utils.Profiler; import info.nightscout.androidaps.utils.Round; @@ -99,32 +101,37 @@ public void invoke(String initiator, boolean tempBasalFallback) { PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.noprofileselected)); return; } + if (pump == null) { + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.nopumpselected))); + if (L.isEnabled(L.APS)) + log.debug(MainApp.gs(R.string.nopumpselected)); + return; + } + if (!isEnabled(PluginType.APS)) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.openapsma_disabled)); return; } if (glucoseStatus == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.openapsma_noglucosedata)); return; } - String units = profile.getUnits(); - double maxBasal = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); - double minBg = Profile.toMgdl(profile.getTargetLow(), units); - double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); - double targetBg = Profile.toMgdl(profile.getTarget(), units); + double minBg = profile.getTargetLowMgdl(); + double maxBg = profile.getTargetHighMgdl(); + double targetBg = profile.getTargetMgdl(); minBg = Round.roundTo(minBg, 0.1d); maxBg = Round.roundTo(maxBg, 0.1d); @@ -160,9 +167,9 @@ public void invoke(String initiator, boolean tempBasalFallback) { return; if (!HardLimits.checkOnlyHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), "carbratio", HardLimits.MINIC, HardLimits.MAXIC)) return; - if (!HardLimits.checkOnlyHardLimits(Profile.toMgdl(profile.getIsf(), units), "sens", HardLimits.MINISF, HardLimits.MAXISF)) + if (!HardLimits.checkOnlyHardLimits(profile.getIsfMgdl(), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!HardLimits.checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!HardLimits.checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; @@ -171,7 +178,7 @@ public void invoke(String initiator, boolean tempBasalFallback) { if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getLastAutosensDataSynchronized("OpenAPSPlugin"); if (autosensData == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openaps_noasdata))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openaps_noasdata))); return; } lastAutosensResult = autosensData.autosensResult; @@ -192,7 +199,8 @@ public void invoke(String initiator, boolean tempBasalFallback) { isTempTarget ); } catch (JSONException e) { - log.error("Unable to set data: " + e.toString()); + FabricPrivacy.logException(e); + return; } @@ -200,23 +208,31 @@ public void invoke(String initiator, boolean tempBasalFallback) { if (L.isEnabled(L.APS)) Profiler.log(log, "AMA calculation", start); // Fix bug determine basal - if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) - determineBasalResultAMA.tempBasalRequested = false; + if (determineBasalResultAMA == null) { + if (L.isEnabled(L.APS)) + log.error("SMB calculation returned null"); + lastDetermineBasalAdapterAMAJS = null; + lastAPSResult = null; + lastAPSRun = 0; + } else { + if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultAMA.tempBasalRequested = false; - determineBasalResultAMA.iob = iobArray[0]; + determineBasalResultAMA.iob = iobArray[0]; - long now = System.currentTimeMillis(); + long now = System.currentTimeMillis(); - try { - determineBasalResultAMA.json.put("timestamp", DateUtil.toISOString(now)); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } + try { + determineBasalResultAMA.json.put("timestamp", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } - lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS; - lastAPSResult = determineBasalResultAMA; - lastAPSRun = now; - MainApp.bus().post(new EventOpenAPSUpdateGui()); + lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS; + lastAPSResult = determineBasalResultAMA; + lastAPSRun = now; + } + RxBus.INSTANCE.send(new EventOpenAPSUpdateGui()); //deviceStatus.suggested = determineBasalResultAMA.json; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java index ae1f390dedd..dbc7782ab33 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java @@ -14,9 +14,13 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +import javax.annotation.Nullable; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; @@ -47,6 +51,7 @@ public class DetermineBasalAdapterMAJS { mScriptReader = scriptReader; } + @Nullable public DetermineBasalResultMA invoke() { DetermineBasalResultMA determineBasalResultMA = null; @@ -156,8 +161,6 @@ public void setData(Profile profile, GlucoseStatus glucoseStatus, MealData mealData) throws JSONException { - String units = profile.getUnits(); - mProfile = new JSONObject(); mProfile.put("max_iob", maxIob); mProfile.put("dia", Math.min(profile.getDia(), 3d)); @@ -168,11 +171,11 @@ public void setData(Profile profile, 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("sens", profile.getIsfMgdl()); mProfile.put("current_basal", basalRate); - if (units.equals(Constants.MMOL)) { + if (ProfileFunctions.getSystemUnits().equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); } @@ -207,7 +210,7 @@ public void setData(Profile profile, private String readFile(String filename) throws IOException { byte[] bytes = mScriptReader.readFile(filename); - String string = new String(bytes, "UTF-8"); + String string = new String(bytes, StandardCharsets.UTF_8); if (string.startsWith("#!/usr/bin/env node")) { string = string.substring(20); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/LoggerCallback.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/LoggerCallback.java index 30ce388f5db..a2596d91740 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/LoggerCallback.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/LoggerCallback.java @@ -36,16 +36,14 @@ public void jsConstructor() { public void jsFunction_log(Object obj1) { if (L.isEnabled(L.APS)) - log.debug(obj1.toString()); + log.debug(obj1.toString().trim()); logBuffer.append(obj1.toString()); - logBuffer.append(' '); } public void jsFunction_error(Object obj1) { if (L.isEnabled(L.APS)) - log.error(obj1.toString()); + log.error(obj1.toString().trim()); errorBuffer.append(obj1.toString()); - errorBuffer.append(' '); } @@ -60,4 +58,4 @@ public static String getScriptDebug() { } return ret; } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.java deleted file mode 100644 index 5d632a44fc5..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.java +++ /dev/null @@ -1,116 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSMA; - -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.squareup.otto.Subscribe; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.JSONFormatter; - -public class OpenAPSMAFragment extends SubscriberFragment implements View.OnClickListener { - Button run; - TextView lastRunView; - TextView glucoseStatusView; - TextView currentTempView; - TextView iobDataView; - TextView profileView; - TextView mealDataView; - TextView resultView; - TextView requestView; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.openapsma_fragment, container, false); - - run = (Button) view.findViewById(R.id.openapsma_run); - run.setOnClickListener(this); - lastRunView = (TextView) view.findViewById(R.id.openapsma_lastrun); - glucoseStatusView = (TextView) view.findViewById(R.id.openapsma_glucosestatus); - currentTempView = (TextView) view.findViewById(R.id.openapsma_currenttemp); - iobDataView = (TextView) view.findViewById(R.id.openapsma_iobdata); - profileView = (TextView) view.findViewById(R.id.openapsma_profile); - mealDataView = (TextView) view.findViewById(R.id.openapsma_mealdata); - resultView = (TextView) view.findViewById(R.id.openapsma_result); - requestView = (TextView) view.findViewById(R.id.openapsma_request); - - updateGUI(); - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } - - return null; - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.openapsma_run: - OpenAPSMAPlugin.getPlugin().invoke("OpenAPSMA button", false); - break; - } - - } - - @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(() -> { - DetermineBasalResultMA lastAPSResult = OpenAPSMAPlugin.getPlugin().lastAPSResult; - if (lastAPSResult != null) { - resultView.setText(JSONFormatter.format(lastAPSResult.json)); - requestView.setText(lastAPSResult.toSpanned()); - } - DetermineBasalAdapterMAJS determineBasalAdapterMAJS = OpenAPSMAPlugin.getPlugin().lastDetermineBasalAdapterMAJS; - if (determineBasalAdapterMAJS != null) { - glucoseStatusView.setText(JSONFormatter.format(determineBasalAdapterMAJS.getGlucoseStatusParam())); - currentTempView.setText(JSONFormatter.format(determineBasalAdapterMAJS.getCurrentTempParam())); - iobDataView.setText(JSONFormatter.format(determineBasalAdapterMAJS.getIobDataParam())); - profileView.setText(JSONFormatter.format(determineBasalAdapterMAJS.getProfileParam())); - mealDataView.setText(JSONFormatter.format(determineBasalAdapterMAJS.getMealDataParam())); - } - if (OpenAPSMAPlugin.getPlugin().lastAPSRun != 0) { - lastRunView.setText(DateUtil.dateAndTimeFullString(OpenAPSMAPlugin.getPlugin().lastAPSRun)); - } - }); - } - - private void updateResultGUI(final String text) { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - resultView.setText(text); - glucoseStatusView.setText(""); - currentTempView.setText(""); - iobDataView.setText(""); - profileView.setText(""); - mealDataView.setText(""); - requestView.setText(""); - lastRunView.setText(""); - }); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.kt new file mode 100644 index 00000000000..1b72f573651 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAFragment.kt @@ -0,0 +1,100 @@ +package info.nightscout.androidaps.plugins.aps.openAPSMA + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.JSONFormatter +import info.nightscout.androidaps.utils.plusAssign +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.openapsama_fragment.* +import org.slf4j.LoggerFactory + +class OpenAPSMAFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.APS) + private var disposable: CompositeDisposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.openapsma_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + openapsma_run.setOnClickListener { + OpenAPSMAPlugin.getPlugin().invoke("OpenAPSMA button", false) + } + + } + + @Synchronized + override fun onResume() { + super.onResume() + + disposable += RxBus + .toObservable(EventOpenAPSUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateGUI() + }, { + FabricPrivacy.logException(it) + }) + disposable += RxBus + .toObservable(EventOpenAPSUpdateResultGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateResultGUI(it.text) + }, { + FabricPrivacy.logException(it) + }) + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + private fun updateGUI() { + if (openapsma_result == null) return + OpenAPSMAPlugin.getPlugin().lastAPSResult?.let { lastAPSResult -> + openapsma_result.text = JSONFormatter.format(lastAPSResult.json) + openapsma_request.text = lastAPSResult.toSpanned() + } + OpenAPSMAPlugin.getPlugin().lastDetermineBasalAdapterMAJS?.let { determineBasalAdapterMAJS -> + openapsma_glucosestatus.text = JSONFormatter.format(determineBasalAdapterMAJS.glucoseStatusParam) + openapsma_currenttemp.text = JSONFormatter.format(determineBasalAdapterMAJS.currentTempParam) + openapsma_iobdata.text = JSONFormatter.format(determineBasalAdapterMAJS.iobDataParam) + openapsma_profile.text = JSONFormatter.format(determineBasalAdapterMAJS.profileParam) + openapsma_mealdata.text = JSONFormatter.format(determineBasalAdapterMAJS.mealDataParam) + } + if (OpenAPSMAPlugin.getPlugin().lastAPSRun != 0L) { + openapsma_lastrun.text = DateUtil.dateAndTimeString(OpenAPSMAPlugin.getPlugin().lastAPSRun) + } + } + + @Synchronized + private fun updateResultGUI(text: String) { + if (openapsma_result == null) return + openapsma_result.text = text + openapsma_glucosestatus.text = "" + openapsma_currenttemp.text = "" + openapsma_iobdata.text = "" + openapsma_profile.text = "" + openapsma_mealdata.text = "" + openapsma_request.text = "" + openapsma_lastrun.text = "" + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java index 2dd78bdb825..0380156c74d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/OpenAPSMAPlugin.java @@ -6,7 +6,6 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; @@ -17,14 +16,17 @@ import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.aps.loop.APSResult; import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.HardLimits; import info.nightscout.androidaps.utils.Profiler; import info.nightscout.androidaps.utils.Round; @@ -98,33 +100,38 @@ public void invoke(String initiator, boolean tempBasalFallback) { PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.noprofileselected)); return; } + if (pump == null) { + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.nopumpselected))); + if (L.isEnabled(L.APS)) + log.debug(MainApp.gs(R.string.nopumpselected)); + return; + } + if (!isEnabled(PluginType.APS)) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.openapsma_disabled)); return; } if (glucoseStatus == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.openapsma_noglucosedata)); return; } - String units = profile.getUnits(); - double maxBasal = MainApp.getConstraintChecker().getMaxBasalAllowed(profile).value(); - double minBg = Profile.toMgdl(profile.getTargetLow(), units); - double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); - double targetBg = Profile.toMgdl(profile.getTarget(), units); + double minBg = profile.getTargetLowMgdl(); + double maxBg = profile.getTargetHighMgdl(); + double targetBg = profile.getTargetMgdl(); minBg = Round.roundTo(minBg, 0.1d); maxBg = Round.roundTo(maxBg, 0.1d); @@ -158,9 +165,9 @@ public void invoke(String initiator, boolean tempBasalFallback) { 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)) + if (!checkOnlyHardLimits(profile.getIsfMgdl(), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; @@ -169,7 +176,8 @@ public void invoke(String initiator, boolean tempBasalFallback) { try { determineBasalAdapterMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getPlugin().getActivePump().getBaseBasalRate(), iobTotal, glucoseStatus, mealData); } catch (JSONException e) { - log.error("Unhandled exception", e); + FabricPrivacy.logException(e); + return; } if (L.isEnabled(L.APS)) Profiler.log(log, "MA calculation", start); @@ -178,22 +186,30 @@ public void invoke(String initiator, boolean tempBasalFallback) { long now = System.currentTimeMillis(); DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke(); - // Fix bug determinef basal - if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) - determineBasalResultMA.tempBasalRequested = false; - - determineBasalResultMA.iob = iobTotal; - - try { - determineBasalResultMA.json.put("timestamp", DateUtil.toISOString(now)); - } catch (JSONException e) { - log.error("Unhandled exception", e); + if (determineBasalResultMA == null) { + if (L.isEnabled(L.APS)) + log.error("MA calculation returned null"); + lastDetermineBasalAdapterMAJS = null; + lastAPSResult = null; + lastAPSRun = 0; + } else { + // Fix bug determinef basal + if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultMA.tempBasalRequested = false; + + determineBasalResultMA.iob = iobTotal; + + try { + determineBasalResultMA.json.put("timestamp", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + + lastDetermineBasalAdapterMAJS = determineBasalAdapterMAJS; + lastAPSResult = determineBasalResultMA; + lastAPSRun = now; } - - lastDetermineBasalAdapterMAJS = determineBasalAdapterMAJS; - lastAPSResult = determineBasalResultMA; - lastAPSRun = now; - MainApp.bus().post(new EventOpenAPSUpdateGui()); + RxBus.INSTANCE.send(new EventOpenAPSUpdateGui()); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.java deleted file mode 100644 index de4292025df..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSMA.events; - -import info.nightscout.androidaps.events.EventUpdateGui; - -/** - * Created by mike on 05.08.2016. - */ -public class EventOpenAPSUpdateGui extends EventUpdateGui { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.kt new file mode 100644 index 00000000000..2b642c68807 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateGui.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.aps.openAPSMA.events + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventOpenAPSUpdateGui : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.java deleted file mode 100644 index fb5ea7e78f7..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.openAPSMA.events; - -import info.nightscout.androidaps.events.EventUpdateGui; - -/** - * Created by mike on 05.08.2016. - */ -public class EventOpenAPSUpdateResultGui extends EventUpdateGui { - public String text; - - public EventOpenAPSUpdateResultGui(String text) { - this.text = text; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.kt new file mode 100644 index 00000000000..4ba02b87559 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/events/EventOpenAPSUpdateResultGui.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.aps.openAPSMA.events + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventOpenAPSUpdateResultGui(val text: String) : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java index bfb75503c19..76571176d30 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java @@ -16,10 +16,14 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + +import javax.annotation.Nullable; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; @@ -68,6 +72,7 @@ public class DetermineBasalAdapterSMBJS { } + @Nullable public DetermineBasalResultSMB invoke() { @@ -215,8 +220,6 @@ public void setData(Profile profile, boolean advancedFiltering ) throws JSONException { - String units = profile.getUnits(); - mProfile = new JSONObject(); mProfile.put("max_iob", maxIob); @@ -228,7 +231,7 @@ public void setData(Profile profile, 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("sens", profile.getIsfMgdl()); 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)); @@ -269,7 +272,7 @@ public void setData(Profile profile, 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)) { + if (ProfileFunctions.getSystemUnits().equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); } @@ -340,7 +343,7 @@ private Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable sco private String readFile(String filename) throws IOException { byte[] bytes = mScriptReader.readFile(filename); - String string = new String(bytes, "UTF-8"); + String string = new String(bytes, StandardCharsets.UTF_8); if (string.startsWith("#!/usr/bin/env node")) { string = string.substring(20); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.java deleted file mode 100644 index 18828e840e1..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.java +++ /dev/null @@ -1,157 +0,0 @@ -package info.nightscout.androidaps.plugins.aps.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.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.logging.L; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.JSONFormatter; - -public class OpenAPSSMBFragment extends SubscriberFragment { - private static Logger log = LoggerFactory.getLogger(L.APS); - - @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); - } - - @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(() -> { - synchronized (OpenAPSSMBFragment.this) { - if (!isBound()) return; - 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.gs(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 != 0) { - lastRunView.setText(DateUtil.dateAndTimeFullString(plugin.lastAPSRun)); - } - 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(() -> { - synchronized (OpenAPSSMBFragment.this) { - if (isBound()) { - resultView.setText(text); - glucoseStatusView.setText(""); - currentTempView.setText(""); - iobDataView.setText(""); - profileView.setText(""); - mealDataView.setText(""); - autosensDataView.setText(""); - scriptdebugView.setText(""); - requestView.setText(""); - lastRunView.setText(""); - } - } - }); - } - - private boolean isBound() { - return run != null - && lastRunView != null - && constraintsView != null - && glucoseStatusView != null - && currentTempView != null - && iobDataView != null - && profileView != null - && mealDataView != null - && autosensDataView != null - && resultView != null - && scriptdebugView != null - && requestView != null; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt new file mode 100644 index 00000000000..06ad0d01fd0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt @@ -0,0 +1,122 @@ +package info.nightscout.androidaps.plugins.aps.openAPSSMB + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.JSONFormatter +import info.nightscout.androidaps.utils.plusAssign +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.openapsama_fragment.* +import org.json.JSONArray +import org.json.JSONException +import org.slf4j.LoggerFactory + +class OpenAPSSMBFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.APS) + private var disposable: CompositeDisposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.openapsama_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + openapsma_run.setOnClickListener { + OpenAPSSMBPlugin.getPlugin().invoke("OpenAPSSMB button", false) + } + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable += RxBus + .toObservable(EventOpenAPSUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateGUI() + }, { + FabricPrivacy.logException(it) + }) + disposable += RxBus + .toObservable(EventOpenAPSUpdateResultGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + updateResultGUI(it.text) + }, { + FabricPrivacy.logException(it) + }) + + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + fun updateGUI() { + if (openapsma_result == null) return + val plugin = OpenAPSSMBPlugin.getPlugin() + plugin.lastAPSResult?.let { lastAPSResult -> + openapsma_result.text = JSONFormatter.format(lastAPSResult.json) + openapsma_request.text = lastAPSResult.toSpanned() + } + plugin.lastDetermineBasalAdapterSMBJS?.let { determineBasalAdapterSMBJS -> + openapsma_glucosestatus.text = JSONFormatter.format(determineBasalAdapterSMBJS.glucoseStatusParam) + openapsma_currenttemp.text = JSONFormatter.format(determineBasalAdapterSMBJS.currentTempParam) + try { + val iobArray = JSONArray(determineBasalAdapterSMBJS.iobDataParam) + openapsma_iobdata.text = TextUtils.concat(String.format(MainApp.gs(R.string.array_of_elements), iobArray.length()) + "\n", JSONFormatter.format(iobArray.getString(0))) + } catch (e: JSONException) { + log.error("Unhandled exception", e) + @SuppressLint("SetTextl18n") + openapsma_iobdata.text = "JSONException see log for details" + } + + openapsma_profile.text = JSONFormatter.format(determineBasalAdapterSMBJS.profileParam) + openapsma_mealdata.text = JSONFormatter.format(determineBasalAdapterSMBJS.mealDataParam) + openapsma_scriptdebugdata.text = determineBasalAdapterSMBJS.scriptDebug + plugin.lastAPSResult?.inputConstraints?.let { + openapsma_constraints.text = it.reasons + } + } + if (plugin.lastAPSRun != 0L) { + openapsma_lastrun.text = DateUtil.dateAndTimeFullString(plugin.lastAPSRun) + } + plugin.lastAutosensResult?.let { + openapsma_autosensdata.text = JSONFormatter.format(it.json()) + } + } + + @Synchronized + private fun updateResultGUI(text: String) { + if (openapsma_result == null) return + openapsma_result.text = text + openapsma_glucosestatus.text = "" + openapsma_currenttemp.text = "" + openapsma_iobdata.text = "" + openapsma_profile.text = "" + openapsma_mealdata.text = "" + openapsma_autosensdata.text = "" + openapsma_scriptdebugdata.text = "" + openapsma_request.text = "" + openapsma_lastrun.text = "" + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java index 728e3145d61..426c04090b6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.java @@ -6,7 +6,6 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; @@ -19,18 +18,21 @@ import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.aps.loop.APSResult; +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.ScriptReader; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateGui; -import info.nightscout.androidaps.plugins.aps.openAPSMA.events.EventOpenAPSUpdateResultGui; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.HardLimits; import info.nightscout.androidaps.utils.Profiler; import info.nightscout.androidaps.utils.Round; @@ -103,47 +105,48 @@ public void invoke(String initiator, boolean tempBasalFallback) { PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.noprofileselected))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.noprofileselected)); return; } + if (pump == null) { + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.nopumpselected))); + if (L.isEnabled(L.APS)) + log.debug(MainApp.gs(R.string.nopumpselected)); + return; + } + if (!isEnabled(PluginType.APS)) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_disabled))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(R.string.openapsma_disabled)); return; } if (glucoseStatus == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openapsma_noglucosedata))); if (L.isEnabled(L.APS)) log.debug(MainApp.gs(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); + double minBg = profile.getTargetLowMgdl(); + double maxBg = profile.getTargetHighMgdl(); + double targetBg = profile.getTargetMgdl(); minBg = Round.roundTo(minBg, 0.1d); maxBg = Round.roundTo(maxBg, 0.1d); long start = System.currentTimeMillis(); long startPart = System.currentTimeMillis(); - IobTotal[] iobArray = IobCobCalculatorPlugin.getPlugin().calculateIobArrayForSMB(profile); - if (L.isEnabled(L.APS)) - Profiler.log(log, "calculateIobArrayInDia()", startPart); - startPart = System.currentTimeMillis(); MealData mealData = TreatmentsPlugin.getPlugin().getMealData(); if (L.isEnabled(L.APS)) Profiler.log(log, "getMealData()", startPart); @@ -170,9 +173,9 @@ public void invoke(String initiator, boolean tempBasalFallback) { 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)) + if (!checkOnlyHardLimits(profile.getIsfMgdl(), "sens", HardLimits.MINISF, HardLimits.MAXISF)) return; - if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.05, HardLimits.maxBasal())) + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.02, HardLimits.maxBasal())) return; if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, HardLimits.maxBasal())) return; @@ -181,7 +184,7 @@ public void invoke(String initiator, boolean tempBasalFallback) { if (MainApp.getConstraintChecker().isAutosensModeEnabled().value()) { AutosensData autosensData = IobCobCalculatorPlugin.getPlugin().getLastAutosensDataSynchronized("OpenAPSPlugin"); if (autosensData == null) { - MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openaps_noasdata))); + RxBus.INSTANCE.send(new EventOpenAPSUpdateResultGui(MainApp.gs(R.string.openaps_noasdata))); return; } lastAutosensResult = autosensData.autosensResult; @@ -190,6 +193,11 @@ public void invoke(String initiator, boolean tempBasalFallback) { lastAutosensResult.sensResult = "autosens disabled"; } + IobTotal[] iobArray = IobCobCalculatorPlugin.getPlugin().calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); + if (L.isEnabled(L.APS)) + Profiler.log(log, "calculateIobArrayInDia()", startPart); + + startPart = System.currentTimeMillis(); Constraint smbAllowed = new Constraint<>(!tempBasalFallback); MainApp.getConstraintChecker().isSMBModeEnabled(smbAllowed); inputConstraints.copyReasons(smbAllowed); @@ -217,7 +225,7 @@ public void invoke(String initiator, boolean tempBasalFallback) { advancedFiltering.value() ); } catch (JSONException e) { - log.error(e.getMessage()); + FabricPrivacy.logException(e); return; } @@ -226,25 +234,33 @@ public void invoke(String initiator, boolean tempBasalFallback) { DetermineBasalResultSMB determineBasalResultSMB = determineBasalAdapterSMBJS.invoke(); if (L.isEnabled(L.APS)) 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; + if (determineBasalResultSMB == null) { + if (L.isEnabled(L.APS)) + log.error("SMB calculation returned null"); + lastDetermineBasalAdapterSMBJS = null; + lastAPSResult = null; + lastAPSRun = 0; + } else { + // TODO still needed with oref1? + // Fix bug determine basal + if (determineBasalResultSMB.rate == 0d && determineBasalResultSMB.duration == 0 && !TreatmentsPlugin.getPlugin().isTempBasalInProgress()) + determineBasalResultSMB.tempBasalRequested = false; - determineBasalResultSMB.iob = iobArray[0]; + determineBasalResultSMB.iob = iobArray[0]; - try { - determineBasalResultSMB.json.put("timestamp", DateUtil.toISOString(now)); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } + try { + determineBasalResultSMB.json.put("timestamp", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } - determineBasalResultSMB.inputConstraints = inputConstraints; + determineBasalResultSMB.inputConstraints = inputConstraints; - lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; - lastAPSResult = determineBasalResultSMB; - lastAPSRun = now; - MainApp.bus().post(new EventOpenAPSUpdateGui()); + lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; + lastAPSResult = determineBasalResultSMB; + lastAPSRun = now; + } + RxBus.INSTANCE.send(new EventOpenAPSUpdateGui()); //deviceStatus.suggested = determineBasalResultAMA.json; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/bus/RxBus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/bus/RxBus.kt new file mode 100644 index 00000000000..1774df14710 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/bus/RxBus.kt @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.plugins.bus + +import info.nightscout.androidaps.events.Event +import io.reactivex.Observable +import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.PublishSubject + +// Use object so we have a singleton instance +object RxBus { + + private val publisher = PublishSubject.create() + + fun send(event: Event) { + publisher.onNext(event) + } + + // Listen should return an Observable and not the publisher + // Using ofType we filter only events that match that class type + fun toObservable(eventType: Class): Observable = + publisher + .subscribeOn(Schedulers.io()) + .ofType(eventType) +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.java b/app/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.java new file mode 100644 index 00000000000..0f61dd3320a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/common/ManufacturerType.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.common; + +public enum ManufacturerType { + + AndroidAPS("AndroidAPS"), + Medtronic("Medtronic"), + Sooil("SOOIL"), + + Tandem("Tandem"), + Insulet("Insulet"), + Animas("Animas"), Cellnovo("Cellnovo"), Roche("Roche"); + + + + private String description; + + ManufacturerType(String description) { + + this.description = description; + } + + public String getDescription() { + return description; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java deleted file mode 100644 index 3ac877b42d3..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/common/SubscriberFragment.java +++ /dev/null @@ -1,32 +0,0 @@ -package info.nightscout.androidaps.plugins.common; - -import android.support.v4.app.Fragment; - -import butterknife.Unbinder; -import info.nightscout.androidaps.MainApp; - -abstract public class SubscriberFragment extends Fragment { - protected Unbinder unbinder; - - @Override - public void onPause() { - super.onPause(); - MainApp.bus().unregister(this); - } - - @Override - public void onResume() { - super.onResume(); - MainApp.bus().register(this); - updateGUI(); - } - - @Override public synchronized void onDestroyView() { - super.onDestroyView(); - if (unbinder != null) - unbinder.unbind(); - } - - - protected abstract void updateGUI(); -} 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 deleted file mode 100644 index 47b5d493574..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.java +++ /dev/null @@ -1,283 +0,0 @@ -package info.nightscout.androidaps.plugins.configBuilder; - - -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.ScrollView; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -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.activities.PreferencesActivity; -import info.nightscout.androidaps.events.EventConfigBuilderChange; -import info.nightscout.androidaps.events.EventRefreshGui; -import info.nightscout.androidaps.interfaces.APSInterface; -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.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin; -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.PasswordProtection; - - -public class ConfigBuilderFragment extends SubscriberFragment { - - private List pluginViewHolders = new ArrayList<>(); - - @BindView(R.id.categories) - LinearLayout categories; - - @BindView(R.id.main_layout) - ScrollView mainLayout; - @BindView(R.id.unlock) - Button unlock; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.configbuilder_fragment, container, false); - unbinder = ButterKnife.bind(this, view); - - if (PasswordProtection.isLocked("settings_password")) - mainLayout.setVisibility(View.GONE); - else unlock.setVisibility(View.GONE); - - createViews(); - - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } - - return null; - } - - @OnClick(R.id.unlock) - void onClickUnlock() { - PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", () -> { - mainLayout.setVisibility(View.VISIBLE); - unlock.setVisibility(View.GONE); - }, null); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - for (PluginViewHolder pluginViewHolder : pluginViewHolders) pluginViewHolder.unbind(); - pluginViewHolders.clear(); - } - - @Override - protected void updateGUI() { - for (PluginViewHolder pluginViewHolder : pluginViewHolders) pluginViewHolder.update(); - } - - private void createViews() { - createViewsForPlugins(R.string.configbuilder_profile, R.string.configbuilder_profile_description, PluginType.PROFILE, MainApp.getSpecificPluginsVisibleInListByInterface(ProfileInterface.class, PluginType.PROFILE)); - createViewsForPlugins(R.string.configbuilder_insulin, R.string.configbuilder_insulin_description, PluginType.INSULIN, MainApp.getSpecificPluginsVisibleInListByInterface(InsulinInterface.class, PluginType.INSULIN)); - createViewsForPlugins(R.string.configbuilder_bgsource, R.string.configbuilder_bgsource_description, PluginType.BGSOURCE, MainApp.getSpecificPluginsVisibleInListByInterface(BgSourceInterface.class, PluginType.BGSOURCE)); - createViewsForPlugins(R.string.configbuilder_pump, R.string.configbuilder_pump_description, PluginType.PUMP, MainApp.getSpecificPluginsVisibleInList(PluginType.PUMP)); - createViewsForPlugins(R.string.configbuilder_sensitivity, R.string.configbuilder_sensitivity_description, PluginType.SENSITIVITY, MainApp.getSpecificPluginsVisibleInListByInterface(SensitivityInterface.class, PluginType.SENSITIVITY)); - createViewsForPlugins(R.string.configbuilder_aps, R.string.configbuilder_aps_description, PluginType.APS, MainApp.getSpecificPluginsVisibleInList(PluginType.APS)); - createViewsForPlugins(R.string.configbuilder_loop, R.string.configbuilder_loop_description, PluginType.LOOP, MainApp.getSpecificPluginsVisibleInList(PluginType.LOOP)); - createViewsForPlugins(R.string.constraints, R.string.configbuilder_constraints_description, PluginType.CONSTRAINTS, MainApp.getSpecificPluginsVisibleInListByInterface(ConstraintsInterface.class, PluginType.CONSTRAINTS)); - createViewsForPlugins(R.string.configbuilder_treatments, R.string.configbuilder_treatments_description, PluginType.TREATMENT, MainApp.getSpecificPluginsVisibleInList(PluginType.TREATMENT)); - createViewsForPlugins(R.string.configbuilder_general, R.string.configbuilder_general_description, PluginType.GENERAL, MainApp.getSpecificPluginsVisibleInList(PluginType.GENERAL)); - } - - private void createViewsForPlugins(@StringRes int title, @StringRes int description, PluginType pluginType, List plugins) { - if (plugins.size() == 0) return; - LinearLayout parent = (LinearLayout) getLayoutInflater().inflate(R.layout.configbuilder_single_category, null); - ((TextView) parent.findViewById(R.id.category_title)).setText(MainApp.gs(title)); - ((TextView) parent.findViewById(R.id.category_description)).setText(MainApp.gs(description)); - LinearLayout pluginContainer = parent.findViewById(R.id.category_plugins); - for (PluginBase plugin: plugins) { - PluginViewHolder pluginViewHolder = new PluginViewHolder(pluginType, plugin); - pluginContainer.addView(pluginViewHolder.getBaseView()); - pluginViewHolders.add(pluginViewHolder); - } - categories.addView(parent); - } - - private boolean areMultipleSelectionsAllowed(PluginType type) { - return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS ||type == PluginType.LOOP; - } - - public static void processOnEnabledCategoryChanged(PluginBase changedPlugin, PluginType type) { - ArrayList pluginsInCategory = null; - switch (type) { - // Multiple selection allowed - case GENERAL: - case CONSTRAINTS: - case LOOP: - break; - // Single selection allowed - case INSULIN: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(InsulinInterface.class); - break; - case SENSITIVITY: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(SensitivityInterface.class); - break; - case APS: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(APSInterface.class); - break; - case PROFILE: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(ProfileInterface.class); - break; - case BGSOURCE: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(BgSourceInterface.class); - break; - case TREATMENT: - case PUMP: - pluginsInCategory = MainApp.getSpecificPluginsListByInterface(PumpInterface.class); - break; - } - if (pluginsInCategory != null) { - boolean newSelection = changedPlugin.isEnabled(type); - if (newSelection) { // new plugin selected -> disable others - for (PluginBase p : pluginsInCategory) { - if (p.getName().equals(changedPlugin.getName())) { - // this is new selected - } else { - p.setPluginEnabled(type, false); - p.setFragmentVisible(type, false); - } - } - } else { // enable first plugin in list - 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).setPluginEnabled(type, true); - } - } - } - - public class PluginViewHolder { - - private Unbinder unbinder; - private PluginType pluginType; - private PluginBase plugin; - - LinearLayout baseView; - @BindView(R.id.plugin_enabled_exclusive) - RadioButton enabledExclusive; - @BindView(R.id.plugin_enabled_inclusive) - CheckBox enabledInclusive; - @BindView(R.id.plugin_name) - TextView pluginName; - @BindView(R.id.plugin_description) - TextView pluginDescription; - @BindView(R.id.plugin_preferences) - ImageButton pluginPreferences; - @BindView(R.id.plugin_visibility) - CheckBox pluginVisibility; - - public PluginViewHolder(PluginType pluginType, PluginBase plugin) { - this.pluginType = pluginType; - this.plugin = plugin; - baseView = (LinearLayout) getLayoutInflater().inflate(R.layout.configbuilder_single_plugin, null); - unbinder = ButterKnife.bind(this, baseView); - update(); - } - - public LinearLayout getBaseView() { - return baseView; - } - - public void update() { - enabledExclusive.setVisibility(areMultipleSelectionsAllowed(pluginType) ? View.GONE : View.VISIBLE); - enabledInclusive.setVisibility(areMultipleSelectionsAllowed(pluginType) ? View.VISIBLE : View.GONE); - enabledExclusive.setChecked(plugin.isEnabled(pluginType)); - enabledInclusive.setChecked(plugin.isEnabled(pluginType)); - enabledInclusive.setEnabled(!plugin.pluginDescription.alwaysEnabled); - enabledExclusive.setEnabled(!plugin.pluginDescription.alwaysEnabled); - pluginName.setText(plugin.getName()); - if (plugin.getDescription() == null) pluginDescription.setVisibility(View.GONE); - else { - pluginDescription.setVisibility(View.VISIBLE); - pluginDescription.setText(plugin.getDescription()); - } - pluginPreferences.setVisibility(plugin.getPreferencesId() == -1 || !plugin.isEnabled(pluginType) ? View.INVISIBLE : View.VISIBLE); - pluginVisibility.setVisibility(plugin.hasFragment() ? View.VISIBLE : View.INVISIBLE); - pluginVisibility.setEnabled(!(plugin.pluginDescription.neverVisible || plugin.pluginDescription.alwayVisible) && plugin.isEnabled(pluginType)); - pluginVisibility.setChecked(plugin.isFragmentVisible()); - } - - @OnClick(R.id.plugin_visibility) - void onVisibilityChanged() { - plugin.setFragmentVisible(pluginType, pluginVisibility.isChecked()); - ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxVisible"); - MainApp.bus().post(new EventRefreshGui()); - ConfigBuilderPlugin.getPlugin().logPluginStatus(); - } - - @OnClick({R.id.plugin_enabled_exclusive, R.id.plugin_enabled_inclusive}) - void onEnabledChanged() { - plugin.switchAllowed(new PluginSwitcher(), getActivity()); - } - - @OnClick(R.id.plugin_preferences) - void onPluginPreferencesClicked() { - PasswordProtection.QueryPassword(getContext(), R.string.settings_password, "settings_password", () -> { - Intent i = new Intent(getContext(), PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - startActivity(i); - }, null); - } - - public void unbind() { - unbinder.unbind(); - } - - public class PluginSwitcher { - public void invoke() { - boolean enabled = enabledExclusive.getVisibility() == View.VISIBLE ? enabledExclusive.isChecked() : enabledInclusive.isChecked(); - plugin.setPluginEnabled(pluginType, enabled); - plugin.setFragmentVisible(pluginType, enabled); - processOnEnabledCategoryChanged(plugin, pluginType); - updateGUI(); - ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxEnabled"); - MainApp.bus().post(new EventRefreshGui()); - MainApp.bus().post(new EventConfigBuilderChange()); - ConfigBuilderPlugin.getPlugin().logPluginStatus(); - } - - public void cancel(){ - updateGUI(); - } - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt new file mode 100644 index 00000000000..059bec14441 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderFragment.kt @@ -0,0 +1,98 @@ +package info.nightscout.androidaps.plugins.configBuilder + + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.PasswordProtection +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.configbuilder_fragment.* +import java.util.* + +class ConfigBuilderFragment : Fragment() { + + private var disposable: CompositeDisposable = CompositeDisposable() + private val pluginViewHolders = ArrayList() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.configbuilder_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (PasswordProtection.isLocked("settings_password")) + configbuilder_main_layout.visibility = View.GONE + else + unlock.visibility = View.GONE + + unlock.setOnClickListener { + PasswordProtection.QueryPassword(context, R.string.settings_password, "settings_password", { + configbuilder_main_layout.visibility = View.VISIBLE + unlock.visibility = View.GONE + }, null) + } + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(RxBus + .toObservable(EventConfigBuilderUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + for (pluginViewHolder in pluginViewHolders) pluginViewHolder.update() + }, { + FabricPrivacy.logException(it) + })) + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + private fun updateGUI() { + configbuilder_categories.removeAllViews() + createViewsForPlugins(R.string.configbuilder_profile, R.string.configbuilder_profile_description, PluginType.PROFILE, MainApp.getSpecificPluginsVisibleInListByInterface(ProfileInterface::class.java, PluginType.PROFILE)) + createViewsForPlugins(R.string.configbuilder_insulin, R.string.configbuilder_insulin_description, PluginType.INSULIN, MainApp.getSpecificPluginsVisibleInListByInterface(InsulinInterface::class.java, PluginType.INSULIN)) + createViewsForPlugins(R.string.configbuilder_bgsource, R.string.configbuilder_bgsource_description, PluginType.BGSOURCE, MainApp.getSpecificPluginsVisibleInListByInterface(BgSourceInterface::class.java, PluginType.BGSOURCE)) + createViewsForPlugins(R.string.configbuilder_pump, R.string.configbuilder_pump_description, PluginType.PUMP, MainApp.getSpecificPluginsVisibleInList(PluginType.PUMP)) + createViewsForPlugins(R.string.configbuilder_sensitivity, R.string.configbuilder_sensitivity_description, PluginType.SENSITIVITY, MainApp.getSpecificPluginsVisibleInListByInterface(SensitivityInterface::class.java, PluginType.SENSITIVITY)) + createViewsForPlugins(R.string.configbuilder_aps, R.string.configbuilder_aps_description, PluginType.APS, MainApp.getSpecificPluginsVisibleInList(PluginType.APS)) + createViewsForPlugins(R.string.configbuilder_loop, R.string.configbuilder_loop_description, PluginType.LOOP, MainApp.getSpecificPluginsVisibleInList(PluginType.LOOP)) + createViewsForPlugins(R.string.constraints, R.string.configbuilder_constraints_description, PluginType.CONSTRAINTS, MainApp.getSpecificPluginsVisibleInListByInterface(ConstraintsInterface::class.java, PluginType.CONSTRAINTS)) + createViewsForPlugins(R.string.configbuilder_treatments, R.string.configbuilder_treatments_description, PluginType.TREATMENT, MainApp.getSpecificPluginsVisibleInList(PluginType.TREATMENT)) + createViewsForPlugins(R.string.configbuilder_general, R.string.configbuilder_general_description, PluginType.GENERAL, MainApp.getSpecificPluginsVisibleInList(PluginType.GENERAL)) + } + + private fun createViewsForPlugins(@StringRes title: Int, @StringRes description: Int, pluginType: PluginType, plugins: List) { + if (plugins.size == 0) return + val parent = layoutInflater.inflate(R.layout.configbuilder_single_category, null) as LinearLayout + (parent.findViewById(R.id.category_title) as TextView).text = MainApp.gs(title) + (parent.findViewById(R.id.category_description) as TextView).text = MainApp.gs(description) + val pluginContainer = parent.findViewById(R.id.category_plugins) + for (plugin in plugins) { + val pluginViewHolder = PluginViewHolder(this, pluginType, plugin) + pluginContainer.addView(pluginViewHolder.baseView) + pluginViewHolders.add(pluginViewHolder) + } + configbuilder_categories.addView(parent) + } + +} 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 3c3531da865..f43513fbf5c 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,6 +1,6 @@ package info.nightscout.androidaps.plugins.configBuilder; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +20,9 @@ import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.SensitivityInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin; +import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; import info.nightscout.androidaps.queue.CommandQueue; @@ -57,7 +59,7 @@ public ConfigBuilderPlugin() { .fragmentClass(ConfigBuilderFragment.class.getName()) .showInList(true) .alwaysEnabled(true) - .alwayVisible(false) + .alwaysVisible(false) .pluginName(R.string.configbuilder) .shortName(R.string.configbuilder_shortname) .description(R.string.description_config_builder) @@ -66,14 +68,12 @@ public ConfigBuilderPlugin() { @Override protected void onStart() { - MainApp.bus().register(this); super.onStart(); } @Override protected void onStop() { super.onStop(); - MainApp.bus().unregister(this); } @@ -82,7 +82,7 @@ public void initialize() { upgradeSettings(); loadSettings(); setAlwaysEnabledPluginsEnabled(); - MainApp.bus().post(new EventAppInitialized()); + RxBus.INSTANCE.send(new EventAppInitialized()); } private void setAlwaysEnabledPluginsEnabled() { @@ -102,7 +102,7 @@ public void storeSettings(String from) { for (PluginBase p : pluginList) { PluginType type = p.getType(); - if (p.pluginDescription.alwaysEnabled && p.pluginDescription.alwayVisible) + if (p.pluginDescription.alwaysEnabled && p.pluginDescription.alwaysVisible) continue; if (p.pluginDescription.alwaysEnabled && p.pluginDescription.neverVisible) continue; @@ -232,31 +232,37 @@ public CommandQueue getCommandQueue() { return commandQueue; } + @Nullable public BgSourceInterface getActiveBgSource() { return activeBgSource; } + @Nullable public ProfileInterface getActiveProfileInterface() { return activeProfile; } + @Nullable public InsulinInterface getActiveInsulin() { return activeInsulin; } + @Nullable public APSInterface getActiveAPS() { return activeAPS; } + @Nullable public PumpInterface getActivePump() { return activePump; } + @Nullable public SensitivityInterface getActiveSensitivity() { return activeSensitivity; } - void logPluginStatus() { + public void logPluginStatus() { if (L.isEnabled(L.CONFIGBUILDER)) for (PluginBase p : pluginList) { log.debug(p.getName() + ":" + @@ -399,4 +405,58 @@ private PluginBase getTheOneEnabledInArray(ArrayList pluginsInCatego return found; } + public void processOnEnabledCategoryChanged(PluginBase changedPlugin, PluginType type) { + ArrayList pluginsInCategory = null; + switch (type) { + // Multiple selection allowed + case GENERAL: + case CONSTRAINTS: + case LOOP: + break; + // Single selection allowed + case INSULIN: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(InsulinInterface.class); + break; + case SENSITIVITY: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(SensitivityInterface.class); + break; + case APS: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(APSInterface.class); + break; + case PROFILE: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(ProfileInterface.class); + break; + case BGSOURCE: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(BgSourceInterface.class); + break; + case TREATMENT: + case PUMP: + pluginsInCategory = MainApp.getSpecificPluginsListByInterface(PumpInterface.class); + break; + } + if (pluginsInCategory != null) { + boolean newSelection = changedPlugin.isEnabled(type); + if (newSelection) { // new plugin selected -> disable others + for (PluginBase p : pluginsInCategory) { + if (p.getName().equals(changedPlugin.getName())) { + // this is new selected + } else { + p.setPluginEnabled(type, false); + p.setFragmentVisible(type, false); + } + } + } else { // enable first plugin in list + 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).setPluginEnabled(type, true); + } + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java deleted file mode 100644 index b19948fc5ea..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/DetailedBolusInfoStorage.java +++ /dev/null @@ -1,44 +0,0 @@ -package info.nightscout.androidaps.plugins.configBuilder; - -import android.support.annotation.Nullable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.logging.L; - -/** - * Created by mike on 08.08.2017. - */ - -public class DetailedBolusInfoStorage { - private static Logger log = LoggerFactory.getLogger(L.PUMP); - private static List store = new ArrayList<>(); - - public static synchronized void add(DetailedBolusInfo detailedBolusInfo) { - log.debug("Stored bolus info: " + detailedBolusInfo); - store.add(detailedBolusInfo); - } - - @Nullable - public static synchronized DetailedBolusInfo findDetailedBolusInfo(long bolustime) { - DetailedBolusInfo found = null; - for (int i = 0; i < store.size(); i++) { - long infoTime = store.get(i).date; - if (L.isEnabled(L.PUMP)) - log.debug("Existing bolus info: " + store.get(i)); - if (bolustime > infoTime - 60 * 1000 && bolustime < infoTime + 60 * 1000) { - found = store.get(i); - if (L.isEnabled(L.PUMP)) - log.debug("Using & removing bolus info: " + store.get(i)); - store.remove(i); - break; - } - } - return found; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/EventConfigBuilderUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/EventConfigBuilderUpdateGui.kt new file mode 100644 index 00000000000..e57fd79983d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/EventConfigBuilderUpdateGui.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.configBuilder + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventConfigBuilderUpdateGui : EventUpdateGui() { +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginViewHolder.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginViewHolder.kt new file mode 100644 index 00000000000..2670e786336 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginViewHolder.kt @@ -0,0 +1,87 @@ +package info.nightscout.androidaps.plugins.configBuilder + +import android.content.Intent +import android.view.View +import android.widget.CheckBox +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.TextView +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.PreferencesActivity +import info.nightscout.androidaps.events.EventRebuildTabs +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.utils.PasswordProtection +import info.nightscout.androidaps.utils.toVisibility + +class PluginViewHolder internal constructor(private val fragment: ConfigBuilderFragment, + private val pluginType: PluginType, + private val plugin: PluginBase) { + + val baseView: LinearLayout = fragment.layoutInflater.inflate(R.layout.configbuilder_single_plugin, null) as LinearLayout + private val enabledExclusive: RadioButton + private val enabledInclusive: CheckBox + private val pluginName: TextView + private val pluginDescription: TextView + private val pluginPreferences: ImageButton + private val pluginVisibility: CheckBox + + init { + enabledExclusive = baseView.findViewById(R.id.plugin_enabled_exclusive) + enabledInclusive = baseView.findViewById(R.id.plugin_enabled_inclusive) + pluginName = baseView.findViewById(R.id.plugin_name) + pluginDescription = baseView.findViewById(R.id.plugin_description) + pluginPreferences = baseView.findViewById(R.id.plugin_preferences) + pluginVisibility = baseView.findViewById(R.id.plugin_visibility) + + pluginVisibility.setOnClickListener { + plugin.setFragmentVisible(pluginType, pluginVisibility.isChecked) + ConfigBuilderPlugin.getPlugin().storeSettings("CheckedCheckboxVisible") + RxBus.send(EventRebuildTabs()) + ConfigBuilderPlugin.getPlugin().logPluginStatus() + } + + enabledExclusive.setOnClickListener { + plugin.switchAllowed(if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, fragment.activity, pluginType) + } + enabledInclusive.setOnClickListener { + plugin.switchAllowed(if (enabledExclusive.visibility == View.VISIBLE) enabledExclusive.isChecked else enabledInclusive.isChecked, fragment.activity, pluginType) + } + + pluginPreferences.setOnClickListener { + PasswordProtection.QueryPassword(fragment.context, R.string.settings_password, "settings_password", { + val i = Intent(fragment.context, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + fragment.startActivity(i) + }, null) + } + update() + } + + fun update() { + enabledExclusive.visibility = areMultipleSelectionsAllowed(pluginType).not().toVisibility() + enabledInclusive.visibility = areMultipleSelectionsAllowed(pluginType).toVisibility() + enabledExclusive.isChecked = plugin.isEnabled(pluginType) + enabledInclusive.isChecked = plugin.isEnabled(pluginType) + enabledInclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled + enabledExclusive.isEnabled = !plugin.pluginDescription.alwaysEnabled + pluginName.text = plugin.name + if (plugin.description == null) + pluginDescription.visibility = View.GONE + else { + pluginDescription.visibility = View.VISIBLE + pluginDescription.text = plugin.description + } + pluginPreferences.visibility = if (plugin.preferencesId == -1 || !plugin.isEnabled(pluginType)) View.INVISIBLE else View.VISIBLE + pluginVisibility.visibility = plugin.hasFragment().toVisibility() + pluginVisibility.isEnabled = !(plugin.pluginDescription.neverVisible || plugin.pluginDescription.alwaysVisible) && plugin.isEnabled(pluginType) + pluginVisibility.isChecked = plugin.isFragmentVisible + } + + private fun areMultipleSelectionsAllowed(type: PluginType): Boolean { + return type == PluginType.GENERAL || type == PluginType.CONSTRAINTS || type == PluginType.LOOP + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java index 0360ba4a680..bdde6606bb8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/ProfileFunctions.java @@ -2,10 +2,10 @@ import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import com.google.firebase.analytics.FirebaseAnalytics; -import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,13 +23,19 @@ import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; +import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; public class ProfileFunctions { private static Logger log = LoggerFactory.getLogger(L.PROFILE); + private CompositeDisposable disposable = new CompositeDisposable(); private static ProfileFunctions profileFunctions = null; @@ -43,65 +49,74 @@ public static ProfileFunctions getInstance() { ProfileFunctions.getInstance(); // register to bus at start } - ProfileFunctions() { - MainApp.bus().register(this); - } - - @Subscribe - public void onProfileSwitch(EventProfileNeedsUpdate ignored) { - if (L.isEnabled(L.PROFILE)) - log.debug("onProfileSwitch"); - ConfigBuilderPlugin.getPlugin().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.gs(R.string.failedupdatebasalprofile)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); - } - if (result.enacted) - MainApp.bus().post(new EventNewBasalProfile()); - } - }); + private ProfileFunctions() { + disposable.add(RxBus.INSTANCE + .toObservable(EventProfileNeedsUpdate.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.PROFILE)) + log.debug("onProfileSwitch"); + ConfigBuilderPlugin.getPlugin().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.gs(R.string.failedupdatebasalprofile)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + if (result.enacted) + RxBus.INSTANCE.send(new EventNewBasalProfile()); + } + }); + }, FabricPrivacy::logException) + ); } public String getProfileName() { - return getProfileName(System.currentTimeMillis()); + return getProfileName(System.currentTimeMillis(), true, false); } public String getProfileName(boolean customized) { - return getProfileName(System.currentTimeMillis(), customized); + return getProfileName(System.currentTimeMillis(), customized, false); } - public String getProfileName(long time) { - return getProfileName(time, true); + public String getProfileNameWithDuration() { + return getProfileName(System.currentTimeMillis(), true, true); } - public String getProfileName(long time, boolean customized) { + public String getProfileName(long time, boolean customized, boolean showRemainingTime) { + String profileName = MainApp.gs(R.string.noprofileselected); + TreatmentsInterface activeTreatments = TreatmentsPlugin.getPlugin(); ProfileInterface activeProfile = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface(); ProfileSwitch profileSwitch = activeTreatments.getProfileSwitchFromHistory(time); if (profileSwitch != null) { if (profileSwitch.profileJson != null) { - return customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName; + profileName = customized ? profileSwitch.getCustomizedName() : profileSwitch.profileName; } else { ProfileStore profileStore = activeProfile.getProfile(); if (profileStore != null) { Profile profile = profileStore.getSpecificProfile(profileSwitch.profileName); if (profile != null) - return profileSwitch.profileName; + profileName = profileSwitch.profileName; } } + + if (showRemainingTime && profileSwitch.durationInMinutes != 0) { + profileName += DateUtil.untilString(profileSwitch.originalEnd()); + } + return profileName; } - return MainApp.gs(R.string.noprofileselected); + return profileName; } public boolean isProfileValid(String from) { - return getProfile() != null && getProfile().isValid(from); + Profile profile = getProfile(); + return profile != null && profile.isValid(from); } @Nullable @@ -109,9 +124,8 @@ public Profile getProfile() { return getProfile(System.currentTimeMillis()); } - public String getProfileUnits() { - Profile profile = getProfile(); - return profile != null ? profile.getUnits() : Constants.MGDL; + public static String getSystemUnits() { + return SP.getString(R.string.key_units, Constants.MGDL); } @Nullable @@ -156,9 +170,11 @@ public static ProfileSwitch prepareProfileSwitch(final ProfileStore profileStore return profileSwitch; } - public static void doProfileSwitch(final ProfileStore profileStore, final String profileName, final int duration, final int percentage, final int timeshift) { - ProfileSwitch profileSwitch = prepareProfileSwitch(profileStore, profileName, duration, percentage, timeshift, System.currentTimeMillis()); + public static void doProfileSwitch(final ProfileStore profileStore, final String profileName, final int duration, final int percentage, final int timeshift, final long date) { + ProfileSwitch profileSwitch = prepareProfileSwitch(profileStore, profileName, duration, percentage, timeshift, date); TreatmentsPlugin.getPlugin().addToHistoryProfileSwitch(profileSwitch); + if (percentage == 90 && duration == 10) + SP.putBoolean(R.string.key_objectiveuseprofileswitch, true); } public static void doProfileSwitch(final int duration, final int percentage, final int timeshift) { @@ -167,7 +183,7 @@ public static void doProfileSwitch(final int duration, final int percentage, fin profileSwitch = new ProfileSwitch(); profileSwitch.date = System.currentTimeMillis(); profileSwitch.source = Source.USER; - profileSwitch.profileName = getInstance().getProfileName(System.currentTimeMillis(), false); + profileSwitch.profileName = getInstance().getProfileName(System.currentTimeMillis(), false, false); profileSwitch.profileJson = getInstance().getProfile().getData().toString(); profileSwitch.profilePlugin = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface().getClass().getName(); profileSwitch.durationInMinutes = duration; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java index ec3ef448815..828fa1b86f0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPlugin.java @@ -15,13 +15,14 @@ import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; public class DstHelperPlugin extends PluginBase implements ConstraintsInterface { public static final int DISABLE_TIMEFRAME_HOURS = -3; - public static final int WARN_PRIOR_TIMEFRAME_HOURS = 24; + public static final int WARN_PRIOR_TIMEFRAME_HOURS = 12; private static Logger log = LoggerFactory.getLogger(L.CONSTRAINTS); static DstHelperPlugin plugin = null; @@ -89,6 +90,6 @@ public Constraint isLoopInvocationAllowed(Constraint value) { private void warnUser(int id, String warningText) { Notification notification = new Notification(id, warningText, Notification.LOW); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java deleted file mode 100644 index b9435710894..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.java +++ /dev/null @@ -1,229 +0,0 @@ -package info.nightscout.androidaps.plugins.constraints.objectives; - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.LinearSmoothScroller; -import android.support.v7.widget.RecyclerView; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.Date; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective; -import info.nightscout.androidaps.utils.FabricPrivacy; - -public class ObjectivesFragment extends SubscriberFragment { - RecyclerView recyclerView; - CheckBox enableFake; - TextView reset; - ObjectivesAdapter objectivesAdapter = new ObjectivesAdapter(); - Handler handler = new Handler(Looper.getMainLooper()); - - private Runnable objectiveUpdater = new Runnable() { - @Override - public void run() { - handler.postDelayed(this, 60 * 1000); - updateGUI(); - } - }; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.objectives_fragment, container, false); - - recyclerView = view.findViewById(R.id.objectives_recyclerview); - recyclerView.setLayoutManager(new LinearLayoutManager(view.getContext())); - recyclerView.setAdapter(objectivesAdapter); - enableFake = view.findViewById(R.id.objectives_fake); - reset = view.findViewById(R.id.objectives_reset); - enableFake.setOnClickListener(v -> updateGUI()); - reset.setOnClickListener(v -> { - ObjectivesPlugin.getPlugin().reset(); - ObjectivesPlugin.getPlugin().saveProgress(); - recyclerView.getAdapter().notifyDataSetChanged(); - scrollToCurrentObjective(); - }); - scrollToCurrentObjective(); - startUpdateTimer(); - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } - - return null; - } - - @Override - public synchronized void onDestroyView() { - super.onDestroyView(); - handler.removeCallbacks(objectiveUpdater); - } - - private void startUpdateTimer() { - handler.removeCallbacks(objectiveUpdater); - for (Objective objective : ObjectivesPlugin.getPlugin().getObjectives()) { - if (objective.isStarted() && !objective.isAccomplished()) { - long timeTillNextMinute = (System.currentTimeMillis() - objective.getStartedOn().getTime()) % (60 * 1000); - handler.postDelayed(objectiveUpdater, timeTillNextMinute); - break; - } - } - } - - private void scrollToCurrentObjective() { - for (int i = 0; i < ObjectivesPlugin.getPlugin().getObjectives().size(); i++) { - Objective objective = ObjectivesPlugin.getPlugin().getObjectives().get(i); - if (!objective.isStarted() || !objective.isAccomplished()) { - RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) { - @Override - protected int getVerticalSnapPreference() { - return LinearSmoothScroller.SNAP_TO_START; - } - - @Override - protected int calculateTimeForScrolling(int dx) { - return super.calculateTimeForScrolling(dx) * 4; - } - }; - smoothScroller.setTargetPosition(i); - recyclerView.getLayoutManager().startSmoothScroll(smoothScroller); - break; - } - } - } - - private class ObjectivesAdapter extends RecyclerView.Adapter { - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.objectives_item, parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Objective objective = ObjectivesPlugin.getPlugin().getObjectives().get(position); - holder.title.setText(MainApp.gs(R.string.nth_objective, position + 1)); - holder.revert.setVisibility(View.INVISIBLE); - if (objective.getObjective() != 0) { - holder.objective.setVisibility(View.VISIBLE); - holder.objective.setText(MainApp.gs(objective.getObjective())); - } else holder.objective.setVisibility(View.GONE); - if (objective.getGate() != 0) { - holder.gate.setVisibility(View.VISIBLE); - holder.gate.setText(MainApp.gs(objective.getGate())); - } else holder.gate.setVisibility(View.GONE); - if (!objective.isStarted()) { - holder.gate.setTextColor(0xFFFFFFFF); - holder.verify.setVisibility(View.GONE); - holder.progress.setVisibility(View.GONE); - if (position == 0 || ObjectivesPlugin.getPlugin().getObjectives().get(position - 1).isAccomplished()) - holder.start.setVisibility(View.VISIBLE); - else holder.start.setVisibility(View.GONE); - } else if (objective.isAccomplished()) { - holder.gate.setTextColor(0xFF4CAF50); - holder.verify.setVisibility(View.GONE); - holder.progress.setVisibility(View.GONE); - holder.start.setVisibility(View.GONE); - } else if (objective.isStarted()) { - holder.gate.setTextColor(0xFFFFFFFF); - holder.verify.setVisibility(View.VISIBLE); - holder.verify.setEnabled(objective.isCompleted() || enableFake.isChecked()); - holder.start.setVisibility(View.GONE); - if(objective.isRevertable()) { - holder.revert.setVisibility(View.VISIBLE); - } - holder.progress.setVisibility(View.VISIBLE); - holder.progress.removeAllViews(); - for (Objective.Task task : objective.getTasks()) { - if (task.shouldBeIgnored()) continue; - TextView textView = new TextView(holder.progress.getContext()); - textView.setTextColor(0xFFFFFFFF); - String basicHTML = "%2$s: %3$s"; - String formattedHTML = String.format(basicHTML, task.isCompleted() ? "#4CAF50" : "#FF9800", MainApp.gs(task.getTask()), task.getProgress()); - textView.setText(Html.fromHtml(formattedHTML)); - holder.progress.addView(textView, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); - } - } - holder.verify.setOnClickListener((view) -> { - objective.setAccomplishedOn(new Date()); - notifyDataSetChanged(); - scrollToCurrentObjective(); - startUpdateTimer(); - }); - holder.start.setOnClickListener((view) -> { - objective.setStartedOn(new Date()); - notifyDataSetChanged(); - scrollToCurrentObjective(); - startUpdateTimer(); - }); - holder.revert.setOnClickListener((view) -> { - objective.setAccomplishedOn(null); - objective.setStartedOn(null); - if (position > 0) { - Objective prevObj = ObjectivesPlugin.getPlugin().getObjectives().get(position - 1); - prevObj.setAccomplishedOn(null); - } - notifyDataSetChanged(); - scrollToCurrentObjective(); - }); - } - - - - @Override - public int getItemCount() { - return ObjectivesPlugin.getPlugin().getObjectives().size(); - } - - public class ViewHolder extends RecyclerView.ViewHolder { - - public CardView cardView; - public TextView title; - public TextView objective; - public TextView gate; - public LinearLayout progress; - public Button verify; - public Button start; - public Button revert; - - public ViewHolder(View itemView) { - super(itemView); - cardView = (CardView) itemView; - title = itemView.findViewById(R.id.objective_title); - objective = itemView.findViewById(R.id.objective_objective); - gate = itemView.findViewById(R.id.objective_gate); - progress = itemView.findViewById(R.id.objective_progress); - verify = itemView.findViewById(R.id.objective_verify); - start = itemView.findViewById(R.id.objective_start); - revert = itemView.findViewById(R.id.objective_back); - } - } - } - - @Override - public void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - objectivesAdapter.notifyDataSetChanged(); - }); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt new file mode 100644 index 00000000000..7f004b7f02f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -0,0 +1,349 @@ +package info.nightscout.androidaps.plugins.constraints.objectives + +import android.graphics.Color +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.SystemClock +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.constraints.objectives.activities.ObjectivesExamDialog +import info.nightscout.androidaps.plugins.constraints.objectives.dialogs.NtpProgressDialog +import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtpStatus +import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui +import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.ExamTask +import info.nightscout.androidaps.receivers.NetworkChangeReceiver +import info.nightscout.androidaps.setupwizard.events.EventSWUpdate +import info.nightscout.androidaps.utils.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.objectives_fragment.* +import org.slf4j.LoggerFactory + +class ObjectivesFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.CONSTRAINTS) + private val objectivesAdapter = ObjectivesAdapter() + private val handler = Handler(Looper.getMainLooper()) + + private var disposable: CompositeDisposable = CompositeDisposable() + + private val objectiveUpdater = object : Runnable { + override fun run() { + handler.postDelayed(this, (60 * 1000).toLong()) + updateGUI() + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.objectives_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + objectives_recyclerview.layoutManager = LinearLayoutManager(view.context) + objectives_recyclerview.adapter = objectivesAdapter + objectives_fake.setOnClickListener { updateGUI() } + objectives_reset.setOnClickListener { + ObjectivesPlugin.reset() + objectives_recyclerview.adapter?.notifyDataSetChanged() + scrollToCurrentObjective() + } + scrollToCurrentObjective() + startUpdateTimer() + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(RxBus + .toObservable(EventObjectivesUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + objectives_recyclerview.adapter?.notifyDataSetChanged() + }, { + FabricPrivacy.logException(it) + }) + ) + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + @Synchronized + override fun onDestroyView() { + super.onDestroyView() + handler.removeCallbacks(objectiveUpdater) + } + + private fun startUpdateTimer() { + handler.removeCallbacks(objectiveUpdater) + for (objective in ObjectivesPlugin.objectives) { + if (objective.isStarted && !objective.isAccomplished) { + val timeTillNextMinute = (System.currentTimeMillis() - objective.startedOn) % (60 * 1000) + handler.postDelayed(objectiveUpdater, timeTillNextMinute) + break + } + } + } + + private fun scrollToCurrentObjective() { + activity?.runOnUiThread { + for (i in 0 until ObjectivesPlugin.objectives.size) { + val objective = ObjectivesPlugin.objectives[i] + if (!objective.isStarted || !objective.isAccomplished) { + context?.let { + val smoothScroller = object : LinearSmoothScroller(it) { + override fun getVerticalSnapPreference(): Int = SNAP_TO_START + override fun calculateTimeForScrolling(dx: Int): Int = super.calculateTimeForScrolling(dx) * 4 + } + smoothScroller.targetPosition = i + objectives_recyclerview.layoutManager?.startSmoothScroll(smoothScroller) + } + break + } + } + } + } + + private inner class ObjectivesAdapter : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.objectives_item, parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val objective = ObjectivesPlugin.objectives[position] + holder.title.text = MainApp.gs(R.string.nth_objective, position + 1) + if (objective.objective != 0) { + holder.objective.visibility = View.VISIBLE + holder.objective.text = MainApp.gs(objective.objective) + } else + holder.objective.visibility = View.GONE + if (objective.gate != 0) { + holder.gate.visibility = View.VISIBLE + holder.gate.text = MainApp.gs(objective.gate) + } else + holder.gate.visibility = View.GONE + if (!objective.isStarted) { + holder.gate.setTextColor(-0x1) + holder.verify.visibility = View.GONE + holder.progress.visibility = View.GONE + holder.accomplished.visibility = View.GONE + holder.unFinish.visibility = View.GONE + holder.unStart.visibility = View.GONE + if (position == 0 || ObjectivesPlugin.objectives[position - 1].isAccomplished) + holder.start.visibility = View.VISIBLE + else + holder.start.visibility = View.GONE + } else if (objective.isAccomplished) { + holder.gate.setTextColor(-0xb350b0) + holder.verify.visibility = View.GONE + holder.progress.visibility = View.GONE + holder.start.visibility = View.GONE + holder.accomplished.visibility = View.VISIBLE + holder.unFinish.visibility = View.VISIBLE + holder.unStart.visibility = View.GONE + } else if (objective.isStarted) { + holder.gate.setTextColor(-0x1) + holder.verify.visibility = View.VISIBLE + holder.verify.isEnabled = objective.isCompleted || objectives_fake.isChecked + holder.start.visibility = View.GONE + holder.accomplished.visibility = View.GONE + holder.unFinish.visibility = View.GONE + holder.unStart.visibility = View.VISIBLE + holder.progress.visibility = View.VISIBLE + holder.progress.removeAllViews() + for (task in objective.tasks) { + if (task.shouldBeIgnored()) continue + // name + val name = TextView(holder.progress.context) + name.text = MainApp.gs(task.task) + ":" + name.setTextColor(-0x1) + holder.progress.addView(name, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + // hint + task.hints.forEach { h -> + if (!task.isCompleted) + holder.progress.addView(h.generate(context)) + } + // state + val state = TextView(holder.progress.context) + state.setTextColor(-0x1) + val basicHTML = "%2\$s" + val formattedHTML = String.format(basicHTML, if (task.isCompleted) "#4CAF50" else "#FF9800", task.progress) + state.text = HtmlHelper.fromHtml(formattedHTML) + state.gravity = Gravity.END + holder.progress.addView(state, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT) + if (task is ExamTask) { + state.setOnClickListener { + val dialog = ObjectivesExamDialog() + val bundle = Bundle() + val taskPosition = objective.tasks.indexOf(task) + bundle.putInt("currentTask", taskPosition) + dialog.arguments = bundle + ObjectivesExamDialog.objective = objective + fragmentManager?.let { dialog.show(it, "ObjectivesFragment") } + } + } + // horizontal line + val separator = View(holder.progress.context) + separator.setBackgroundColor(Color.DKGRAY) + holder.progress.addView(separator, LinearLayout.LayoutParams.MATCH_PARENT, 2) + } + } + holder.accomplished.text = MainApp.gs(R.string.accomplished, DateUtil.dateAndTimeString(objective.accomplishedOn)) + holder.accomplished.setTextColor(-0x3e3e3f) + holder.verify.setOnClickListener { + NetworkChangeReceiver.grabNetworkStatus(context) + if (objectives_fake.isChecked) { + objective.accomplishedOn = DateUtil.now() + scrollToCurrentObjective() + startUpdateTimer() + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + } else { + // move out of UI thread + Thread { + NtpProgressDialog().show((context as AppCompatActivity).supportFragmentManager, "NtpCheck") + RxBus.send(EventNtpStatus(MainApp.gs(R.string.timedetection), 0)) + SntpClient.ntpTime(object : SntpClient.Callback() { + override fun run() { + log.debug("NTP time: $time System time: ${DateUtil.now()}") + SystemClock.sleep(300) + if (!networkConnected) { + RxBus.send(EventNtpStatus(MainApp.gs(R.string.notconnected), 99)) + } else if (success) { + if (objective.isCompleted(time)) { + objective.accomplishedOn = time + RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100)) + SystemClock.sleep(1000) + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + SystemClock.sleep(100) + scrollToCurrentObjective() + } else { + RxBus.send(EventNtpStatus(MainApp.gs(R.string.requirementnotmet), 99)) + } + } else { + RxBus.send(EventNtpStatus(MainApp.gs(R.string.failedretrievetime), 99)) + } + } + }, NetworkChangeReceiver.isConnected()) + }.start() + } + } + holder.start.setOnClickListener { + NetworkChangeReceiver.grabNetworkStatus(context) + if (objectives_fake.isChecked) { + objective.startedOn = DateUtil.now() + scrollToCurrentObjective() + startUpdateTimer() + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + } else + // move out of UI thread + Thread { + NtpProgressDialog().show((context as AppCompatActivity).supportFragmentManager, "NtpCheck") + RxBus.send(EventNtpStatus(MainApp.gs(R.string.timedetection), 0)) + SntpClient.ntpTime(object : SntpClient.Callback() { + override fun run() { + log.debug("NTP time: $time System time: ${DateUtil.now()}") + SystemClock.sleep(300) + if (!networkConnected) { + RxBus.send(EventNtpStatus(MainApp.gs(R.string.notconnected), 99)) + } else if (success) { + objective.startedOn = time + RxBus.send(EventNtpStatus(MainApp.gs(R.string.success), 100)) + SystemClock.sleep(1000) + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + SystemClock.sleep(100) + scrollToCurrentObjective() + } else { + RxBus.send(EventNtpStatus(MainApp.gs(R.string.failedretrievetime), 99)) + } + } + }, NetworkChangeReceiver.isConnected()) + }.start() + } + holder.unStart.setOnClickListener { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.objectives), MainApp.gs(R.string.doyouwantresetstart), Runnable { + objective.startedOn = 0 + scrollToCurrentObjective() + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + }) + } + } + holder.unFinish.setOnClickListener { + objective.accomplishedOn = 0 + scrollToCurrentObjective() + RxBus.send(EventObjectivesUpdateGui()) + RxBus.send(EventSWUpdate(false)) + } + if (objective.hasSpecialInput && !objective.isAccomplished && objective.isStarted && objective.specialActionEnabled()) { + // generate random request code if none exists + val request = SP.getString(R.string.key_objectives_request_code, String.format("%1$05d", (Math.random() * 99999).toInt())) + SP.putString(R.string.key_objectives_request_code, request) + holder.requestCode.text = MainApp.gs(R.string.requestcode, request) + holder.requestCode.visibility = View.VISIBLE + holder.enterButton.visibility = View.VISIBLE + holder.input.visibility = View.VISIBLE + holder.inputHint.visibility = View.VISIBLE + holder.enterButton.setOnClickListener { + val input = holder.input.text.toString() + objective.specialAction(activity, input) + RxBus.send(EventObjectivesUpdateGui()) + } + } else { + holder.enterButton.visibility = View.GONE + holder.input.visibility = View.GONE + holder.inputHint.visibility = View.GONE + holder.requestCode.visibility = View.GONE + } + } + + override fun getItemCount(): Int { + return ObjectivesPlugin.objectives.size + } + + inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { + val title: TextView = itemView.findViewById(R.id.objective_title) + val objective: TextView = itemView.findViewById(R.id.objective_objective) + val gate: TextView = itemView.findViewById(R.id.objective_gate) + val accomplished: TextView = itemView.findViewById(R.id.objective_accomplished) + val progress: LinearLayout = itemView.findViewById(R.id.objective_progress) + val verify: Button = itemView.findViewById(R.id.objective_verify) + val start: Button = itemView.findViewById(R.id.objective_start) + val unFinish: Button = itemView.findViewById(R.id.objective_unfinish) + val unStart: Button = itemView.findViewById(R.id.objective_unstart) + val inputHint: TextView = itemView.findViewById(R.id.objective_inputhint) + val input: EditText = itemView.findViewById(R.id.objective_input) + val enterButton: Button = itemView.findViewById(R.id.objective_enterbutton) + val requestCode: TextView = itemView.findViewById(R.id.objective_requestcode) + } + } + + fun updateGUI() { + activity?.runOnUiThread { objectivesAdapter.notifyDataSetChanged() } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.java deleted file mode 100644 index 1d18cd14b48..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.java +++ /dev/null @@ -1,164 +0,0 @@ -package info.nightscout.androidaps.plugins.constraints.objectives; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -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.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesSaved; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective1; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective2; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective3; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective4; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective5; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective6; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective7; -import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective8; -import info.nightscout.androidaps.utils.SP; - -/** - * Created by mike on 05.08.2016. - */ -public class ObjectivesPlugin extends PluginBase implements ConstraintsInterface { - private static Logger log = LoggerFactory.getLogger(L.CONSTRAINTS); - - private static ObjectivesPlugin objectivesPlugin; - - public List objectives = new ArrayList<>(); - public boolean bgIsAvailableInNS = false; - public boolean pumpStatusIsAvailableInNS = false; - public Integer manualEnacts = 0; - - public static ObjectivesPlugin getPlugin() { - if (objectivesPlugin == null) { - objectivesPlugin = new ObjectivesPlugin(); - } - return objectivesPlugin; - } - - private ObjectivesPlugin() { - super(new PluginDescription() - .mainType(PluginType.CONSTRAINTS) - .fragmentClass(ObjectivesFragment.class.getName()) - .alwaysEnabled(!Config.NSCLIENT) - .showInList(!Config.NSCLIENT) - .pluginName(R.string.objectives) - .shortName(R.string.objectives_shortname) - .description(R.string.description_objectives) - ); - setupObjectives(); - loadProgress(); - } - - @Override - public boolean specialEnableCondition() { - PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); - return pump == null || pump.getPumpDescription().isTempBasalCapable; - } - - private void setupObjectives() { - objectives.add(new Objective1()); - objectives.add(new Objective2()); - objectives.add(new Objective3()); - objectives.add(new Objective4()); - objectives.add(new Objective5()); - objectives.add(new Objective6()); - objectives.add(new Objective7()); - objectives.add(new Objective8()); - } - - public void reset() { - for (Objective objective : objectives) { - objective.setStartedOn(null); - objective.setAccomplishedOn(null); - } - bgIsAvailableInNS = false; - pumpStatusIsAvailableInNS = false; - manualEnacts = 0; - saveProgress(); - } - - public void saveProgress() { - SP.putBoolean("Objectives" + "bgIsAvailableInNS", bgIsAvailableInNS); - SP.putBoolean("Objectives" + "pumpStatusIsAvailableInNS", pumpStatusIsAvailableInNS); - SP.putString("Objectives" + "manualEnacts", Integer.toString(manualEnacts)); - if (L.isEnabled(L.CONSTRAINTS)) - log.debug("Objectives stored"); - MainApp.bus().post(new EventObjectivesSaved()); - } - - private void loadProgress() { - bgIsAvailableInNS = SP.getBoolean("Objectives" + "bgIsAvailableInNS", false); - pumpStatusIsAvailableInNS = SP.getBoolean("Objectives" + "pumpStatusIsAvailableInNS", false); - try { - manualEnacts = SP.getInt("Objectives" + "manualEnacts", 0); - } catch (Exception e) { - log.error("Unhandled exception", e); - } - if (L.isEnabled(L.CONSTRAINTS)) - log.debug("Objectives loaded"); - } - - public List getObjectives() { - return objectives; - } - - /** - * Constraints interface - **/ - @Override - public Constraint isLoopInvocationAllowed(Constraint value) { - if (!objectives.get(0).isStarted()) - value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), 1), this); - return value; - } - - @Override - 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 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 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 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 Constraint applyMaxIOBConstraints(Constraint maxIob) { - if (objectives.get(3).isStarted() && !objectives.get(3).isAccomplished()) - maxIob.set(0d, String.format(MainApp.gs(R.string.objectivenotfinished), 4), this); - return maxIob; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt new file mode 100644 index 00000000000..ebae80150cc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPlugin.kt @@ -0,0 +1,170 @@ +package info.nightscout.androidaps.plugins.constraints.objectives + +import android.app.Activity +import com.google.common.base.Charsets +import com.google.common.hash.Hashing +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.Config +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.constraints.objectives.objectives.* +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.SP +import java.util.* + +/** + * Created by mike on 05.08.2016. + */ +object ObjectivesPlugin : PluginBase(PluginDescription() + .mainType(PluginType.CONSTRAINTS) + .fragmentClass(ObjectivesFragment::class.qualifiedName) + .alwaysEnabled(Config.APS) + .showInList(Config.APS) + .pluginName(R.string.objectives) + .shortName(R.string.objectives_shortname) + .description(R.string.description_objectives)), ConstraintsInterface { + + var objectives: MutableList = ArrayList() + + val FIRST_OBJECTIVE = 0 + val USAGE_OBJECTIVE = 1 + val EXAM_OBJECTIVE = 2 + val OPENLOOP_OBJECTIVE = 3 + val MAXBASAL_OBJECTIVE = 4 + val MAXIOB_ZERO_CL_OBJECTIVE = 5 + val MAXIOB_OBJECTIVE = 6 + val AUTOSENS_OBJECTIVE = 7 + val AMA_OBJECTIVE = 8 + val SMB_OBJECTIVE = 9 + + init { + convertSP() + setupObjectives() + } + + override fun specialEnableCondition(): Boolean { + val pump = ConfigBuilderPlugin.getPlugin().activePump + return pump == null || pump.pumpDescription.isTempBasalCapable + } + + // convert 2.3 SP version + private fun convertSP() { + doConvertSP(0, "config") + doConvertSP(1, "openloop") + doConvertSP(2, "maxbasal") + doConvertSP(3, "maxiobzero") + doConvertSP(4, "maxiob") + doConvertSP(5, "autosens") + doConvertSP(6, "ama") + doConvertSP(7, "smb") + } + + private fun doConvertSP(number: Int, name: String) { + if (!SP.contains("Objectives_" + name + "_started")) { + SP.putLong("Objectives_" + name + "_started", SP.getLong("Objectives" + number + "started", 0L)) + SP.putLong("Objectives_" + name + "_accomplished", SP.getLong("Objectives" + number + "accomplished", 0L)) + } + // TODO: we can remove Objectives1accomplished sometimes later + } + + private fun setupObjectives() { + objectives.clear() + objectives.add(Objective0()) + objectives.add(Objective1()) + objectives.add(Objective2()) + objectives.add(Objective3()) + objectives.add(Objective4()) + objectives.add(Objective5()) + objectives.add(Objective6()) + objectives.add(Objective7()) + objectives.add(Objective8()) + objectives.add(Objective9()) + } + + fun reset() { + for (objective in objectives) { + objective.startedOn = 0 + objective.accomplishedOn = 0 + } + SP.putBoolean(R.string.key_ObjectivesbgIsAvailableInNS, false) + SP.putBoolean(R.string.key_ObjectivespumpStatusIsAvailableInNS, false) + SP.putInt(R.string.key_ObjectivesmanualEnacts, 0) + SP.putBoolean(R.string.key_objectiveuseprofileswitch, false) + SP.putBoolean(R.string.key_objectiveusedisconnect, false) + SP.putBoolean(R.string.key_objectiveusereconnect, false) + SP.putBoolean(R.string.key_objectiveusetemptarget, false) + SP.putBoolean(R.string.key_objectiveuseactions, false) + SP.putBoolean(R.string.key_objectiveuseloop, false) + SP.putBoolean(R.string.key_objectiveusescale, false) + } + + fun completeObjectives(activity: Activity, request: String) { + val requestCode = SP.getString(R.string.key_objectives_request_code, "") + var url = SP.getString(R.string.key_nsclientinternal_url, "").toLowerCase() + if (!url.endsWith("/")) url = "$url/" + @Suppress("DEPRECATION") val hashNS = Hashing.sha1().hashString(url + BuildConfig.APPLICATION_ID + "/" + requestCode, Charsets.UTF_8).toString() + if (request.equals(hashNS.substring(0, 10), ignoreCase = true)) { + SP.putLong("Objectives_" + "openloop" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "openloop" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "maxbasal" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "maxbasal" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "maxiobzero" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "maxiobzero" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "maxiob" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "maxiob" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "autosens" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "autosens" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "ama" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "ama" + "_accomplished", DateUtil.now()) + SP.putLong("Objectives_" + "smb" + "_started", DateUtil.now()) + SP.putLong("Objectives_" + "smb" + "_accomplished", DateUtil.now()) + setupObjectives() + OKDialog.show(activity, MainApp.gs(R.string.objectives), MainApp.gs(R.string.codeaccepted)) + } else { + OKDialog.show(activity, MainApp.gs(R.string.objectives), MainApp.gs(R.string.codeinvalid)) + } + } + + /** + * Constraints interface + */ + override fun isLoopInvocationAllowed(value: Constraint): Constraint { + if (!objectives[FIRST_OBJECTIVE].isStarted) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), FIRST_OBJECTIVE + 1), this) + return value + } + + override fun isClosedLoopAllowed(value: Constraint): Constraint { + if (!objectives[MAXIOB_ZERO_CL_OBJECTIVE].isStarted) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), MAXIOB_ZERO_CL_OBJECTIVE + 1), this) + return value + } + + override fun isAutosensModeEnabled(value: Constraint): Constraint { + if (!objectives[AUTOSENS_OBJECTIVE].isStarted) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), AUTOSENS_OBJECTIVE + 1), this) + return value + } + + override fun isAMAModeEnabled(value: Constraint): Constraint { + if (!objectives[AMA_OBJECTIVE].isStarted) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), AMA_OBJECTIVE + 1), this) + return value + } + + override fun isSMBModeEnabled(value: Constraint): Constraint { + if (!objectives[SMB_OBJECTIVE].isStarted) + value.set(false, String.format(MainApp.gs(R.string.objectivenotstarted), SMB_OBJECTIVE + 1), this) + return value + } + + override fun applyMaxIOBConstraints(maxIob: Constraint): Constraint { + if (objectives[MAXIOB_ZERO_CL_OBJECTIVE].isStarted && !objectives[MAXIOB_ZERO_CL_OBJECTIVE].isAccomplished) + maxIob.set(0.0, String.format(MainApp.gs(R.string.objectivenotfinished), MAXIOB_ZERO_CL_OBJECTIVE + 1), this) + return maxIob + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt new file mode 100644 index 00000000000..1428652be51 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/activities/ObjectivesExamDialog.kt @@ -0,0 +1,130 @@ +package info.nightscout.androidaps.plugins.constraints.objectives.activities + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui +import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective +import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.* +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.OKDialog +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.ToastUtils +import kotlinx.android.synthetic.main.objectives_exam_fragment.* + +class ObjectivesExamDialog : DialogFragment() { + companion object { + var objective: Objective? = null + } + + var currentTask = 0 + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + // load data from bundle + (savedInstanceState ?: arguments)?.let { bundle -> + currentTask = bundle.getInt("currentTask", 0) + } + + return inflater.inflate(R.layout.objectives_exam_fragment, container, false) + } + + override fun onStart() { + super.onStart() + dialog?.setCanceledOnTouchOutside(false) + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + override fun onResume() { + super.onResume() + updateGui() + } + + override fun onSaveInstanceState(bundle: Bundle) { + super.onSaveInstanceState(bundle) + bundle.putInt("currentTask", currentTask) + } + + fun updateGui() { + objective?.let { objective -> + val task: ExamTask = objective.tasks[currentTask] as ExamTask + objectives_exam_name.setText(task.task) + objectives_exam_question.setText(task.question) + // Options + objectives_exam_options.removeAllViews() + task.options.forEach { + val cb = it.generate(context) + if (task.answered) { + cb.isEnabled = false + if (it.isCorrect) + cb.isChecked = true + } + objectives_exam_options.addView(cb) + } + // Hints + objectives_exam_hints.removeAllViews() + for (h in task.hints) { + objectives_exam_hints.addView(h.generate(context)) + } + // Disabled to + objectives_exam_disabledto.text = MainApp.gs(R.string.answerdisabledto, DateUtil.timeString(task.disabledTo)) + objectives_exam_disabledto.visibility = if (task.isEnabledAnswer) View.GONE else View.VISIBLE + // Buttons + objectives_exam_verify.isEnabled = !task.answered && task.isEnabledAnswer + objectives_exam_verify.setOnClickListener { + var result = true + for (o in task.options) { + val option: Option = o as Option; + result = result && option.evaluate() + } + task.setAnswered(result); + if (!result) { + task.disabledTo = DateUtil.now() + T.hours(1).msecs() + ToastUtils.showToastInUiThread(context, R.string.wronganswer) + } else task.disabledTo = 0 + updateGui() + RxBus.send(EventObjectivesUpdateGui()) + } + close.setOnClickListener { dismiss() } + objectives_exam_reset.setOnClickListener { + task.answered = false + //task.disabledTo = 0 + updateGui() + RxBus.send(EventObjectivesUpdateGui()) + } + objectives_back_button.isEnabled = currentTask != 0 + objectives_back_button.setOnClickListener { + currentTask-- + updateGui() + } + objectives_next_button.isEnabled = currentTask != objective.tasks.size - 1 + objectives_next_button.setOnClickListener { + currentTask++ + updateGui() + } + + objectives_next_unanswered_button.isEnabled = !objective.isCompleted + objectives_next_unanswered_button.setOnClickListener { + for (i in (currentTask + 1)..(objective.tasks.size - 1)) { + if (!objective.tasks[i].isCompleted) { + currentTask = i + updateGui() + return@setOnClickListener + } + } + for (i in 0..currentTask) { + if (!objective.tasks[i].isCompleted) { + currentTask = i + updateGui() + return@setOnClickListener + } + } + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/dialogs/NtpProgressDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/dialogs/NtpProgressDialog.kt new file mode 100644 index 00000000000..77e6f22b436 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/dialogs/NtpProgressDialog.kt @@ -0,0 +1,85 @@ +package info.nightscout.androidaps.plugins.constraints.objectives.dialogs + +import android.os.Bundle +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtpStatus +import info.nightscout.androidaps.utils.FabricPrivacy +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.dialog_bolusprogress.* +import org.slf4j.LoggerFactory + +class NtpProgressDialog : DialogFragment() { + private val log = LoggerFactory.getLogger(L.UI) + private val disposable = CompositeDisposable() + + private val DEFAULT_STATE = MainApp.gs(R.string.timedetection) + private var state: String = DEFAULT_STATE + private var percent = 0 + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + dialog?.setTitle(String.format(MainApp.gs(R.string.objectives))) + isCancelable = false + + state = savedInstanceState?.getString("state", DEFAULT_STATE) ?: DEFAULT_STATE + percent = savedInstanceState?.getInt("percent", 0) ?: 0 + + return inflater.inflate(R.layout.dialog_bolusprogress, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + overview_bolusprogress_stop.setOnClickListener { dismiss() } + overview_bolusprogress_status.text = state + overview_bolusprogress_progressbar.max = 100 + overview_bolusprogress_progressbar.progress = percent + overview_bolusprogress_stop.text = MainApp.gs(R.string.close) + overview_bolusprogress_title.text = MainApp.gs(R.string.please_wait) + } + + override fun onResume() { + super.onResume() + if (L.isEnabled(L.UI)) log.debug("onResume") + if (percent == 100) { + dismiss() + return + } else + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + disposable.add(toObservable(EventNtpStatus::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ event: EventNtpStatus -> + if (L.isEnabled(L.UI)) log.debug("Status: " + event.status + " Percent: " + event.percent) + overview_bolusprogress_status?.text = event.status + overview_bolusprogress_progressbar?.progress = event.percent + if (event.percent == 100) { + SystemClock.sleep(100) + dismiss() + } + state = event.status + percent = event.percent + }) { FabricPrivacy.logException(it) } + ) + } + + override fun onPause() { + if (L.isEnabled(L.UI)) log.debug("onPause") + super.onPause() + disposable.clear() + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putString("state", state) + outState.putInt("percent", percent) + super.onSaveInstanceState(outState) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventNtpStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventNtpStatus.kt new file mode 100644 index 00000000000..fc4e5cb8e91 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventNtpStatus.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.constraints.objectives.events + +import info.nightscout.androidaps.events.Event + +class EventNtpStatus(val status: String, val percent: Int) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesSaved.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesSaved.java deleted file mode 100644 index d4dbb0251de..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesSaved.java +++ /dev/null @@ -1,6 +0,0 @@ -package info.nightscout.androidaps.plugins.constraints.objectives.events; - -import info.nightscout.androidaps.events.Event; - -public class EventObjectivesSaved extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesUpdateGui.kt new file mode 100644 index 00000000000..fd59db7d9fe --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/events/EventObjectivesUpdateGui.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.constraints.objectives.events + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventObjectivesUpdateGui : EventUpdateGui() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java index 83abe156882..7082c7e7bce 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/objectives/Objective.java @@ -1,35 +1,46 @@ package info.nightscout.androidaps.plugins.constraints.objectives.objectives; -import android.support.annotation.StringRes; +import android.app.Activity; +import android.content.Context; +import android.graphics.Color; +import android.text.util.Linkify; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import androidx.annotation.StringRes; 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.utils.DateUtil; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; public abstract class Objective { - private int number; + private String spName; @StringRes private int objective; @StringRes private int gate; - private Date startedOn; - private Date accomplishedOn; - private List tasks = new ArrayList<>(); + private long startedOn; + private long accomplishedOn; + List tasks = new ArrayList<>(); + public boolean hasSpecialInput = false; - public Objective(int number, @StringRes int objective, @StringRes int gate) { - this.number = number; + public Objective(String spName, @StringRes int objective, @StringRes int gate) { + this.spName = spName; this.objective = objective; this.gate = gate; - startedOn = new Date(SP.getLong("Objectives" + number + "started", 0L)); - if (startedOn.getTime() == 0L) startedOn = null; - accomplishedOn = new Date(SP.getLong("Objectives" + number + "accomplished", 0L)); - if (accomplishedOn.getTime() == 0L) accomplishedOn = null; + startedOn = SP.getLong("Objectives_" + spName + "_started", 0L); + accomplishedOn = SP.getLong("Objectives_" + spName + "_accomplished", 0L); + if ((accomplishedOn - DateUtil.now()) > T.hours(3).msecs() || (startedOn - DateUtil.now()) > T.hours(3).msecs()) { // more than 3 hours in the future + startedOn = 0; + accomplishedOn = 0; + } setupTasks(tasks); for (Task task : tasks) task.objective = this; } @@ -42,19 +53,23 @@ public boolean isCompleted() { return true; } - public boolean isRevertable() { - return false; + public boolean isCompleted(long trueTime) { + for (Task task : tasks) { + if (!task.shouldBeIgnored() && !task.isCompleted(trueTime)) + return false; + } + return true; } public boolean isAccomplished() { - return accomplishedOn != null; + return accomplishedOn != 0 && accomplishedOn < DateUtil.now(); } public boolean isStarted() { - return startedOn != null; + return startedOn != 0; } - public Date getStartedOn() { + public long getStartedOn() { return startedOn; } @@ -66,14 +81,18 @@ public int getGate() { return gate; } - public void setStartedOn(Date startedOn) { + public void setStartedOn(long startedOn) { this.startedOn = startedOn; - SP.putLong("Objectives" + number + "started", startedOn == null ? 0 : startedOn.getTime()); + SP.putLong("Objectives_" + spName + "_started", startedOn); } - public void setAccomplishedOn(Date accomplishedOn) { + public void setAccomplishedOn(long accomplishedOn) { this.accomplishedOn = accomplishedOn; - SP.putLong("Objectives" + number + "accomplished", accomplishedOn == null ? 0 : accomplishedOn.getTime()); + SP.putLong("Objectives_" + spName + "_accomplished", accomplishedOn); + } + + public long getAccomplishedOn() { + return accomplishedOn; } protected void setupTasks(List tasks) { @@ -84,16 +103,21 @@ public List getTasks() { return tasks; } + public boolean specialActionEnabled() { return true; } + + public void specialAction(Activity activity, String input) {} + public abstract class Task { @StringRes private int task; private Objective objective; + ArrayList hints = new ArrayList<>(); public Task(@StringRes int task) { this.task = task; } - public int getTask() { + public @StringRes int getTask() { return task; } @@ -102,11 +126,21 @@ protected Objective getObjective() { } public abstract boolean isCompleted(); + public boolean isCompleted(long trueTime) { return isCompleted(); }; public String getProgress() { return MainApp.gs(isCompleted() ? R.string.completed_well_done : R.string.not_completed_yet); } + Task hint(Hint hint) { + hints.add(hint); + return this; + } + + public ArrayList getHints() { + return hints; + } + public boolean shouldBeIgnored() { return false; } @@ -116,19 +150,24 @@ public class MinimumDurationTask extends Task { private long minimumDuration; - public MinimumDurationTask(long minimumDuration) { + MinimumDurationTask(long minimumDuration) { super(R.string.time_elapsed); this.minimumDuration = minimumDuration; } @Override public boolean isCompleted() { - return getObjective().isStarted() && System.currentTimeMillis() - getObjective().getStartedOn().getTime() >= minimumDuration; + return getObjective().isStarted() && System.currentTimeMillis() - getObjective().getStartedOn() >= minimumDuration; + } + + @Override + public boolean isCompleted(long trueTime) { + return getObjective().isStarted() && trueTime - getObjective().getStartedOn() >= minimumDuration; } @Override public String getProgress() { - return getDurationText(System.currentTimeMillis() - getObjective().getStartedOn().getTime()) + return getDurationText(System.currentTimeMillis() - getObjective().getStartedOn()) + " / " + getDurationText(minimumDuration); } @@ -142,4 +181,107 @@ private String getDurationText(long duration) { } } + public class ExamTask extends Task { + @StringRes + int question; + ArrayList

+ * Author: Andy {andy.rozman@gmail.com} + */ + +public enum PumpHistoryEntryGroup { + + All(R.string.medtronic_history_group_all), // + Bolus(R.string.danar_history_bolus), // + Basal(R.string.medtronic_history_group_basal), // + Prime(R.string.danar_history_prime), // + Configuration(R.string.medtronic_history_group_configuration), // + Alarm(R.string.danar_history_alarm), // + Glucose(R.string.danar_history_glucose), // + Notification(R.string.medtronic_history_group_notification), // + Statistic(R.string.medtronic_history_group_statistic), + Unknown(R.string.medtronic_history_group_unknown), // + ; + + private int resourceId; + private String translated; + + public static boolean doNotTranslate = false; + + private static List list; + + static { + list = new ArrayList<>(); + + for (PumpHistoryEntryGroup pumpHistoryEntryGroup : values()) { + //if (doNotTranslate) { + pumpHistoryEntryGroup.translated = MainApp.gs(pumpHistoryEntryGroup.resourceId); + //} + list.add(pumpHistoryEntryGroup); + } + } + + + PumpHistoryEntryGroup(int resourceId) { + this.resourceId = resourceId; + // this.translated = MainApp.gs(resourceId); + } + + + public static List getList() { + return list; + } + + + public int getResourceId() { + return resourceId; + } + + + public String getTranslated() { + return translated; + } + + + public String toString() { + return this.translated; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpStatusType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpStatusType.java new file mode 100644 index 00000000000..bcdeeb59e4b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpStatusType.java @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.plugins.pump.common.defs; + +/** + * Created by andy on 5/12/18. + */ + +public enum PumpStatusType { + Running("normal"), // + Suspended("suspended") // + ; + + private String statusString; + + + PumpStatusType(String statusString) { + this.statusString = statusString; + } + + + public String getStatus() { + return statusString; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.java index 9a7e351bf17..00ce44fe715 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/defs/PumpType.java @@ -6,133 +6,143 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.pump.common.data.DoseSettings; - +import info.nightscout.androidaps.utils.Round; /** * Created by andy on 02/05/2018. - * + *

* Most of this defintions is intended for VirtualPump only, but they can be used by other plugins. */ public enum PumpType { - GenericAAPS("Generic AAPS", 0.1d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + GenericAAPS("Generic AAPS", ManufacturerType.AndroidAPS, "VirutalPump", 0.1d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Percent, // - new DoseSettings(10,30, 24*60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.01d, 0.01d, null, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(10, 30, 24 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.01d, 0.01d, null, PumpCapability.VirtualPumpCapabilities, false, false), // // Cellnovo - Cellnovo1("Cellnovo", 0.05d, null, // - new DoseSettings(0.05d, 30, 24*60, 1d, null), + Cellnovo1("Cellnovo", ManufacturerType.Cellnovo, "Cellnovo", 0.05d, null, // + new DoseSettings(0.05d, 30, 24 * 60, 1d, null), PumpTempBasalType.Percent, - new DoseSettings(5,30, 24*60, 0d, 200d), PumpCapability.BasalRate_Duration30minAllowed, // - 0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(5, 30, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities, false, false), // // Accu-Chek - AccuChekCombo("Accu-Chek Combo", 0.1d, null, // - new DoseSettings(0.1d, 15, 12*60, 0.1d), // + AccuChekCombo("Accu-Chek Combo", ManufacturerType.Roche, "Combo", 0.1d, null, // + new DoseSettings(0.1d, 15, 12 * 60, 0.1d), // PumpTempBasalType.Percent, - new DoseSettings(10, 15, 12*60,0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.01d, 0.01d, DoseStepSize.ComboBasal, PumpCapability.ComboCapabilities), // + new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.01d, 0.01d, DoseStepSize.ComboBasal, PumpCapability.ComboCapabilities, false, false), // - AccuChekSpirit("Accu-Chek Spirit", 0.1d, null, // - new DoseSettings(0.1d, 15, 12*60, 0.1d), // + AccuChekSpirit("Accu-Chek Spirit", ManufacturerType.Roche, "Spirit", 0.1d, null, // + new DoseSettings(0.1d, 15, 12 * 60, 0.1d), // PumpTempBasalType.Percent, - new DoseSettings(10, 15, 12*60,0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.01d, 0.1d, null, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(10, 15, 12 * 60, 0d, 500d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.01d, 0.1d, null, PumpCapability.VirtualPumpCapabilities, false, false), // - AccuChekInsight("Accu-Chek Insight", 0.05d, DoseStepSize.InsightBolus, // - new DoseSettings(0.05d, 15, 24*60, 0.05d), // + AccuChekInsight("Accu-Chek Insight", ManufacturerType.Roche, "Insight", 0.05d, DoseStepSize.InsightBolus, // + new DoseSettings(0.05d, 15, 24 * 60, 0.05d), // PumpTempBasalType.Percent, - new DoseSettings(10, 15, 24*60,0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.02d, 0.01d, null, PumpCapability.InsightCapabilities), // + new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.02d, 0.01d, null, PumpCapability.InsightCapabilities, false, false), // - AccuChekInsightBluetooth("Accu-Chek Insight", 0.01d, null, // - new DoseSettings(0.01d, 15, 24*60, 0.05d), // + AccuChekInsightBluetooth("Accu-Chek Insight", ManufacturerType.Roche, "Insight", 0.01d, null, // + new DoseSettings(0.01d, 15, 24 * 60, 0.05d), // PumpTempBasalType.Percent, - new DoseSettings(10, 15, 24*60,0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.02d, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities), // + new DoseSettings(10, 15, 24 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.02d, 0.01d, DoseStepSize.InsightBolus, PumpCapability.InsightCapabilities, false, false), // // Animas - AnimasVibe("Animas Vibe", 0.05d, null, // AnimasBolus? - new DoseSettings(0.05d, 30, 12*60, 0.05d), // + AnimasVibe("Animas Vibe", ManufacturerType.Animas, "Vibe", 0.05d, null, // AnimasBolus? + new DoseSettings(0.05d, 30, 12 * 60, 0.05d), // PumpTempBasalType.Percent, // - new DoseSettings(10, 30, 24*60, 0d, 300d), PumpCapability.BasalRate_Duration30minAllowed, // - 0.025d, 5d, 0d, null, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(10, 30, 24 * 60, 0d, 300d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.025d, 5d, 0d, null, PumpCapability.VirtualPumpCapabilities, false, false), // - AnimasPing("Animas Ping", AnimasVibe), + AnimasPing("Animas Ping", "Ping", AnimasVibe), // Dana - DanaR("DanaR", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + DanaR("DanaR", ManufacturerType.Sooil, "DanaR", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Percent, // - new DoseSettings(10d, 60, 24*60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // - 0.04d, 0.01d, null, PumpCapability.DanaCapabilities), + new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // + 0.04d, 0.01d, null, PumpCapability.DanaCapabilities, false, false), - DanaRKorean("DanaR Korean", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + DanaRKorean("DanaR Korean", ManufacturerType.Sooil, "DanaRKorean", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Percent, // - new DoseSettings(10d, 60, 24*60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // - 0.1d, 0.01d, null, PumpCapability.DanaCapabilities), + new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minNotAllowed, // + 0.1d, 0.01d, null, PumpCapability.DanaCapabilities, false, false), - DanaRS("DanaRS", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + DanaRS("DanaRS", ManufacturerType.Sooil, "DanaRS", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Percent, // - new DoseSettings(10d, 60, 24*60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.04d, 0.01d, null, PumpCapability.DanaWithHistoryCapabilities), - - DanaRv2("DanaRv2", DanaRS), + new DoseSettings(10d, 60, 24 * 60, 0d, 200d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.04d, 0.01d, null, PumpCapability.DanaWithHistoryCapabilities, false, false), + DanaRv2("DanaRv2", "DanaRv2", DanaRS), // Insulet - Insulet_Omnipod("Insulet Omnipod", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + Insulet_Omnipod("Insulet Omnipod", ManufacturerType.Insulet, "Omnipod (Eros)", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // + PumpTempBasalType.Absolute, // + new DoseSettings(0.05d, 30, 12 * 60, 0d, 30.0d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.05d, 0.05d, null, PumpCapability.OmnipodCapabilities, true, true), + + Insulet_Omnipod_Dash("Insulet Omnipod Dash", ManufacturerType.Insulet, "Omnipod Dash", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Absolute, // - new DoseSettings(0.05d, 30, 12*60, 0d, 30.0d), PumpCapability.BasalRate_Duration30minAllowed, // cannot exceed max basal rate 30u/hr - 0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), + new DoseSettings(0.05d, 30, 12 * 60, 0d, 30.0d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.05d, 0.05d, null, PumpCapability.OmnipodCapabilities, true, true), // TODO just copied OmniPod for now // Medtronic - Medtronic_512_712("Medtronic 512/712", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + Medtronic_512_712("Medtronic 512/712", ManufacturerType.Medtronic, "512/712", 0.1d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Absolute, // - new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // - 0.05d, 0.05d, null, PumpCapability.VirtualPumpCapabilities), // TODO + new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.05d, 0.05d, null, PumpCapability.MedtronicCapabilities, false, false), // - Medtronic_515_715("Medtronic 515/715", Medtronic_512_712), - Medtronic_522_722("Medtronic 522/722", Medtronic_512_712), + Medtronic_515_715("Medtronic 515/715", "515/715", Medtronic_512_712), + Medtronic_522_722("Medtronic 522/722", "522/722", Medtronic_512_712), - Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", 0.05d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + Medtronic_523_723_Revel("Medtronic 523/723 (Revel)", ManufacturerType.Medtronic, "523/723 (Revel)", 0.05d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Absolute, // - new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // - 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.MedtronicCapabilities, false, false), // - Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", Medtronic_523_723_Revel), // TODO + Medtronic_554_754_Veo("Medtronic 554/754 (Veo)", "554/754 (Veo)", Medtronic_523_723_Revel), // TODO - Medtronic_640G("Medtronic 640G", 0.025d, null, // - new DoseSettings(0.05d, 30, 8*60, 0.05d), // + Medtronic_640G("Medtronic 640G", ManufacturerType.Medtronic, "640G", 0.025d, null, // + new DoseSettings(0.05d, 30, 8 * 60, 0.05d), // PumpTempBasalType.Absolute, // - new DoseSettings(0.05d, 30, 24*60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // - 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities), // + new DoseSettings(0.05d, 30, 24 * 60, 0d, 35d), PumpCapability.BasalRate_Duration30minAllowed, // + 0.025d, 0.025d, DoseStepSize.MedtronicVeoBasal, PumpCapability.VirtualPumpCapabilities, false, false), // // Tandem - TandemTSlim("Tandem t:slim", 0.01d, null, // - new DoseSettings(0.01d,15, 8*60, 0.4d), + TandemTSlim("Tandem t:slim", ManufacturerType.Tandem, "t:slim", 0.01d, null, // + new DoseSettings(0.01d, 15, 8 * 60, 0.4d), PumpTempBasalType.Percent, - new DoseSettings(1,15, 8*60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // - 0.1d, 0.001d, null, PumpCapability.VirtualPumpCapabilities), + new DoseSettings(1, 15, 8 * 60, 0d, 250d), PumpCapability.BasalRate_Duration15and30minAllowed, // + 0.1d, 0.001d, null, PumpCapability.VirtualPumpCapabilities, false, false), - TandemTFlex("Tandem t:flex", TandemTSlim), // - TandemTSlimG4("Tandem t:slim G4", TandemTSlim), // - TandemTSlimX2("Tandem t:slim X2", TandemTSlim), // - ; + TandemTFlex("Tandem t:flex", "t:flex", TandemTSlim), // + TandemTSlimG4("Tandem t:slim G4", "t:slim G4", TandemTSlim), // + TandemTSlimX2("Tandem t:slim X2", "t:slim X2", TandemTSlim), // + + // MDI + MDI("MDI", ManufacturerType.AndroidAPS, "MDI"); private String description; + private ManufacturerType manufacturer; + private String model; private double bolusSize; private DoseStepSize specialBolusSize; private DoseSettings extendedBolusSettings; @@ -144,12 +154,13 @@ public enum PumpType { private double baseBasalStep; // private DoseStepSize baseBasalSpecialSteps; // private PumpCapability pumpCapability; + private boolean hasFixedUnreachableAlert; + private boolean hasCustomUnreachableAlertCheck; private PumpType parent; - private static Map mapByDescription; + private static Map mapByDescription; - static - { + static { mapByDescription = new HashMap<>(); for (PumpType pumpType : values()) { @@ -158,33 +169,42 @@ public enum PumpType { } - PumpType(String description, PumpType parent) - { + PumpType(String description, String model, PumpType parent) { this.description = description; this.parent = parent; + this.model = model; } - PumpType(String description, PumpType parent, PumpCapability pumpCapability) - { + + PumpType(String description, ManufacturerType manufacturer, String model) { + this.description = description; + this.manufacturer = manufacturer; + this.model = model; + } + + + PumpType(String description, String model, PumpType parent, PumpCapability pumpCapability) { this.description = description; this.parent = parent; this.pumpCapability = pumpCapability; + parent.model = model; } - PumpType(String description, double bolusSize, DoseStepSize specialBolusSize, // + PumpType(String description, ManufacturerType manufacturer, String model, double bolusSize, DoseStepSize specialBolusSize, // DoseSettings extendedBolusSettings, // - PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, // - double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) - { - this(description, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability); + PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, // + double baseBasalMinValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability, boolean hasFixedUnreachableAlert, boolean hasCustomUnreachableAlertCheck) { + this(description, manufacturer, model, bolusSize, specialBolusSize, extendedBolusSettings, pumpTempBasalType, tbrSettings, specialBasalDurations, baseBasalMinValue, null, baseBasalStep, baseBasalSpecialSteps, pumpCapability, hasFixedUnreachableAlert, hasCustomUnreachableAlertCheck); } - PumpType(String description, double bolusSize, DoseStepSize specialBolusSize, // + PumpType(String description, ManufacturerType manufacturer, String model, double bolusSize, DoseStepSize specialBolusSize, // DoseSettings extendedBolusSettings, // PumpTempBasalType pumpTempBasalType, DoseSettings tbrSettings, PumpCapability specialBasalDurations, // - double baseBasalMinValue, Double baseBasalMaxValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, PumpCapability pumpCapability) - { + double baseBasalMinValue, Double baseBasalMaxValue, double baseBasalStep, DoseStepSize baseBasalSpecialSteps, // + PumpCapability pumpCapability, boolean hasFixedUnreachableAlert, boolean hasCustomUnreachableAlertCheck) { this.description = description; + this.manufacturer = manufacturer; + this.model = model; this.bolusSize = bolusSize; this.specialBolusSize = specialBolusSize; this.extendedBolusSettings = extendedBolusSettings; @@ -196,13 +216,30 @@ public enum PumpType { this.baseBasalStep = baseBasalStep; this.baseBasalSpecialSteps = baseBasalSpecialSteps; this.pumpCapability = pumpCapability; + this.hasFixedUnreachableAlert = hasFixedUnreachableAlert; + this.hasCustomUnreachableAlertCheck = hasCustomUnreachableAlertCheck; } + public boolean getHasFixedUnreachableAlert() { + return hasFixedUnreachableAlert; + } + + public boolean getHasCustomUnreachableAlertCheck() { + return hasFixedUnreachableAlert; + } public String getDescription() { return description; } + public ManufacturerType getManufacturer() { + return isParentSet() ? parent.manufacturer : manufacturer; + } + + public String getModel() { + return isParentSet() ? parent.model : model; + } + public PumpCapability getPumpCapability() { if (isParentSet()) @@ -261,20 +298,15 @@ public PumpType getParent() { } - private boolean isParentSet() - { - return this.parent!=null; + private boolean isParentSet() { + return this.parent != null; } - public static PumpType getByDescription(String desc) - { - if (mapByDescription.containsKey(desc)) - { + public static PumpType getByDescription(String desc) { + if (mapByDescription.containsKey(desc)) { return mapByDescription.get(desc); - } - else - { + } else { return PumpType.GenericAAPS; } } @@ -282,7 +314,7 @@ public static PumpType getByDescription(String desc) public String getFullDescription(String i18nTemplate, boolean hasExtendedBasals) { - String unit = getPumpTempBasalType()==PumpTempBasalType.Percent ? "%" : ""; + String unit = getPumpTempBasalType() == PumpTempBasalType.Percent ? "%" : ""; DoseSettings eb = getExtendedBolusSettings(); DoseSettings tbr = getTbrSettings(); @@ -291,24 +323,22 @@ public String getFullDescription(String i18nTemplate, boolean hasExtendedBasals) return String.format(i18nTemplate, // getStep("" + getBolusSize(), getSpecialBolusSize()), // - eb.getStep(), eb.getDurationStep(), eb.getMaxDuration()/60, // + eb.getStep(), eb.getDurationStep(), eb.getMaxDuration() / 60, // getStep(getBaseBasalRange(), getBaseBasalSpecialSteps()), // tbr.getMinDose() + unit + "-" + tbr.getMaxDose() + unit, tbr.getStep() + unit, - tbr.getDurationStep(), tbr.getMaxDuration()/60, extendedNote); + tbr.getDurationStep(), tbr.getMaxDuration() / 60, extendedNote); } - private String getBaseBasalRange() - { + private String getBaseBasalRange() { Double maxValue = getBaseBasalMaxValue(); - return maxValue==null ? "" + getBaseBasalMinValue() : getBaseBasalMinValue() + "-" + maxValue; + return maxValue == null ? "" + getBaseBasalMinValue() : getBaseBasalMinValue() + "-" + maxValue; } - private String getStep(String step, DoseStepSize stepSize) - { - if (stepSize!=null) + private String getStep(String step, DoseStepSize stepSize) { + if (stepSize != null) return step + " [" + stepSize.getDescription() + "] *"; else return "" + step; @@ -316,18 +346,15 @@ private String getStep(String step, DoseStepSize stepSize) public boolean hasExtendedBasals() { - return ((getBaseBasalSpecialSteps() !=null) || (getSpecialBolusSize() != null)); + return ((getBaseBasalSpecialSteps() != null) || (getSpecialBolusSize() != null)); } public PumpCapability getSpecialBasalDurations() { - if (isParentSet()) - { + if (isParentSet()) { return parent.getSpecialBasalDurations(); - } - else - { + } else { return specialBasalDurations == null ? // PumpCapability.BasalRate_Duration15and30minNotAllowed : specialBasalDurations; } @@ -338,20 +365,24 @@ public double determineCorrectBolusSize(double bolusAmount) { return bolusAmount; } - double bolusStepSize; + double bolusStepSize = getBolusSize(); - if (getSpecialBolusSize() == null) { - bolusStepSize = getBolusSize(); - } else { + if (getSpecialBolusSize() != null) { DoseStepSize specialBolusSize = getSpecialBolusSize(); - - bolusStepSize = specialBolusSize.getStepSizeForAmount((double)bolusAmount); + bolusStepSize = specialBolusSize.getStepSizeForAmount(bolusAmount); } - return Math.round(bolusAmount / bolusStepSize) * bolusStepSize; + return Round.roundTo(bolusAmount, bolusStepSize); } + public double determineCorrectBolusStepSize(double bolusAmount) { + DoseStepSize specialBolusSize = getSpecialBolusSize(); + if (specialBolusSize != null) + return specialBolusSize.getStepSizeForAmount(bolusAmount); + return getBolusSize(); + } + public double determineCorrectExtendedBolusSize(double bolusAmount) { if (bolusAmount == 0.0d) { return bolusAmount; @@ -371,7 +402,7 @@ public double determineCorrectExtendedBolusSize(double bolusAmount) { bolusAmount = extendedBolusSettings.getMaxDose(); } - return Math.round(bolusAmount / bolusStepSize) * bolusStepSize; + return Round.roundTo(bolusAmount, bolusStepSize); } @@ -393,7 +424,7 @@ public double determineCorrectBasalSize(double basalAmount) { if (basalAmount > getTbrSettings().getMaxDose()) basalAmount = getTbrSettings().getMaxDose().doubleValue(); - return Math.round(basalAmount / basalStepSize) * basalStepSize; + return Round.roundTo(basalAmount, basalStepSize); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RefreshableInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RefreshableInterface.java new file mode 100644 index 00000000000..df847501527 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RefreshableInterface.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.common.dialog; + +/** + * Created by andy on 5/19/18. + */ + +public interface RefreshableInterface { + + void refreshData(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEScanActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEScanActivity.java new file mode 100644 index 00000000000..ae0a0d34f75 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/dialog/RileyLinkBLEScanActivity.java @@ -0,0 +1,419 @@ +package info.nightscout.androidaps.plugins.pump.common.dialog; + +import android.Manifest; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.ParcelUuid; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +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.activities.NoSplashAppCompatActivity; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; +import info.nightscout.androidaps.plugins.pump.common.utils.LocationHelper; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +// IMPORTANT: This activity needs to be called from RileyLinkSelectPreference (see pref_medtronic.xml as example) +public class RileyLinkBLEScanActivity extends NoSplashAppCompatActivity { + + private static final Logger LOG = LoggerFactory.getLogger(RileyLinkBLEScanActivity.class); + + private static final int PERMISSION_REQUEST_COARSE_LOCATION = 30241; // arbitrary. + private static final int REQUEST_ENABLE_BT = 30242; // arbitrary + + private static String TAG = "RileyLinkBLEScanActivity"; + + // Stops scanning after 30 seconds. + private static final long SCAN_PERIOD = 30000; + public boolean mScanning; + public ScanSettings settings; + public List filters; + public ListView listBTScan; + public Toolbar toolbarBTScan; + public Context mContext = this; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothLeScanner mLEScanner; + private LeDeviceListAdapter mLeDeviceListAdapter; + private Handler mHandler; + + private String actionTitleStart, actionTitleStop; + private MenuItem menuItem; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.rileylink_scan_activity); + + // Initializes Bluetooth adapter. + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mHandler = new Handler(); + + mLeDeviceListAdapter = new LeDeviceListAdapter(); + listBTScan = (ListView) findViewById(R.id.rileylink_listBTScan); + listBTScan.setAdapter(mLeDeviceListAdapter); + listBTScan.setOnItemClickListener((parent, view, position, id) -> { + + // stop scanning if still active + if (mScanning) { + mScanning = false; + mLEScanner.stopScan(mScanCallback2); + } + + TextView textview = (TextView) view.findViewById(R.id.rileylink_device_address); + String bleAddress = textview.getText().toString(); + + SP.putString(RileyLinkConst.Prefs.RileyLinkAddress, bleAddress); + + RileyLinkUtil.getRileyLinkSelectPreference().setSummary(bleAddress); + + MedtronicPumpStatus pumpStatus = MedtronicUtil.getPumpStatus(); + pumpStatus.verifyConfiguration(); // force reloading of address + + RxBus.INSTANCE.send(new EventMedtronicPumpConfigurationChanged()); + + finish(); + }); + + toolbarBTScan = (Toolbar) findViewById(R.id.rileylink_toolbarBTScan); + toolbarBTScan.setTitle(R.string.rileylink_scanner_title); + setSupportActionBar(toolbarBTScan); + + prepareForScanning(); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_rileylink_ble_scan, menu); + + actionTitleStart = MainApp.gs(R.string.rileylink_scanner_scan_scan); + actionTitleStop = MainApp.gs(R.string.rileylink_scanner_scan_stop); + + menuItem = menu.getItem(0); + + menuItem.setTitle(actionTitleStart); + + return true; + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.rileylink_miScan: { + scanLeDevice(menuItem.getTitle().equals(actionTitleStart)); + return true; + } + + default: + return super.onOptionsItemSelected(item); + } + } + + + public void prepareForScanning() { + // https://developer.android.com/training/permissions/requesting.html + // http://developer.radiusnetworks.com/2015/09/29/is-your-beacon-app-ready-for-android-6.html + if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { + Toast.makeText(this, R.string.rileylink_scanner_ble_not_supported, Toast.LENGTH_SHORT).show(); + } else { + // Use this check to determine whether BLE is supported on the device. Then + // you can selectively disable BLE-related features. + if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + // your code that requires permission + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, + PERMISSION_REQUEST_COARSE_LOCATION); + } + + // Ensures Bluetooth is available on the device and it is enabled. If not, + // displays a dialog requesting user permission to enable Bluetooth. + if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) { + Toast.makeText(this, R.string.rileylink_scanner_ble_not_enabled, Toast.LENGTH_SHORT).show(); + } else { + + // Will request that GPS be enabled for devices running Marshmallow or newer. + if (!LocationHelper.isLocationEnabled(this)) { + LocationHelper.requestLocationForBluetooth(this); + } + + mLEScanner = mBluetoothAdapter.getBluetoothLeScanner(); + settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); + filters = Arrays.asList(new ScanFilter.Builder().setServiceUuid( + ParcelUuid.fromString(GattAttributes.SERVICE_RADIO)).build()); + + } + } + + // disable currently selected RL, so that we can discover it + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnect); + } + + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_ENABLE_BT) { + if (resultCode == RESULT_OK) { + // User allowed Bluetooth to turn on + } else if (resultCode == RESULT_CANCELED) { + // Error, or user said "NO" + finish(); + } + } + } + + private ScanCallback mScanCallback2 = new ScanCallback() { + + @Override + public void onScanResult(int callbackType, final ScanResult scanRecord) { + + Log.d(TAG, scanRecord.toString()); + + runOnUiThread(() -> { + if (addDevice(scanRecord)) + mLeDeviceListAdapter.notifyDataSetChanged(); + }); + } + + + @Override + public void onBatchScanResults(final List results) { + + runOnUiThread(() -> { + + boolean added = false; + + for (ScanResult result : results) { + if (addDevice(result)) + added = true; + } + + if (added) + mLeDeviceListAdapter.notifyDataSetChanged(); + }); + } + + + private boolean addDevice(ScanResult result) { + + BluetoothDevice device = result.getDevice(); + + List serviceUuids = result.getScanRecord().getServiceUuids(); + + if (serviceUuids == null || serviceUuids.size() == 0) { + Log.v(TAG, "Device " + device.getAddress() + " has no serviceUuids (Not RileyLink)."); + } else if (serviceUuids.size() > 1) { + Log.v(TAG, "Device " + device.getAddress() + " has too many serviceUuids (Not RileyLink)."); + } else { + + String uuid = serviceUuids.get(0).getUuid().toString().toLowerCase(); + + if (uuid.equals(GattAttributes.SERVICE_RADIO)) { + Log.i(TAG, "Found RileyLink with address: " + device.getAddress()); + mLeDeviceListAdapter.addDevice(result); + return true; + } else { + Log.v(TAG, "Device " + device.getAddress() + " has incorrect uuid (Not RileyLink)."); + } + } + + return false; + } + + + private String getDeviceDebug(BluetoothDevice device) { + return "BluetoothDevice [name=" + device.getName() + ", address=" + device.getAddress() + // + ", type=" + device.getType(); // + ", alias=" + device.getAlias(); + } + + + @Override + public void onScanFailed(int errorCode) { + Log.e("Scan Failed", "Error Code: " + errorCode); + Toast.makeText(mContext, MainApp.gs(R.string.rileylink_scanner_scanning_error, errorCode), + Toast.LENGTH_LONG).show(); + } + + }; + + + private void scanLeDevice(final boolean enable) { + + if (mLEScanner == null) + return; + + if (enable) { + + mLeDeviceListAdapter.clear(); + mLeDeviceListAdapter.notifyDataSetChanged(); + + // Stops scanning after a pre-defined scan period. + mHandler.postDelayed(() -> { + + if (mScanning) { + mScanning = false; + mLEScanner.stopScan(mScanCallback2); + LOG.debug("scanLeDevice: Scanning Stop"); + Toast.makeText(mContext, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show(); + menuItem.setTitle(actionTitleStart); + } + }, SCAN_PERIOD); + + mScanning = true; + mLEScanner.startScan(filters, settings, mScanCallback2); + LOG.debug("scanLeDevice: Scanning Start"); + Toast.makeText(this, R.string.rileylink_scanner_scanning, Toast.LENGTH_SHORT).show(); + + menuItem.setTitle(actionTitleStop); + + } else { + if (mScanning) { + mScanning = false; + mLEScanner.stopScan(mScanCallback2); + + LOG.debug("scanLeDevice: Scanning Stop"); + Toast.makeText(this, R.string.rileylink_scanner_scanning_finished, Toast.LENGTH_SHORT).show(); + + menuItem.setTitle(actionTitleStart); + } + } + } + + private class LeDeviceListAdapter extends BaseAdapter { + + private ArrayList mLeDevices; + private Map rileyLinkDevices; + private LayoutInflater mInflator; + String currentlySelectedAddress; + + + public LeDeviceListAdapter() { + super(); + mLeDevices = new ArrayList<>(); + rileyLinkDevices = new HashMap<>(); + mInflator = RileyLinkBLEScanActivity.this.getLayoutInflater(); + currentlySelectedAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, ""); + } + + + public void addDevice(ScanResult result) { + + if (!mLeDevices.contains(result.getDevice())) { + mLeDevices.add(result.getDevice()); + } + rileyLinkDevices.put(result.getDevice(), result.getRssi()); + notifyDataSetChanged(); + } + + + public void clear() { + mLeDevices.clear(); + rileyLinkDevices.clear(); + notifyDataSetChanged(); + } + + + @Override + public int getCount() { + return mLeDevices.size(); + } + + + @Override + public Object getItem(int i) { + return mLeDevices.get(i); + } + + + @Override + public long getItemId(int i) { + return i; + } + + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + + ViewHolder viewHolder; + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.rileylink_scan_item, null); + viewHolder = new ViewHolder(); + viewHolder.deviceAddress = (TextView) view.findViewById(R.id.rileylink_device_address); + viewHolder.deviceName = (TextView) view.findViewById(R.id.rileylink_device_name); + view.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) view.getTag(); + } + + BluetoothDevice device = mLeDevices.get(i); + String deviceName = device.getName(); + + if (StringUtils.isBlank(deviceName)) { + deviceName = "RileyLink"; + } + + deviceName += " [" + rileyLinkDevices.get(device).intValue() + "]"; + + if (currentlySelectedAddress.equals(device.getAddress())) { + // viewHolder.deviceName.setTextColor(getColor(R.color.secondary_text_light)); + // viewHolder.deviceAddress.setTextColor(getColor(R.color.secondary_text_light)); + deviceName += " (" + getResources().getString(R.string.rileylink_scanner_selected_device) + ")"; + } + + viewHolder.deviceName.setText(deviceName); + viewHolder.deviceAddress.setText(device.getAddress()); + + return view; + } + + } + + static class ViewHolder { + + TextView deviceName; + TextView deviceAddress; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java new file mode 100644 index 00000000000..2e49b88c208 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkCommunicationManager.java @@ -0,0 +1,462 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyScanResults; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.FrequencyTrial; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.utils.SP; + +/** + * This is abstract class for RileyLink Communication, this one needs to be extended by specific "Pump" class. + *

+ * Created by andy on 5/10/18. + */ +public abstract class RileyLinkCommunicationManager { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static final int SCAN_TIMEOUT = 1500; + private static final int ALLOWED_PUMP_UNREACHABLE = 10 * 60 * 1000; // 10 minutes + + protected final RFSpy rfspy; + protected int receiverDeviceAwakeForMinutes = 1; // override this in constructor of specific implementation + protected String receiverDeviceID; // String representation of receiver device (ex. Pump (xxxxxx) or Pod (yyyyyy)) + protected long lastGoodReceiverCommunicationTime = 0; + protected PumpStatus pumpStatus; + protected RileyLinkServiceData rileyLinkServiceData; + private long nextWakeUpRequired = 0L; + + // internal flag + private boolean showPumpMessages = true; + private int timeoutCount = 0; + + + public RileyLinkCommunicationManager(RFSpy rfspy) { + this.rfspy = rfspy; + this.rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData(); + RileyLinkUtil.setRileyLinkCommunicationManager(this); + + configurePumpSpecificSettings(); + } + + + protected abstract void configurePumpSpecificSettings(); + + + // All pump communications go through this function. + public E sendAndListen(RLMessage msg, int timeout_ms, Class clazz) + throws RileyLinkCommunicationException { + return sendAndListen(msg, timeout_ms, null, clazz); + } + + public E sendAndListen(RLMessage msg, int timeout_ms, Integer extendPreamble_ms, Class clazz) + throws RileyLinkCommunicationException { + return sendAndListen(msg, timeout_ms, 0, extendPreamble_ms, clazz); + } + + // For backward compatibility + public E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, Integer extendPreamble_ms, Class clazz) + throws RileyLinkCommunicationException { + return sendAndListen(msg, timeout_ms, repeatCount, 0, extendPreamble_ms, clazz); + } + + public E sendAndListen(RLMessage msg, int timeout_ms, int repeatCount, int retryCount, Integer extendPreamble_ms, Class clazz) + throws RileyLinkCommunicationException { + + if (showPumpMessages) { + if (isLogEnabled()) + LOG.info("Sent:" + ByteUtil.shortHexString(msg.getTxData())); + } + + RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(msg.getTxData()), + (byte)0, (byte)repeatCount, (byte)0, (byte)0, timeout_ms, (byte)retryCount, extendPreamble_ms); + + RadioResponse radioResponse = rfSpyResponse.getRadioResponse(); + E response = createResponseMessage(radioResponse.getPayload(), clazz); + + if (response.isValid()) { + // Mark this as the last time we heard from the pump. + rememberLastGoodDeviceCommunicationTime(); + } else { + LOG.warn("isDeviceReachable. Response is invalid ! [interrupted={}, timeout={}, unknownCommand={}, invalidParam={}]", rfSpyResponse.wasInterrupted(), + rfSpyResponse.wasTimeout(), rfSpyResponse.isUnknownCommand(), rfSpyResponse.isInvalidParam()); + + if (rfSpyResponse.wasTimeout()) { + if (hasTunning()) { + timeoutCount++; + + long diff = System.currentTimeMillis() - pumpStatus.lastConnection; + + if (diff > ALLOWED_PUMP_UNREACHABLE) { + LOG.warn("We reached max time that Pump can be unreachable. Starting Tuning."); + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + timeoutCount = 0; + } + } + + throw new RileyLinkCommunicationException(RileyLinkBLEError.Timeout); + } else if (rfSpyResponse.wasInterrupted()) { + throw new RileyLinkCommunicationException(RileyLinkBLEError.Interrupted); + } + } + + if (showPumpMessages) { + if (isLogEnabled()) + LOG.info("Received:" + ByteUtil.shortHexString(rfSpyResponse.getRadioResponse().getPayload())); + } + + return response; + } + + + public abstract E createResponseMessage(byte[] payload, Class clazz); + + + public void wakeUp(boolean force) { + wakeUp(receiverDeviceAwakeForMinutes, force); + } + + + public int getNotConnectedCount() { + return rfspy != null ? rfspy.notConnectedCount : 0; + } + + public boolean hasTunning() { + return true; + } + + + // FIXME change wakeup + // TODO we might need to fix this. Maybe make pump awake for shorter time (battery factor for pump) - Andy + public void wakeUp(int duration_minutes, boolean force) { + // If it has been longer than n minutes, do wakeup. Otherwise assume pump is still awake. + // **** FIXME: this wakeup doesn't seem to work well... must revisit + // receiverDeviceAwakeForMinutes = duration_minutes; + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp); + + if (force) + nextWakeUpRequired = 0L; + + if (System.currentTimeMillis() > nextWakeUpRequired) { + if (isLogEnabled()) + LOG.info("Waking pump..."); + + byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, + (byte) 0, (byte) 0, 25000, (byte) 0); + if (isLogEnabled()) + LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw())); + + // FIXME wakeUp successful !!!!!!!!!!!!!!!!!! + + nextWakeUpRequired = System.currentTimeMillis() + (receiverDeviceAwakeForMinutes * 60 * 1000); + } else { + if (isLogEnabled()) + LOG.trace("Last pump communication was recent, not waking pump."); + } + + // long lastGoodPlus = getLastGoodReceiverCommunicationTime() + (receiverDeviceAwakeForMinutes * 60 * 1000); + // + // if (System.currentTimeMillis() > lastGoodPlus || force) { + // LOG.info("Waking pump..."); + // + // byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.PowerOn); + // RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, (byte) + // 0, (byte) 0, 15000, (byte) 0); + // LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(resp.getRaw())); + // } else { + // LOG.trace("Last pump communication was recent, not waking pump."); + // } + } + + + public void setRadioFrequencyForPump(double freqMHz) { + rfspy.setBaseFrequency(freqMHz); + } + + + public double tuneForDevice() { + return scanForDevice(RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies()); + } + + + /** + * If user changes pump and one pump is running in US freq, and other in WW, then previously set frequency would be + * invalid, + * so we would need to retune. This checks that saved frequency is correct range. + * + * @param frequency + * @return + */ + public boolean isValidFrequency(double frequency) { + + double[] scanFrequencies = RileyLinkUtil.getRileyLinkTargetFrequency().getScanFrequencies(); + + if (scanFrequencies.length == 1) { + return RileyLinkUtil.isSame(scanFrequencies[0], frequency); + } else { + return (scanFrequencies[0] <= frequency && scanFrequencies[scanFrequencies.length - 1] >= frequency); + } + } + + + /** + * Do device connection, with wakeup + * + * @return + */ + public abstract boolean tryToConnectToDevice(); + + + public double scanForDevice(double[] frequencies) { + if (isLogEnabled()) + LOG.info("Scanning for receiver ({})", receiverDeviceID); + wakeUp(receiverDeviceAwakeForMinutes, false); + FrequencyScanResults results = new FrequencyScanResults(); + + for (int i = 0; i < frequencies.length; i++) { + int tries = 3; + FrequencyTrial trial = new FrequencyTrial(); + trial.frequencyMHz = frequencies[i]; + rfspy.setBaseFrequency(frequencies[i]); + + int sumRSSI = 0; + for (int j = 0; j < tries; j++) { + + byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); + RFSpyResponse resp = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 0, + (byte) 0, (byte) 0, 1250, (byte) 0); + if (resp.wasTimeout()) { + LOG.error("scanForPump: Failed to find pump at frequency {}", frequencies[i]); + } else if (resp.looksLikeRadioPacket()) { + RadioResponse radioResponse = new RadioResponse(); + + try { + + radioResponse.init(resp.getRaw()); + + if (radioResponse.isValid()) { + int rssi = calculateRssi(radioResponse.rssi); + sumRSSI += rssi; + trial.rssiList.add(rssi); + trial.successes++; + } else { + LOG.warn("Failed to parse radio response: " + ByteUtil.shortHexString(resp.getRaw())); + trial.rssiList.add(-99); + } + + } catch (RileyLinkCommunicationException rle) { + LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw())); + trial.rssiList.add(-99); + } + + } else { + LOG.error("scanForPump: raw response is " + ByteUtil.shortHexString(resp.getRaw())); + trial.rssiList.add(-99); + } + trial.tries++; + } + sumRSSI += -99.0 * (trial.tries - trial.successes); + trial.averageRSSI2 = (double) (sumRSSI) / (double) (trial.tries); + + trial.calculateAverage(); + + results.trials.add(trial); + } + + results.dateTime = System.currentTimeMillis(); + + StringBuilder stringBuilder = new StringBuilder("Scan results:\n"); + + for (int k = 0; k < results.trials.size(); k++) { + FrequencyTrial one = results.trials.get(k); + + stringBuilder.append(String.format("Scan Result[%s]: Freq=%s, avg RSSI = %s\n", "" + k, "" + + one.frequencyMHz, "" + one.averageRSSI + ", RSSIs =" + one.rssiList)); + } + + LOG.info(stringBuilder.toString()); + + results.sort(); // sorts in ascending order + + FrequencyTrial bestTrial = results.trials.get(results.trials.size() - 1); + results.bestFrequencyMHz = bestTrial.frequencyMHz; + if (bestTrial.successes > 0) { + rfspy.setBaseFrequency(results.bestFrequencyMHz); + if (isLogEnabled()) + LOG.debug("Best frequency found: " + results.bestFrequencyMHz); + return results.bestFrequencyMHz; + } else { + LOG.error("No pump response during scan."); + return 0.0; + } + } + + + private int calculateRssi(int rssiIn) { + int rssiOffset = 73; + int outRssi = 0; + if (rssiIn >= 128) { + outRssi = ((rssiIn - 256) / 2) - rssiOffset; + } else { + outRssi = (rssiIn / 2) - rssiOffset; + } + + return outRssi; + } + + + public abstract byte[] createPumpMessageContent(RLMessageType type); + + + private int tune_tryFrequency(double freqMHz) { + rfspy.setBaseFrequency(freqMHz); + // RLMessage msg = makeRLMessage(RLMessageType.ReadSimpleData); + byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); + RadioPacket pkt = new RadioPacket(pumpMsgContent); + RFSpyResponse resp = rfspy.transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, SCAN_TIMEOUT, (byte) 0); + if (resp.wasTimeout()) { + LOG.warn("tune_tryFrequency: no pump response at frequency {}", freqMHz); + } else if (resp.looksLikeRadioPacket()) { + RadioResponse radioResponse = new RadioResponse(); + try { + radioResponse.init(resp.getRaw()); + + if (radioResponse.isValid()) { + LOG.warn("tune_tryFrequency: saw response level {} at frequency {}", radioResponse.rssi, freqMHz); + return calculateRssi(radioResponse.rssi); + } else { + LOG.warn("tune_tryFrequency: invalid radio response:" + + ByteUtil.shortHexString(radioResponse.getPayload())); + } + + } catch (RileyLinkCommunicationException e) { + LOG.warn("Failed to decode radio response: " + ByteUtil.shortHexString(resp.getRaw())); + } + } + + return 0; + } + + + public double quickTuneForPump(double startFrequencyMHz) { + double betterFrequency = startFrequencyMHz; + double stepsize = 0.05; + for (int tries = 0; tries < 4; tries++) { + double evenBetterFrequency = quickTunePumpStep(betterFrequency, stepsize); + if (evenBetterFrequency == 0.0) { + // could not see the pump at all. + // Try again at larger step size + stepsize += 0.05; + } else { + if ((int) (evenBetterFrequency * 100) == (int) (betterFrequency * 100)) { + // value did not change, so we're done. + break; + } + betterFrequency = evenBetterFrequency; // and go again. + } + } + if (betterFrequency == 0.0) { + // we've failed... caller should try a full scan for pump + if (isLogEnabled()) + LOG.error("quickTuneForPump: failed to find pump"); + } else { + rfspy.setBaseFrequency(betterFrequency); + if (betterFrequency != startFrequencyMHz) { + if (isLogEnabled()) + LOG.info("quickTuneForPump: new frequency is {}MHz", betterFrequency); + } else { + if (isLogEnabled()) + LOG.info("quickTuneForPump: pump frequency is the same: {}MHz", startFrequencyMHz); + } + } + return betterFrequency; + } + + + private double quickTunePumpStep(double startFrequencyMHz, double stepSizeMHz) { + if (isLogEnabled()) + LOG.info("Doing quick radio tune for receiver ({})", receiverDeviceID); + wakeUp(false); + int startRssi = tune_tryFrequency(startFrequencyMHz); + double lowerFrequency = startFrequencyMHz - stepSizeMHz; + int lowerRssi = tune_tryFrequency(lowerFrequency); + double higherFrequency = startFrequencyMHz + stepSizeMHz; + int higherRssi = tune_tryFrequency(higherFrequency); + + if ((higherRssi == 0.0) && (lowerRssi == 0.0) && (startRssi == 0.0)) { + // we can't see the pump at all... + return 0.0; + } + if (higherRssi > startRssi) { + // need to move higher + return higherFrequency; + } else if (lowerRssi > startRssi) { + // need to move lower. + return lowerFrequency; + } + return startFrequencyMHz; + } + + + protected void rememberLastGoodDeviceCommunicationTime() { + lastGoodReceiverCommunicationTime = System.currentTimeMillis(); + + SP.putLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, lastGoodReceiverCommunicationTime); + if(pumpStatus != null) { + pumpStatus.setLastCommunicationToNow(); + } + } + + + private long getLastGoodReceiverCommunicationTime() { + // If we have a value of zero, we need to load from prefs. + if (lastGoodReceiverCommunicationTime == 0L) { + lastGoodReceiverCommunicationTime = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + // Might still be zero, but that's fine. + } + double minutesAgo = (System.currentTimeMillis() - lastGoodReceiverCommunicationTime) / (1000.0 * 60.0); + if (isLogEnabled()) + LOG.trace("Last good pump communication was " + minutesAgo + " minutes ago."); + return lastGoodReceiverCommunicationTime; + } + + + public PumpStatus getPumpStatus() { + return pumpStatus; + } + + + public void clearNotConnectedCount() { + if (rfspy != null) { + rfspy.notConnectedCount = 0; + } + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + public void setPumpStatus(PumpStatus pumpStatus) { + this.pumpStatus = pumpStatus; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkConst.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkConst.java new file mode 100644 index 00000000000..bff2a392403 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkConst.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 16/05/2018. + */ + +public class RileyLinkConst { + + static final String Prefix = "AAPS.RileyLink."; + + public class Intents { + + public static final String RileyLinkReady = Prefix + "RileyLink_Ready"; + public static final String RileyLinkGattFailed = Prefix + "RileyLink_Gatt_Failed"; + + public static final String BluetoothConnected = Prefix + "Bluetooth_Connected"; + public static final String BluetoothReconnected = Prefix + "Bluetooth_Reconnected"; + public static final String BluetoothDisconnected = Prefix + "Bluetooth_Disconnected"; + public static final String RileyLinkDisconnected = Prefix + "RileyLink_Disconnected"; + + public static final String RileyLinkNewAddressSet = Prefix + "NewAddressSet"; + + public static final String INTENT_NEW_rileylinkAddressKey = Prefix + "INTENT_NEW_rileylinkAddressKey"; + public static final String INTENT_NEW_pumpIDKey = Prefix + "INTENT_NEW_pumpIDKey"; + public static final String RileyLinkDisconnect = Prefix + "RileyLink_Disconnect"; + } + + public class Prefs { + + //public static final String PrefPrefix = "pref_rileylink_"; + //public static final String RileyLinkAddress = PrefPrefix + "mac_address"; // pref_rileylink_mac_address + public static final int RileyLinkAddress = R.string.key_rileylink_mac_address; + public static final String LastGoodDeviceCommunicationTime = Prefix + "lastGoodDeviceCommunicationTime"; + public static final String LastGoodDeviceFrequency = Prefix + "LastGoodDeviceFrequency"; + } + + public class IPC { + + // needs to br renamed (and maybe removed) + public static final String MSG_PUMP_quickTune = Prefix + "MSG_PUMP_quickTune"; + public static final String MSG_PUMP_tunePump = Prefix + "MSG_PUMP_tunePump"; + public static final String MSG_PUMP_fetchHistory = Prefix + "MSG_PUMP_fetchHistory"; + public static final String MSG_PUMP_fetchSavedHistory = Prefix + "MSG_PUMP_fetchSavedHistory"; + + public static final String MSG_ServiceCommand = Prefix + "MSG_ServiceCommand"; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkUtil.java new file mode 100644 index 00000000000..3c7ba730a73 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/RileyLinkUtil.java @@ -0,0 +1,328 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink; + +import android.content.Context; +import android.content.Intent; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6b; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding.Encoding4b6bGeoff; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.BleAdvertisedData; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceNotification; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask; +import info.nightscout.androidaps.plugins.pump.common.ui.RileyLinkSelectPreference; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange; + +/** + * Created by andy on 17/05/2018. + */ + +public class RileyLinkUtil { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + protected static List historyRileyLink = new ArrayList<>(); + protected static RileyLinkCommunicationManager rileyLinkCommunicationManager; + static ServiceTask currentTask; + private static Context context; + private static RileyLinkBLE rileyLinkBLE; + private static RileyLinkServiceData rileyLinkServiceData; + private static RileyLinkService rileyLinkService; + private static RileyLinkTargetFrequency rileyLinkTargetFrequency; + + private static RileyLinkTargetDevice targetDevice; + private static RileyLinkEncodingType encoding; + private static RileyLinkSelectPreference rileyLinkSelectPreference; + private static Encoding4b6b encoding4b6b; + private static RileyLinkFirmwareVersion firmwareVersion; + + + public static void setContext(Context contextIn) { + RileyLinkUtil.context = contextIn; + } + + + public static RileyLinkEncodingType getEncoding() { + return encoding; + } + + + public static void setEncoding(RileyLinkEncodingType encoding) { + RileyLinkUtil.encoding = encoding; + + if (encoding == RileyLinkEncodingType.FourByteSixByteLocal) { + RileyLinkUtil.encoding4b6b = new Encoding4b6bGeoff(); + } + } + + + public static void sendBroadcastMessage(String message) { + if (context != null) { + Intent intent = new Intent(message); + LocalBroadcastManager.getInstance(RileyLinkUtil.context).sendBroadcast(intent); + } + } + + + public static void setServiceState(RileyLinkServiceState newState) { + setServiceState(newState, null); + } + + + public static RileyLinkError getError() { + if (RileyLinkUtil.rileyLinkServiceData != null) + return RileyLinkUtil.rileyLinkServiceData.errorCode; + else + return null; + } + + + public static RileyLinkServiceState getServiceState() { + return workWithServiceState(null, null, false); + } + + + public static void setServiceState(RileyLinkServiceState newState, RileyLinkError errorCode) { + workWithServiceState(newState, errorCode, true); + } + + + private static synchronized RileyLinkServiceState workWithServiceState(RileyLinkServiceState newState, + RileyLinkError errorCode, boolean set) { + + if (set) { + + RileyLinkUtil.rileyLinkServiceData.serviceState = newState; + RileyLinkUtil.rileyLinkServiceData.errorCode = errorCode; + + if (L.isEnabled(L.PUMP)) + LOG.info("RileyLink State Changed: {} {}", newState, errorCode == null ? "" : " - Error State: " + + errorCode.name()); + + RileyLinkUtil.historyRileyLink.add(new RLHistoryItem(RileyLinkUtil.rileyLinkServiceData.serviceState, + RileyLinkUtil.rileyLinkServiceData.errorCode, targetDevice)); + RxBus.INSTANCE.send(new EventMedtronicDeviceStatusChange(newState, errorCode)); + return null; + + } else { + return (RileyLinkUtil.rileyLinkServiceData == null || RileyLinkUtil.rileyLinkServiceData.serviceState == null) ? // + RileyLinkServiceState.NotStarted + : RileyLinkUtil.rileyLinkServiceData.serviceState; + } + + } + + + public static RileyLinkBLE getRileyLinkBLE() { + return RileyLinkUtil.rileyLinkBLE; + } + + + public static void setRileyLinkBLE(RileyLinkBLE rileyLinkBLEIn) { + RileyLinkUtil.rileyLinkBLE = rileyLinkBLEIn; + } + + + public static RileyLinkServiceData getRileyLinkServiceData() { + return RileyLinkUtil.rileyLinkServiceData; + } + + + public static void setRileyLinkServiceData(RileyLinkServiceData rileyLinkServiceData) { + RileyLinkUtil.rileyLinkServiceData = rileyLinkServiceData; + } + + + public static boolean hasPumpBeenTunned() { + return RileyLinkUtil.rileyLinkServiceData.tuneUpDone; + } + + + public static RileyLinkService getRileyLinkService() { + return RileyLinkUtil.rileyLinkService; + } + + + public static void setRileyLinkService(RileyLinkService rileyLinkService) { + RileyLinkUtil.rileyLinkService = rileyLinkService; + } + + + public static RileyLinkCommunicationManager getRileyLinkCommunicationManager() { + return RileyLinkUtil.rileyLinkCommunicationManager; + } + + + public static void setRileyLinkCommunicationManager(RileyLinkCommunicationManager rileyLinkCommunicationManager) { + RileyLinkUtil.rileyLinkCommunicationManager = rileyLinkCommunicationManager; + } + + + public static boolean sendNotification(ServiceNotification notification, Integer clientHashcode) { + return false; + } + + + // FIXME remove ? + public static void setCurrentTask(ServiceTask task) { + if (currentTask == null) { + currentTask = task; + } else { + //LOG.error("setCurrentTask: Cannot replace current task"); + } + } + + + public static void finishCurrentTask(ServiceTask task) { + if (task != currentTask) { + //LOG.error("finishCurrentTask: task does not match"); + } + // hack to force deep copy of transport contents + ServiceTransport transport = task.getServiceTransport().clone(); + + if (transport.hasServiceResult()) { + sendServiceTransportResponse(transport, transport.getServiceResult()); + } + currentTask = null; + } + + + public static void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) { + // get the key (hashcode) of the client who requested this + Integer clientHashcode = transport.getSenderHashcode(); + // make a new bundle to send as the message data + transport.setServiceResult(serviceResult); + // FIXME + // transport.setTransportType(RT2Const.IPC.MSG_ServiceResult); + // rileyLinkIPCConnection.sendTransport(transport, clientHashcode); + } + + + public static RileyLinkTargetFrequency getRileyLinkTargetFrequency() { + return RileyLinkUtil.rileyLinkTargetFrequency; + } + + + public static void setRileyLinkTargetFrequency(RileyLinkTargetFrequency rileyLinkTargetFrequency) { + RileyLinkUtil.rileyLinkTargetFrequency = rileyLinkTargetFrequency; + } + + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + + + @Deprecated + public static BleAdvertisedData parseAdertisedData(byte[] advertisedData) { + List uuids = new ArrayList(); + String name = null; + if (advertisedData == null) { + return new BleAdvertisedData(uuids, name); + } + + ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN); + while (buffer.remaining() > 2) { + byte length = buffer.get(); + if (length == 0) + break; + + byte type = buffer.get(); + switch (type) { + case 0x02: // Partial list of 16-bit UUIDs + case 0x03: // Complete list of 16-bit UUIDs + while (length >= 2) { + uuids + .add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", buffer.getShort()))); + length -= 2; + } + break; + case 0x06: // Partial list of 128-bit UUIDs + case 0x07: // Complete list of 128-bit UUIDs + while (length >= 16) { + long lsb = buffer.getLong(); + long msb = buffer.getLong(); + uuids.add(new UUID(msb, lsb)); + length -= 16; + } + break; + case 0x09: + byte[] nameBytes = new byte[length - 1]; + buffer.get(nameBytes); + name = new String(nameBytes, StandardCharsets.UTF_8); + break; + default: + buffer.position(buffer.position() + length - 1); + break; + } + } + return new BleAdvertisedData(uuids, name); + } + + + public static List getRileyLinkHistory() { + return historyRileyLink; + } + + + public static RileyLinkTargetDevice getTargetDevice() { + return targetDevice; + } + + + public static void setTargetDevice(RileyLinkTargetDevice targetDevice) { + RileyLinkUtil.targetDevice = targetDevice; + } + + + public static void setRileyLinkSelectPreference(RileyLinkSelectPreference rileyLinkSelectPreference) { + + RileyLinkUtil.rileyLinkSelectPreference = rileyLinkSelectPreference; + } + + + public static RileyLinkSelectPreference getRileyLinkSelectPreference() { + + return rileyLinkSelectPreference; + } + + + public static Encoding4b6b getEncoding4b6b() { + return RileyLinkUtil.encoding4b6b; + } + + + public static void setFirmwareVersion(RileyLinkFirmwareVersion firmwareVersion) { + RileyLinkUtil.firmwareVersion = firmwareVersion; + } + + + public static RileyLinkFirmwareVersion getFirmwareVersion() { + return firmwareVersion; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpy.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpy.java new file mode 100644 index 00000000000..1fb24821e13 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpy.java @@ -0,0 +1,438 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble; + +import android.os.SystemClock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.UUID; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.Reset; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SendAndListen; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetHardwareEncoding; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.SetPreamble; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.UpdateRegister; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RXFilterMode; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by geoff on 5/26/16. + */ +public class RFSpy { + + public static final long RILEYLINK_FREQ_XTAL = 24000000; + public static final int EXPECTED_MAX_BLUETOOTH_LATENCY_MS = 7500; // 1500 + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + public int notConnectedCount = 0; + private RileyLinkBLE rileyLinkBle; + private RFSpyReader reader; + private RileyLinkTargetFrequency selectedTargetFrequency; + private UUID radioServiceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO); + private UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA); + private UUID radioVersionUUID = UUID.fromString(GattAttributes.CHARA_RADIO_VERSION); + private UUID responseCountUUID = UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT); + private RileyLinkFirmwareVersion firmwareVersion; + private String bleVersion; // We don't use it so no need of sofisticated logic + Double currentFrequencyMHz; + + + public RFSpy(RileyLinkBLE rileyLinkBle) { + this.rileyLinkBle = rileyLinkBle; + reader = new RFSpyReader(rileyLinkBle); + } + + + public RileyLinkFirmwareVersion getRLVersionCached() { + return firmwareVersion; + } + + + public String getBLEVersionCached() { + return bleVersion; + } + + + // Call this after the RL services are discovered. + // Starts an async task to read when data is available + public void startReader() { + rileyLinkBle.registerRadioResponseCountNotification(new Runnable() { + + @Override + public void run() { + newDataIsAvailable(); + } + }); + reader.start(); + } + + + // Here should go generic RL initialisation + protocol adjustments depending on + // firmware version + public void initializeRileyLink() { + bleVersion = getVersion(); + firmwareVersion = getFirmwareVersion(); + RileyLinkUtil.setFirmwareVersion(firmwareVersion); + } + + + // Call this from the "response count" notification handler. + public void newDataIsAvailable() { + // pass the message to the reader (which should be internal to RFSpy) + reader.newDataIsAvailable(); + } + + + // This gets the version from the BLE113, not from the CC1110. + // I.e., this gets the version from the BLE interface, not from the radio. + public String getVersion() { + BLECommOperationResult result = rileyLinkBle.readCharacteristic_blocking(radioServiceUUID, radioVersionUUID); + if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) { + String version = StringUtil.fromBytes(result.value); + if (isLogEnabled()) + LOG.debug("BLE Version: " + version); + return version; + } else { + LOG.error("getVersion failed with code: " + result.resultCode); + return "(null)"; + } + } + + + public RileyLinkFirmwareVersion getFirmwareVersion() { + + if (isLogEnabled()) + LOG.debug("Firmware Version. Get Version - Start"); + + for (int i = 0; i < 5; i++) { + // We have to call raw version of communication to get firmware version + // So that we can adjust other commands accordingly afterwords + + byte[] getVersionRaw = getByteArray(RileyLinkCommandType.GetVersion.code); + byte[] response = writeToDataRaw(getVersionRaw, 5000); + + if (isLogEnabled()) + LOG.debug("Firmware Version. GetVersion [response={}]", ByteUtil.shortHexString(response)); + + if (response != null) { // && response[0] == (byte) 0xDD) { + + String versionString = StringUtil.fromBytes(response); + + RileyLinkFirmwareVersion version = RileyLinkFirmwareVersion.getByVersionString(StringUtil + .fromBytes(response)); + + if (isLogEnabled()) + LOG.trace("Firmware Version string: {}, resolved to {}.", versionString, version); + + if (version != RileyLinkFirmwareVersion.UnknownVersion) + return version; + + SystemClock.sleep(1000); + } + } + + LOG.error("Firmware Version can't be determined. Checking with BLE Version [{}].", bleVersion); + + if (bleVersion.contains(" 2.")) { + return RileyLinkFirmwareVersion.Version_2_0; + } + + return RileyLinkFirmwareVersion.UnknownVersion; + } + + + private byte[] writeToDataRaw(byte[] bytes, int responseTimeout_ms) { + SystemClock.sleep(100); + // FIXME drain read queue? + byte[] junkInBuffer = reader.poll(0); + + while (junkInBuffer != null) { + LOG.warn(ThreadUtil.sig() + "writeToData: draining read queue, found this: " + + ByteUtil.shortHexString(junkInBuffer)); + junkInBuffer = reader.poll(0); + } + + // prepend length, and send it. + byte[] prepended = ByteUtil.concat(new byte[]{(byte) (bytes.length)}, bytes); + + LOG.debug("writeToData (raw={})", ByteUtil.shortHexString(prepended)); + + BLECommOperationResult writeCheck = rileyLinkBle.writeCharacteristic_blocking(radioServiceUUID, radioDataUUID, + prepended); + if (writeCheck.resultCode != BLECommOperationResult.RESULT_SUCCESS) { + LOG.error("BLE Write operation failed, code=" + writeCheck.resultCode); + return null; // will be a null (invalid) response + } + SystemClock.sleep(100); + // Log.i(TAG,ThreadUtil.sig()+String.format(" writeToData:(timeout %d) %s",(responseTimeout_ms),ByteUtil.shortHexString(prepended))); + byte[] rawResponse = reader.poll(responseTimeout_ms); + return rawResponse; + + } + + + // The caller has to know how long the RFSpy will be busy with what was sent to it. + private RFSpyResponse writeToData(RileyLinkCommand command, int responseTimeout_ms) { + + byte[] bytes = command.getRaw(); + byte[] rawResponse = writeToDataRaw(bytes, responseTimeout_ms); + + RFSpyResponse resp = new RFSpyResponse(command, rawResponse); + if (rawResponse == null) { + LOG.error("writeToData: No response from RileyLink"); + notConnectedCount++; + } else { + if (resp.wasInterrupted()) { + LOG.error("writeToData: RileyLink was interrupted"); + } else if (resp.wasTimeout()) { + LOG.error("writeToData: RileyLink reports timeout"); + notConnectedCount++; + } else if (resp.isOK()) { + LOG.warn("writeToData: RileyLink reports OK"); + resetNotConnectedCount(); + } else { + if (resp.looksLikeRadioPacket()) { + // RadioResponse radioResp = resp.getRadioResponse(); + // byte[] responsePayload = radioResp.getPayload(); + if (isLogEnabled()) + LOG.trace("writeToData: received radio response. Will decode at upper level"); + resetNotConnectedCount(); + } + // Log.i(TAG, "writeToData: raw response is " + ByteUtil.shortHexString(rawResponse)); + } + } + return resp; + } + + + private void resetNotConnectedCount() { + this.notConnectedCount = 0; + } + + + private byte[] getByteArray(byte... input) { + return input; + } + + + private byte[] getCommandArray(RileyLinkCommandType command, byte[] body) { + int bodyLength = body == null ? 0 : body.length; + + byte[] output = new byte[bodyLength + 1]; + + output[0] = command.code; + + if (body != null) { + for (int i = 0; i < body.length; i++) { + output[i + 1] = body[i]; + } + } + + return output; + } + + + public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms, + byte listenChannel, int timeout_ms, byte retryCount) { + return transmitThenReceive(pkt, sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms, retryCount, null); + } + + + public RFSpyResponse transmitThenReceive(RadioPacket pkt, int timeout_ms) { + return transmitThenReceive(pkt, (byte) 0, (byte) 0, (byte) 0, (byte) 0, timeout_ms, (byte) 0); + } + + + public RFSpyResponse transmitThenReceive(RadioPacket pkt, byte sendChannel, byte repeatCount, byte delay_ms, + byte listenChannel, int timeout_ms, byte retryCount, Integer extendPreamble_ms) { + + int sendDelay = repeatCount * delay_ms; + int receiveDelay = timeout_ms * (retryCount + 1); + + SendAndListen command = new SendAndListen(sendChannel, repeatCount, delay_ms, listenChannel, timeout_ms, + retryCount, extendPreamble_ms, pkt); + + return writeToData(command, sendDelay + receiveDelay + EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + } + + + public RFSpyResponse updateRegister(CC111XRegister reg, int val) { + RFSpyResponse resp = writeToData(new UpdateRegister(reg, (byte) val), EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + return resp; + } + + + public void setBaseFrequency(double freqMHz) { + int value = (int) (freqMHz * 1000000 / ((double) (RILEYLINK_FREQ_XTAL) / Math.pow(2.0, 16.0))); + updateRegister(CC111XRegister.freq0, (byte) (value & 0xff)); + updateRegister(CC111XRegister.freq1, (byte) ((value >> 8) & 0xff)); + updateRegister(CC111XRegister.freq2, (byte) ((value >> 16) & 0xff)); + LOG.info("Set frequency to {} MHz", freqMHz); + + this.currentFrequencyMHz = freqMHz; + + configureRadioForRegion(RileyLinkUtil.getRileyLinkTargetFrequency()); + } + + + private void configureRadioForRegion(RileyLinkTargetFrequency frequency) { + + // we update registers only on first run, or if region changed + + switch (frequency) { + case Medtronic_WorldWide: { + // updateRegister(CC111X_MDMCFG4, (byte) 0x59); + setRXFilterMode(RXFilterMode.Wide); + // updateRegister(CC111X_MDMCFG3, (byte) 0x66); + // updateRegister(CC111X_MDMCFG2, (byte) 0x33); + updateRegister(CC111XRegister.mdmcfg1, 0x62); + updateRegister(CC111XRegister.mdmcfg0, 0x1A); + updateRegister(CC111XRegister.deviatn, 0x13); + setMedtronicEncoding(); + } + break; + + case Medtronic_US: { + // updateRegister(CC111X_MDMCFG4, (byte) 0x99); + setRXFilterMode(RXFilterMode.Narrow); + // updateRegister(CC111X_MDMCFG3, (byte) 0x66); + // updateRegister(CC111X_MDMCFG2, (byte) 0x33); + updateRegister(CC111XRegister.mdmcfg1, 0x61); + updateRegister(CC111XRegister.mdmcfg0, 0x7E); + updateRegister(CC111XRegister.deviatn, 0x15); + setMedtronicEncoding(); + } + break; + + case Omnipod: { + RFSpyResponse r = null; + // RL initialization for Omnipod is a copy/paste from OmniKit implementation. + // Last commit from original repository: 5c3beb4144 + // so if something is terribly wrong, please check git diff PodCommsSession.swift since that commit + r = updateRegister(CC111XRegister.pktctrl1, 0x20); + r = updateRegister(CC111XRegister.agcctrl0, 0x00); + r = updateRegister(CC111XRegister.fsctrl1, 0x06); + r = updateRegister(CC111XRegister.mdmcfg4, 0xCA); + r = updateRegister(CC111XRegister.mdmcfg3, 0xBC); + r = updateRegister(CC111XRegister.mdmcfg2, 0x06); + r = updateRegister(CC111XRegister.mdmcfg1, 0x70); + r = updateRegister(CC111XRegister.mdmcfg0, 0x11); + r = updateRegister(CC111XRegister.deviatn, 0x44); + r = updateRegister(CC111XRegister.mcsm0, 0x18); + r = updateRegister(CC111XRegister.foccfg, 0x17); + r = updateRegister(CC111XRegister.fscal3, 0xE9); + r = updateRegister(CC111XRegister.fscal2, 0x2A); + r = updateRegister(CC111XRegister.fscal1, 0x00); + r = updateRegister(CC111XRegister.fscal0, 0x1F); + + r = updateRegister(CC111XRegister.test1, 0x31); + r = updateRegister(CC111XRegister.test0, 0x09); + r = updateRegister(CC111XRegister.paTable0, 0x84); + r = updateRegister(CC111XRegister.sync1, 0xA5); + r = updateRegister(CC111XRegister.sync0, 0x5A); + + r = setRileyLinkEncoding(RileyLinkEncodingType.Manchester); + r = setPreamble(0x6665); + + } + break; + default: + LOG.warn("No region configuration for RfSpy and {}", frequency.name()); + break; + + } + + this.selectedTargetFrequency = frequency; + } + + + private void setMedtronicEncoding() { + RileyLinkEncodingType encoding = RileyLinkEncodingType.FourByteSixByteLocal; + + if (RileyLinkFirmwareVersion.isSameVersion(this.firmwareVersion, RileyLinkFirmwareVersion.Version2AndHigher)) { + if (SP.getString(MedtronicConst.Prefs.Encoding, "None").equals(MainApp.gs(R.string.key_medtronic_pump_encoding_4b6b_rileylink))) { + encoding = RileyLinkEncodingType.FourByteSixByteRileyLink; + } + } + + setRileyLinkEncoding(encoding); + + if (isLogEnabled()) + LOG.debug("Set Encoding for Medtronic: " + encoding.name()); + } + + + private RFSpyResponse setPreamble(int preamble) { + RFSpyResponse resp = null; + try { + resp = writeToData(new SetPreamble(preamble), EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + } catch (Exception e) { + e.toString(); + } + return resp; + } + + + public RFSpyResponse setRileyLinkEncoding(RileyLinkEncodingType encoding) { + RFSpyResponse resp = writeToData(new SetHardwareEncoding(encoding), EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + + if (resp.isOK()) { + reader.setRileyLinkEncodingType(encoding); + RileyLinkUtil.setEncoding(encoding); + } + + return resp; + } + + + private void setRXFilterMode(RXFilterMode mode) { + + byte drate_e = (byte) 0x9; // exponent of symbol rate (16kbps) + byte chanbw = mode.value; + + updateRegister(CC111XRegister.mdmcfg4, (byte) (chanbw | drate_e)); + } + + /** + * Reset RileyLink Configuration (set all updateRegisters) + */ + public void resetRileyLinkConfiguration() { + if (this.currentFrequencyMHz != null) + this.setBaseFrequency(this.currentFrequencyMHz); + } + + + public RFSpyResponse resetRileyLink() { + RFSpyResponse resp = null; + try { + resp = writeToData(new Reset(), EXPECTED_MAX_BLUETOOTH_LATENCY_MS); + if (isLogEnabled()) + LOG.debug("Reset command send, response: {}", resp); + } catch (Exception e) { + e.toString(); + } + return resp; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPBTCOMM); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpyReader.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpyReader.java new file mode 100644 index 00000000000..063cdb877bf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RFSpyReader.java @@ -0,0 +1,150 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble; + +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.os.AsyncTask; +import android.os.SystemClock; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil; + +/** + * Created by geoff on 5/26/16. + */ +public class RFSpyReader { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + private static AsyncTask readerTask; + private RileyLinkBLE rileyLinkBle; + private Semaphore waitForRadioData = new Semaphore(0, true); + private LinkedBlockingQueue mDataQueue = new LinkedBlockingQueue<>(); + private int acquireCount = 0; + private int releaseCount = 0; + private boolean stopAtNull = true; + + + public RFSpyReader(RileyLinkBLE rileyLinkBle) { + this.rileyLinkBle = rileyLinkBle; + } + + + public void init(RileyLinkBLE rileyLinkBLE) { + this.rileyLinkBle = rileyLinkBLE; + } + + + public void setRileyLinkBle(RileyLinkBLE rileyLinkBle) { + if (readerTask != null) { + readerTask.cancel(true); + } + this.rileyLinkBle = rileyLinkBle; + } + + public void setRileyLinkEncodingType(RileyLinkEncodingType encodingType) { + stopAtNull = !(encodingType == RileyLinkEncodingType.Manchester || // + encodingType == RileyLinkEncodingType.FourByteSixByteRileyLink); + } + + + // This timeout must be coordinated with the length of the RFSpy radio operation or Bad Things Happen. + public byte[] poll(int timeout_ms) { + if (isLogEnabled()) + LOG.trace(ThreadUtil.sig() + "Entering poll at t==" + SystemClock.uptimeMillis() + ", timeout is " + timeout_ms + + " mDataQueue size is " + mDataQueue.size()); + + if (mDataQueue.isEmpty()) + try { + // block until timeout or data available. + // returns null if timeout. + byte[] dataFromQueue = mDataQueue.poll(timeout_ms, TimeUnit.MILLISECONDS); + if (dataFromQueue != null) { + if (isLogEnabled()) + LOG.debug("Got data [" + ByteUtil.shortHexString(dataFromQueue) + "] at t==" + + SystemClock.uptimeMillis()); + } else { + if (isLogEnabled()) + LOG.debug("Got data [null] at t==" + SystemClock.uptimeMillis()); + } + return dataFromQueue; + } catch (InterruptedException e) { + LOG.error("poll: Interrupted waiting for data"); + } + return null; + } + + + // Call this from the "response count" notification handler. + public void newDataIsAvailable() { + releaseCount++; + + if (isLogEnabled()) + LOG.trace(ThreadUtil.sig() + "waitForRadioData released(count=" + releaseCount + ") at t=" + + SystemClock.uptimeMillis()); + waitForRadioData.release(); + } + + + public void start() { + readerTask = new AsyncTask() { + + @Override + protected Void doInBackground(Void... voids) { + UUID serviceUUID = UUID.fromString(GattAttributes.SERVICE_RADIO); + UUID radioDataUUID = UUID.fromString(GattAttributes.CHARA_RADIO_DATA); + BLECommOperationResult result; + while (true) { + try { + acquireCount++; + waitForRadioData.acquire(); + if (isLogEnabled()) + LOG.trace(ThreadUtil.sig() + "waitForRadioData acquired (count=" + acquireCount + ") at t=" + + SystemClock.uptimeMillis()); + SystemClock.sleep(100); + SystemClock.sleep(1); + result = rileyLinkBle.readCharacteristic_blocking(serviceUUID, radioDataUUID); + SystemClock.sleep(100); + + if (result.resultCode == BLECommOperationResult.RESULT_SUCCESS) { + if (stopAtNull) { + // only data up to the first null is valid + for (int i = 0; i < result.value.length; i++) { + if (result.value[i] == 0) { + result.value = ByteUtil.substring(result.value, 0, i); + break; + } + } + } + mDataQueue.add(result.value); + } else if (result.resultCode == BLECommOperationResult.RESULT_INTERRUPTED) { + LOG.error("Read operation was interrupted"); + } else if (result.resultCode == BLECommOperationResult.RESULT_TIMEOUT) { + LOG.error("Read operation on Radio Data timed out"); + } else if (result.resultCode == BLECommOperationResult.RESULT_BUSY) { + LOG.error("FAIL: RileyLinkBLE reports operation already in progress"); + } else if (result.resultCode == BLECommOperationResult.RESULT_NONE) { + LOG.error("FAIL: got invalid result code: " + result.resultCode); + } + } catch (InterruptedException e) { + LOG.error("Interrupted while waiting for data"); + } + } + } + }.execute(); + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPBTCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkBLE.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkBLE.java new file mode 100644 index 00000000000..aab5e35142e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkBLE.java @@ -0,0 +1,589 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.SystemClock; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Semaphore; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperation; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.BLECommOperationResult; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicReadOperation; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.CharacteristicWriteOperation; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations.DescriptorWriteOperation; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.ThreadUtil; + +/** + * Created by geoff on 5/26/16. + * Added: State handling, configuration of RF for different configuration ranges, connection handling + */ +public class RileyLinkBLE { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + + private final Context context; + public boolean gattDebugEnabled = true; + boolean manualDisconnect = false; + private BluetoothAdapter bluetoothAdapter; + private BluetoothGattCallback bluetoothGattCallback; + private BluetoothDevice rileyLinkDevice; + private BluetoothGatt bluetoothConnectionGatt = null; + private BLECommOperation mCurrentOperation; + private Semaphore gattOperationSema = new Semaphore(1, true); + private Runnable radioResponseCountNotified; + private boolean mIsConnected = false; + + + public RileyLinkBLE(final Context context) { + this.context = context; + this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (isLogEnabled()) + LOG.debug("BT Adapter: " + this.bluetoothAdapter); + bluetoothGattCallback = new BluetoothGattCallback() { + + @Override + public void onCharacteristicChanged(final BluetoothGatt gatt, + final BluetoothGattCharacteristic characteristic) { + super.onCharacteristicChanged(gatt, characteristic); + if (gattDebugEnabled && isLogEnabled()) { + LOG.trace(ThreadUtil.sig() + "onCharacteristicChanged " + + GattAttributes.lookup(characteristic.getUuid()) + " " + + ByteUtil.getHex(characteristic.getValue())); + if (characteristic.getUuid().equals(UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT))) { + LOG.debug("Response Count is " + ByteUtil.shortHexString(characteristic.getValue())); + } + } + if (radioResponseCountNotified != null) { + radioResponseCountNotified.run(); + } + } + + + @Override + public void onCharacteristicRead(final BluetoothGatt gatt, + final BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicRead(gatt, characteristic, status); + + final String statusMessage = getGattStatusMessage(status); + if (gattDebugEnabled && isLogEnabled()) { + LOG.trace(ThreadUtil.sig() + "onCharacteristicRead (" + + GattAttributes.lookup(characteristic.getUuid()) + ") " + statusMessage + ":" + + ByteUtil.getHex(characteristic.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue()); + } + + + @Override + public void onCharacteristicWrite(final BluetoothGatt gatt, + final BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicWrite(gatt, characteristic, status); + + final String uuidString = GattAttributes.lookup(characteristic.getUuid()); + if (gattDebugEnabled && isLogEnabled()) { + LOG.trace(ThreadUtil.sig() + "onCharacteristicWrite " + getGattStatusMessage(status) + " " + + uuidString + " " + ByteUtil.shortHexString(characteristic.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(characteristic.getUuid(), characteristic.getValue()); + } + + + @Override + public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { + super.onConnectionStateChange(gatt, status, newState); + + // https://github.com/NordicSemiconductor/puck-central-android/blob/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt/GattManager.java#L117 + if (status == 133) { + LOG.error("Got the status 133 bug, closing gatt"); + disconnect(); + SystemClock.sleep(500); + return; + } + + if (gattDebugEnabled) { + final String stateMessage; + if (newState == BluetoothProfile.STATE_CONNECTED) { + stateMessage = "CONNECTED"; + } else if (newState == BluetoothProfile.STATE_CONNECTING) { + stateMessage = "CONNECTING"; + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + stateMessage = "DISCONNECTED"; + } else if (newState == BluetoothProfile.STATE_DISCONNECTING) { + stateMessage = "DISCONNECTING"; + } else { + stateMessage = "UNKNOWN newState (" + newState + ")"; + } + + if (isLogEnabled()) + LOG.warn("onConnectionStateChange " + getGattStatusMessage(status) + " " + stateMessage); + } + + if (newState == BluetoothProfile.STATE_CONNECTED) { + if (status == BluetoothGatt.GATT_SUCCESS) { + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothConnected); + } else { + if (isLogEnabled()) + LOG.debug("BT State connected, GATT status {} ({})", status, getGattStatusMessage(status)); + } + + } else if ((newState == BluetoothProfile.STATE_CONNECTING) || // + (newState == BluetoothProfile.STATE_DISCONNECTING)) { + // LOG.debug("We are in {} state.", status == BluetoothProfile.STATE_CONNECTING ? "Connecting" : + // "Disconnecting"); + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkDisconnected); + if (manualDisconnect) + close(); + LOG.warn("RileyLink Disconnected."); + } else { + LOG.warn("Some other state: (status={},newState={})", status, newState); + } + } + + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorWrite(gatt, descriptor, status); + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onDescriptorWrite " + GattAttributes.lookup(descriptor.getUuid()) + " " + + getGattStatusMessage(status) + " written: " + ByteUtil.getHex(descriptor.getValue())); + } + mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue()); + } + + + @Override + public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorRead(gatt, descriptor, status); + mCurrentOperation.gattOperationCompletionCallback(descriptor.getUuid(), descriptor.getValue()); + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onDescriptorRead " + getGattStatusMessage(status) + " status " + descriptor); + } + } + + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + super.onMtuChanged(gatt, mtu, status); + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onMtuChanged " + mtu + " status " + status); + } + } + + + @Override + public void onReadRemoteRssi(final BluetoothGatt gatt, int rssi, int status) { + super.onReadRemoteRssi(gatt, rssi, status); + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onReadRemoteRssi " + getGattStatusMessage(status) + ": " + rssi); + } + } + + + @Override + public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { + super.onReliableWriteCompleted(gatt, status); + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onReliableWriteCompleted status " + status); + } + } + + + @Override + public void onServicesDiscovered(final BluetoothGatt gatt, int status) { + super.onServicesDiscovered(gatt, status); + + if (status == BluetoothGatt.GATT_SUCCESS) { + final List services = gatt.getServices(); + + boolean rileyLinkFound = false; + + for (BluetoothGattService service : services) { + final UUID uuidService = service.getUuid(); + + if (isAnyRileyLinkServiceFound(service)) { + rileyLinkFound = true; + } + + if (gattDebugEnabled) { + debugService(service, 0); + } + } + + if (gattDebugEnabled && isLogEnabled()) { + LOG.warn("onServicesDiscovered " + getGattStatusMessage(status)); + } + + LOG.info("Gatt device is RileyLink device: " + rileyLinkFound); + + if (rileyLinkFound) { + mIsConnected = true; + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkReady); + // RileyLinkUtil.sendNotification(new + // ServiceNotification(RileyLinkConst.Intents.RileyLinkReady), null); + } else { + mIsConnected = false; + RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkError, + RileyLinkError.DeviceIsNotRileyLink); + } + + } else { + if (isLogEnabled()) + LOG.debug("onServicesDiscovered " + getGattStatusMessage(status)); + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkGattFailed); + } + } + }; + } + + + private boolean isAnyRileyLinkServiceFound(BluetoothGattService service) { + + boolean found = false; + + found = GattAttributes.isRileyLink(service.getUuid()); + + if (found) { + return true; + } else { + List includedServices = service.getIncludedServices(); + + for (BluetoothGattService serviceI : includedServices) { + if (isAnyRileyLinkServiceFound(serviceI)) { + return true; + } + + } + } + + return false; + } + + + public BluetoothDevice getRileyLinkDevice() { + return this.rileyLinkDevice; + } + + + public void debugService(BluetoothGattService service, int indentCount) { + + String indentString = StringUtils.repeat(' ', indentCount); + + final UUID uuidService = service.getUuid(); + + if (gattDebugEnabled) { + final String uuidServiceString = uuidService.toString(); + + StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append(indentString); + stringBuilder.append(GattAttributes.lookup(uuidServiceString, "Unknown service")); + stringBuilder.append(" (" + uuidServiceString + ")"); + + for (BluetoothGattCharacteristic character : service.getCharacteristics()) { + final String uuidCharacteristicString = character.getUuid().toString(); + + stringBuilder.append("\n "); + stringBuilder.append(indentString); + stringBuilder.append(" - " + GattAttributes.lookup(uuidCharacteristicString, "Unknown Characteristic")); + stringBuilder.append(" (" + uuidCharacteristicString + ")"); + } + + stringBuilder.append("\n\n"); + + LOG.warn(stringBuilder.toString()); + + List includedServices = service.getIncludedServices(); + + for (BluetoothGattService serviceI : includedServices) { + debugService(serviceI, indentCount + 4); + } + } + } + + + public void registerRadioResponseCountNotification(Runnable notifier) { + radioResponseCountNotified = notifier; + } + + + public boolean isConnected() { + return mIsConnected; + } + + + public boolean discoverServices() { + + if (bluetoothConnectionGatt == null) { + // shouldn't happen, but if it does we exit + return false; + } + + if (bluetoothConnectionGatt.discoverServices()) { + if (isLogEnabled()) + LOG.warn("Starting to discover GATT Services."); + return true; + } else { + LOG.error("Cannot discover GATT Services."); + return false; + } + } + + + public boolean enableNotifications() { + BLECommOperationResult result = setNotification_blocking(UUID.fromString(GattAttributes.SERVICE_RADIO), // + UUID.fromString(GattAttributes.CHARA_RADIO_RESPONSE_COUNT)); + if (result.resultCode != BLECommOperationResult.RESULT_SUCCESS) { + LOG.error("Error setting response count notification"); + return false; + } + return true; + } + + + public void findRileyLink(String RileyLinkAddress) { + if (isLogEnabled()) + LOG.debug("RileyLink address: " + RileyLinkAddress); + // Must verify that this is a valid MAC, or crash. + + rileyLinkDevice = bluetoothAdapter.getRemoteDevice(RileyLinkAddress); + // if this succeeds, we get a connection state change callback? + + if (rileyLinkDevice!=null) { + connectGatt(); + } else { + LOG.error("RileyLink device not found with address: " + RileyLinkAddress); + } + } + + + // This function must be run on UI thread. + public void connectGatt() { + if (this.rileyLinkDevice==null) { + LOG.error("RileyLink device is null, can't do connectGatt."); + return; + } + + bluetoothConnectionGatt = rileyLinkDevice.connectGatt(context, true, bluetoothGattCallback); + // , BluetoothDevice.TRANSPORT_LE + if (bluetoothConnectionGatt == null) { + LOG.error("Failed to connect to Bluetooth Low Energy device at " + bluetoothAdapter.getAddress()); + } else { + if (gattDebugEnabled) { + if (isLogEnabled()) + LOG.debug("Gatt Connected."); + } + } + } + + + public void disconnect() { + mIsConnected = false; + if (isLogEnabled()) + LOG.warn("Closing GATT connection"); + // Close old conenction + if (bluetoothConnectionGatt != null) { + // Not sure if to disconnect or to close first.. + bluetoothConnectionGatt.disconnect(); + manualDisconnect = true; + } + } + + + public void close() { + if (bluetoothConnectionGatt != null) { + bluetoothConnectionGatt.close(); + bluetoothConnectionGatt = null; + } + } + + + public BLECommOperationResult setNotification_blocking(UUID serviceUUID, UUID charaUUID) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + LOG.error("setNotification_blocking: interrupted waiting for gattOperationSema"); + return rval; + } + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + if (bluetoothConnectionGatt.getService(serviceUUID) == null) { + // Catch if the service is not supported by the BLE device + rval.resultCode = BLECommOperationResult.RESULT_NONE; + LOG.error("BT Device not supported"); + // TODO: 11/07/2016 UI update for user + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID) + .getCharacteristic(charaUUID); + // Tell Android that we want the notifications + bluetoothConnectionGatt.setCharacteristicNotification(chara, true); + List list = chara.getDescriptors(); + if (gattDebugEnabled) { + for (int i = 0; i < list.size(); i++) { + if (isLogEnabled()) + LOG.debug("Found descriptor: " + list.get(i).toString()); + } + } + BluetoothGattDescriptor descr = list.get(0); + // Tell the remote device to send the notifications + mCurrentOperation = new DescriptorWriteOperation(bluetoothConnectionGatt, descr, + BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } + } else { + LOG.error("setNotification_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + + // call from main + public BLECommOperationResult writeCharacteristic_blocking(UUID serviceUUID, UUID charaUUID, byte[] value) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + rval.value = value; + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + LOG.error("writeCharacteristic_blocking: interrupted waiting for gattOperationSema"); + return rval; + } + + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + if (bluetoothConnectionGatt.getService(serviceUUID) == null) { + // Catch if the service is not supported by the BLE device + // GGW: Tue Jul 12 01:14:01 UTC 2016: This can also happen if the + // app that created the bluetoothConnectionGatt has been destroyed/created, + // e.g. when the user switches from portrait to landscape. + rval.resultCode = BLECommOperationResult.RESULT_NONE; + LOG.error("BT Device not supported"); + // TODO: 11/07/2016 UI update for user + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID) + .getCharacteristic(charaUUID); + mCurrentOperation = new CharacteristicWriteOperation(bluetoothConnectionGatt, chara, value); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } + } else { + LOG.error("writeCharacteristic_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + + public BLECommOperationResult readCharacteristic_blocking(UUID serviceUUID, UUID charaUUID) { + BLECommOperationResult rval = new BLECommOperationResult(); + if (bluetoothConnectionGatt != null) { + try { + gattOperationSema.acquire(); + SystemClock.sleep(1); // attempting to yield thread, to make sequence of events easier to follow + } catch (InterruptedException e) { + LOG.error("readCharacteristic_blocking: Interrupted waiting for gattOperationSema"); + return rval; + } + if (mCurrentOperation != null) { + rval.resultCode = BLECommOperationResult.RESULT_BUSY; + } else { + if (bluetoothConnectionGatt.getService(serviceUUID) == null) { + // Catch if the service is not supported by the BLE device + rval.resultCode = BLECommOperationResult.RESULT_NONE; + LOG.error("BT Device not supported"); + // TODO: 11/07/2016 UI update for user + } else { + BluetoothGattCharacteristic chara = bluetoothConnectionGatt.getService(serviceUUID).getCharacteristic( + charaUUID); + mCurrentOperation = new CharacteristicReadOperation(bluetoothConnectionGatt, chara); + mCurrentOperation.execute(this); + if (mCurrentOperation.timedOut) { + rval.resultCode = BLECommOperationResult.RESULT_TIMEOUT; + } else if (mCurrentOperation.interrupted) { + rval.resultCode = BLECommOperationResult.RESULT_INTERRUPTED; + } else { + rval.resultCode = BLECommOperationResult.RESULT_SUCCESS; + rval.value = mCurrentOperation.getValue(); + } + } + } + mCurrentOperation = null; + gattOperationSema.release(); + } else { + LOG.error("readCharacteristic_blocking: not configured!"); + rval.resultCode = BLECommOperationResult.RESULT_NOT_CONFIGURED; + } + return rval; + } + + + private String getGattStatusMessage(final int status) { + final String statusMessage; + if (status == BluetoothGatt.GATT_SUCCESS) { + statusMessage = "SUCCESS"; + } else if (status == BluetoothGatt.GATT_FAILURE) { + statusMessage = "FAILED"; + } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) { + statusMessage = "NOT PERMITTED"; + } else if (status == 133) { + statusMessage = "Found the strange 133 bug"; + } else { + statusMessage = "UNKNOWN (" + status + ")"; + } + + return statusMessage; + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPBTCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkCommunicationException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkCommunicationException.java new file mode 100644 index 00000000000..c27de0a7e67 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/RileyLinkCommunicationException.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; + +/** + * Created by andy on 11/23/18. + */ + +public class RileyLinkCommunicationException extends Exception { + String extendedErrorText; + private RileyLinkBLEError errorCode; + + public RileyLinkCommunicationException(RileyLinkBLEError errorCode, String extendedErrorText) { + super(errorCode.getDescription()); + + this.errorCode = errorCode; + this.extendedErrorText = extendedErrorText; + } + + + public RileyLinkCommunicationException(RileyLinkBLEError errorCode) { + super(errorCode.getDescription()); + + this.errorCode = errorCode; + // this.extendedErrorText = extendedErrorText; + } + + public RileyLinkBLEError getErrorCode() { + return errorCode; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/GetVersion.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/GetVersion.java new file mode 100644 index 00000000000..757be66aacc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/GetVersion.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; + +public class GetVersion extends RileyLinkCommand { + + public GetVersion() { + super(); + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.GetVersion; + } + + + @Override + public byte[] getRaw() { + return super.getRawSimple(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/Reset.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/Reset.java new file mode 100644 index 00000000000..bd556d66957 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/Reset.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; + +public class Reset extends RileyLinkCommand { + + public Reset() { + super(); + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.Reset; + } + + + @Override + public byte[] getRaw() { + return super.getRawSimple(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/ResetRadioConfig.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/ResetRadioConfig.java new file mode 100644 index 00000000000..c37d4da35ca --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/ResetRadioConfig.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; + +public class ResetRadioConfig extends RileyLinkCommand { + + public ResetRadioConfig() { + super(); + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.ResetRadioConfig; + } + + + @Override + public byte[] getRaw() { + return super.getRawSimple(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/RileyLinkCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/RileyLinkCommand.java new file mode 100644 index 00000000000..4eeffc64395 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/RileyLinkCommand.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; + +public abstract class RileyLinkCommand { + + public RileyLinkCommand() { + } + + + public abstract RileyLinkCommandType getCommandType(); + + + public abstract byte[] getRaw(); + + + protected byte[] getRawSimple() { + return getByteArray(getCommandType().code); + + } + + + protected byte[] getByteArray(byte... input) { + return input; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SendAndListen.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SendAndListen.java new file mode 100644 index 00000000000..900275a05fa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SendAndListen.java @@ -0,0 +1,96 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +public class SendAndListen extends RileyLinkCommand { + + private byte sendChannel; + private byte repeatCount; + private int delayBetweenPackets_ms; + private byte listenChannel; + private int timeout_ms; + private byte retryCount; + private Integer preambleExtension_ms; + private RadioPacket packetToSend; + + + public SendAndListen(byte sendChannel, byte repeatCount, byte delayBetweenPackets_ms, byte listenChannel, + int timeout_ms, byte retryCount, RadioPacket packetToSend + + ) { + this(sendChannel, repeatCount, delayBetweenPackets_ms, listenChannel, timeout_ms, retryCount, null, + packetToSend); + } + + + public SendAndListen(byte sendChannel, byte repeatCount, int delayBetweenPackets_ms, byte listenChannel, + int timeout_ms, byte retryCount, Integer preambleExtension_ms, RadioPacket packetToSend + + ) { + super(); + this.sendChannel = sendChannel; + this.repeatCount = repeatCount; + this.delayBetweenPackets_ms = delayBetweenPackets_ms; + this.listenChannel = listenChannel; + this.timeout_ms = timeout_ms; + this.retryCount = retryCount; + this.preambleExtension_ms = preambleExtension_ms == null ? 0 : preambleExtension_ms; + this.packetToSend = packetToSend; + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.SendAndListen; + } + + + @Override + public byte[] getRaw() { + + // If firmware version is not set (error reading version from device, shouldn't happen), + // we will default to version 2 + boolean isPacketV2 = RileyLinkUtil.getFirmwareVersion() != null ? RileyLinkUtil.getFirmwareVersion() + .isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher) : true; + + ArrayList bytes = new ArrayList(); + bytes.add(this.getCommandType().code); + bytes.add(this.sendChannel); + bytes.add(this.repeatCount); + + if (isPacketV2) { // delay is unsigned 16-bit integer + byte[] delayBuff = ByteBuffer.allocate(4).putInt(delayBetweenPackets_ms).array(); + bytes.add(delayBuff[2]); + bytes.add(delayBuff[3]); + } else { + bytes.add((byte)delayBetweenPackets_ms); + } + + bytes.add(this.listenChannel); + + byte[] timeoutBuff = ByteBuffer.allocate(4).putInt(timeout_ms).array(); + + bytes.add(timeoutBuff[0]); + bytes.add(timeoutBuff[1]); + bytes.add(timeoutBuff[2]); + bytes.add(timeoutBuff[3]); + + bytes.add(retryCount); + + if (isPacketV2) { // 2.x (and probably higher versions) support preamble extension + byte[] preambleBuf = ByteBuffer.allocate(4).putInt(preambleExtension_ms).array(); + bytes.add(preambleBuf[2]); + bytes.add(preambleBuf[3]); + } + + return ByteUtil.concat(ByteUtil.getByteArrayFromList(bytes), packetToSend.getEncoded()); + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetHardwareEncoding.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetHardwareEncoding.java new file mode 100644 index 00000000000..8019563ee91 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetHardwareEncoding.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; + +public class SetHardwareEncoding extends RileyLinkCommand { + + private final RileyLinkEncodingType encoding; + + + public SetHardwareEncoding(RileyLinkEncodingType encoding) { + super(); + this.encoding = encoding; + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.SetHardwareEncoding; + } + + + @Override + public byte[] getRaw() { + return getByteArray(getCommandType().code, encoding.value); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetPreamble.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetPreamble.java new file mode 100644 index 00000000000..7333459f2b9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/SetPreamble.java @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import java.nio.ByteBuffer; + +import org.apache.commons.lang3.NotImplementedException; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; + +public class SetPreamble extends RileyLinkCommand { + + private int preamble; + + + public SetPreamble(int preamble) throws Exception { + super(); + + // this command was not supported before 2.0 + if (!RileyLinkUtil.getFirmwareVersion().isSameVersion(RileyLinkFirmwareVersion.Version2AndHigher)) { + throw new NotImplementedException("Old firmware does not support SetPreamble command"); + } + + if (preamble < 0 || preamble > 0xFFFF) { + throw new Exception("preamble value is out of range"); + } + this.preamble = preamble; + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.SetPreamble; + } + + + @Override + public byte[] getRaw() { + byte[] bytes = ByteBuffer.allocate(4).putInt(preamble).array(); + return getByteArray(this.getCommandType().code, bytes[2], bytes[3]); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/UpdateRegister.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/UpdateRegister.java new file mode 100644 index 00000000000..71ae922ba82 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/command/UpdateRegister.java @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.CC111XRegister; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; + +public class UpdateRegister extends RileyLinkCommand { + + CC111XRegister register; + byte registerValue; + + + public UpdateRegister(CC111XRegister register, byte registerValue) { + super(); + this.register = register; + this.registerValue = registerValue; + } + + + @Override + public RileyLinkCommandType getCommandType() { + return RileyLinkCommandType.UpdateRegister; + } + + + @Override + public byte[] getRaw() { + return getByteArray(getCommandType().code, register.value, registerValue); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyScanResults.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyScanResults.java new file mode 100644 index 00000000000..88850e315e5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyScanResults.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by geoff on 5/30/16. + * changed by Andy 10/20/18 + */ +public class FrequencyScanResults { + + public List trials = new ArrayList<>(); + public double bestFrequencyMHz = 0.0; + public long dateTime; + + + public void sort() { + Collections.sort(trials, (trial1, trial2) -> { + int res = trial1.averageRSSI.compareTo(trial2.averageRSSI); + + if (res == 0) { + return (int)(trial1.frequencyMHz - trial2.frequencyMHz); + } else + return res; + + }); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyTrial.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyTrial.java new file mode 100644 index 00000000000..af7dce6a980 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/FrequencyTrial.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by geoff on 5/30/16. + * changed by Andy 10/20/18 + */ +public class FrequencyTrial { + + public int tries = 0; + public int successes = 0; + public Double averageRSSI = 0.0; + public double frequencyMHz = 0.0; + public List rssiList = new ArrayList<>(); + public double averageRSSI2; + + + public void calculateAverage() { + int sum = 0; + int count = 0; + for (Integer rssi : rssiList) { + sum += Math.abs(rssi); + count++; + } + + double avg = (sum / (count * 1.0d)); + + if (count != 0) + this.averageRSSI = avg * (-1); + else + this.averageRSSI = -99.0d; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/GattAttributes.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/GattAttributes.java new file mode 100644 index 00000000000..4a76d25d1c7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/GattAttributes.java @@ -0,0 +1,87 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Created by geoff on 5/21/16. + */ +public class GattAttributes { + + // NOTE: these uuid strings must be lower case! + + public static String PREFIX = "0000"; + public static String SUFFIX = "-0000-1000-8000-00805f9b34fb"; + public static String SERVICE_GAP = PREFIX + "1800" + SUFFIX; + public static String CHARA_GAP_NAME = PREFIX + "2a00" + SUFFIX; // RileyLink RFSpy + public static String CHARA_GAP_NUM = PREFIX + "2a01" + SUFFIX; // 0000 + public static String CHARA_GAP_UNK = PREFIX + "2a01" + SUFFIX; // a + + public static String SERVICE_BATTERY = PREFIX + "180f" + SUFFIX; // Battery + public static String CHARA_BATTERY_UNK = PREFIX + "2a19" + SUFFIX; + + // RileyLink Radio Service + public static String SERVICE_RADIO = "0235733b-99c5-4197-b856-69219c2a3845"; + public static String CHARA_RADIO_DATA = "c842e849-5028-42e2-867c-016adada9155"; + public static String CHARA_RADIO_RESPONSE_COUNT = "6e6c7910-b89e-43a5-a0fe-50c5e2b81f4a"; + public static String CHARA_RADIO_TIMER_TICK = "6e6c7910-b89e-43a5-78af-50c5e2b86f7e"; + public static String CHARA_RADIO_CUSTOM_NAME = "d93b2af0-1e28-11e4-8c21-0800200c9a66"; + public static String CHARA_RADIO_VERSION = "30d99dc9-7c91-4295-a051-0a104d238cf2"; + public static String CHARA_RADIO_LED_MODE = "c6d84241-f1a7-4f9c-a25f-fce16732f14e"; + + private static Map attributes; + private static Map attributesRileyLinkSpecific; + + // table of names for uuids + static { + attributes = new HashMap<>(); + + attributes.put(SERVICE_GAP, "Device Information Service"); + attributes.put(CHARA_GAP_NAME, "Name"); // + attributes.put(CHARA_GAP_NUM, "Number"); // + + attributes.put(SERVICE_BATTERY, "Battery Service"); + + attributes.put(SERVICE_RADIO, "Radio Interface"); // a + attributes.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name"); + attributes.put(CHARA_RADIO_DATA, "Data"); + attributes.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count"); + attributes.put(CHARA_RADIO_TIMER_TICK, "Timer Tick"); + attributes.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion + attributes.put(CHARA_RADIO_LED_MODE, "Led Mode"); + + attributesRileyLinkSpecific = new HashMap<>(); + + attributesRileyLinkSpecific.put(SERVICE_RADIO, "Radio Interface"); // a + attributesRileyLinkSpecific.put(CHARA_RADIO_CUSTOM_NAME, "Custom Name"); + attributesRileyLinkSpecific.put(CHARA_RADIO_DATA, "Data"); + attributesRileyLinkSpecific.put(CHARA_RADIO_RESPONSE_COUNT, "Response Count"); + attributesRileyLinkSpecific.put(CHARA_RADIO_TIMER_TICK, "Timer Tick"); + attributesRileyLinkSpecific.put(CHARA_RADIO_VERSION, "Version"); // firmwareVersion + attributesRileyLinkSpecific.put(CHARA_RADIO_LED_MODE, "Led Mode"); + } + + + public static String lookup(UUID uuid) { + return lookup(uuid.toString()); + } + + + public static String lookup(String uuid) { + return lookup(uuid, uuid); + } + + + public static String lookup(String uuid, String defaultName) { + String name = attributes.get(uuid); + return name == null ? defaultName : name; + } + + + // we check for specific UUID (Radio ones, because thoose seem to be unique + public static boolean isRileyLink(UUID uuid) { + return attributesRileyLinkSpecific.containsKey(uuid.toString()); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RFSpyResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RFSpyResponse.java new file mode 100644 index 00000000000..cf5362427cd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RFSpyResponse.java @@ -0,0 +1,134 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RFSpyRLResponse; + +/** + * Created by geoff on 5/26/16. + */ +public class RFSpyResponse { + + // 0xaa == timeout + // 0xbb == interrupted + // 0xcc == zero-data + // 0xdd == success + // 0x11 == invalidParam + // 0x22 == unknownCommand + + protected byte[] raw; + protected RadioResponse radioResponse; + private RileyLinkCommand command; + + + public RFSpyResponse() { + init(new byte[0]); + } + + + public RFSpyResponse(byte[] bytes) { + init(bytes); + } + + + public RFSpyResponse(RileyLinkCommand command, byte[] rawResponse) { + + this.command = command; + init(rawResponse); + } + + + public void init(byte[] bytes) { + if (bytes == null) { + raw = new byte[0]; + } else { + raw = bytes; + } + + } + + + public RadioResponse getRadioResponse() throws RileyLinkCommunicationException { + if (looksLikeRadioPacket()) { + radioResponse = new RadioResponse(command); + radioResponse.init(raw); + } else { + radioResponse = new RadioResponse(); + } + return radioResponse; + } + + + public boolean wasTimeout() { + if ((raw.length == 1) || (raw.length == 2)) { + if (raw[0] == (byte)0xaa) { + return true; + } + } + return false; + } + + + public boolean wasInterrupted() { + if ((raw.length == 1) || (raw.length == 2)) { + if (raw[0] == (byte)0xbb) { + return true; + } + } + return false; + } + + + public boolean isInvalidParam() { + if ((raw.length == 1) || (raw.length == 2)) { + if (raw[0] == (byte)0x11) { + return true; + } + } + return false; + } + + + public boolean isUnknownCommand() { + if ((raw.length == 1) || (raw.length == 2)) { + if (raw[0] == (byte)0x22) { + return true; + } + } + return false; + } + + + public boolean isOK() { + if ((raw.length == 1) || (raw.length == 2)) { + if (raw[0] == (byte)0x01 || raw[0] == (byte)0xDD) { + return true; + } + } + return false; + } + + + public boolean looksLikeRadioPacket() { + if (raw.length > 2) { + return true; + } + return false; + } + + + @Override + public String toString() { + if (raw.length > 2) { + return "Radio packet"; + } else { + RFSpyRLResponse r = RFSpyRLResponse.fromByte(raw[0]); + return r.toString(); + } + } + + + public byte[] getRaw() { + return raw; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessage.java new file mode 100644 index 00000000000..b52c3eb2642 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessage.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +/** + * Created by andy on 5/6/18. + */ +public interface RLMessage { + + byte[] getTxData(); + + + boolean isValid(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessageType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessageType.java new file mode 100644 index 00000000000..4fbc3144bd3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RLMessageType.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +/** + * Created by andy on 5/6/18. + */ + +public enum RLMessageType { + PowerOn, // for powering on the pump (wakeup) + ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel) + ; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioPacket.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioPacket.java new file mode 100644 index 00000000000..a48e3de2088 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioPacket.java @@ -0,0 +1,57 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import org.apache.commons.lang3.NotImplementedException; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.CRC; + +/** + * Created by geoff on 5/22/16. + */ + +public class RadioPacket { + + protected byte[] pkt; + + + public RadioPacket(byte[] pkt) { + this.pkt = pkt; + } + + + public byte[] getRaw() { + return pkt; + } + + + public byte[] getWithCRC() { + byte[] withCRC = ByteUtil.concat(pkt, CRC.crc8(pkt)); + return withCRC; + } + + + public byte[] getEncoded() { + + switch (RileyLinkUtil.getEncoding()) { + case Manchester: { // We have this encoding in RL firmware + return pkt; + } + + case FourByteSixByteLocal: { + byte[] withCRC = getWithCRC(); + + byte[] encoded = RileyLinkUtil.getEncoding4b6b().encode4b6b(withCRC); + return ByteUtil.concat(encoded, (byte)0); + } + + case FourByteSixByteRileyLink: { + return getWithCRC(); + } + + default: + throw new NotImplementedException(("Encoding not supported: " + RileyLinkUtil.getEncoding().toString())); + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioResponse.java new file mode 100644 index 00000000000..646d7aac9ac --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/RadioResponse.java @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data; + +import org.apache.commons.lang3.NotImplementedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.command.RileyLinkCommand; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkCommandType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.CRC; + +/** + * Created by geoff on 5/30/16. + */ +public class RadioResponse { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + + public boolean decodedOK = false; + public int rssi; + public int responseNumber; + public byte[] decodedPayload = new byte[0]; + public byte receivedCRC; + private RileyLinkCommand command; + + + public RadioResponse() { + + } + + + // public RadioResponse(byte[] rxData) { + // init(rxData); + // } + + public RadioResponse(RileyLinkCommand command /* , byte[] raw */) { + this.command = command; + // init(raw); + } + + + public boolean isValid() { + + // We should check for all listening commands, but only one is actually used + if (command != null && command.getCommandType() != RileyLinkCommandType.SendAndListen) { + return true; + } + + if (!decodedOK) { + return false; + } + if (decodedPayload != null) { + if (receivedCRC == CRC.crc8(decodedPayload)) { + return true; + } + } + return false; + } + + + public void init(byte[] rxData) throws RileyLinkCommunicationException { + + if (rxData == null) { + return; + } + if (rxData.length < 3) { + // This does not look like something valid heard from a RileyLink device + return; + } + byte[] encodedPayload; + + if (RileyLinkFirmwareVersion.isSameVersion(RileyLinkUtil.getRileyLinkServiceData().versionCC110, + RileyLinkFirmwareVersion.Version2)) { + encodedPayload = ByteUtil.substring(rxData, 3, rxData.length - 3); + rssi = rxData[1]; + responseNumber = rxData[2]; + } else { + encodedPayload = ByteUtil.substring(rxData, 2, rxData.length - 2); + rssi = rxData[0]; + responseNumber = rxData[1]; + } + + try { + + // for non-radio commands we just return the raw response + // well, for non-radio commands we shouldn't even reach this point + // but getVersion is kind of exception + if (command != null && // + command.getCommandType() != RileyLinkCommandType.SendAndListen) { + decodedOK = true; + decodedPayload = encodedPayload; + return; + } + + switch (RileyLinkUtil.getEncoding()) { + + case Manchester: + case FourByteSixByteRileyLink: { + decodedOK = true; + decodedPayload = encodedPayload; + } + break; + + case FourByteSixByteLocal: { + byte[] decodeThis = RileyLinkUtil.getEncoding4b6b().decode4b6b(encodedPayload); + + if (decodeThis != null && decodeThis.length > 2) { + decodedOK = true; + + decodedPayload = ByteUtil.substring(decodeThis, 0, decodeThis.length - 1); + receivedCRC = decodeThis[decodeThis.length - 1]; + byte calculatedCRC = CRC.crc8(decodedPayload); + if (receivedCRC != calculatedCRC) { + LOG.error(String.format("RadioResponse: CRC mismatch, calculated 0x%02x, received 0x%02x", + calculatedCRC, receivedCRC)); + } + } else { + throw new RileyLinkCommunicationException(RileyLinkBLEError.TooShortOrNullResponse); + } + } + break; + + default: + throw new NotImplementedException("this {" + RileyLinkUtil.getEncoding().toString() + + "} encoding is not supported"); + } + } catch (NumberFormatException e) { + decodedOK = false; + LOG.error("Failed to decode radio data: " + ByteUtil.shortHexString(encodedPayload)); + } + } + + + public byte[] getPayload() { + return decodedPayload; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6b.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6b.java new file mode 100644 index 00000000000..a05a0be4012 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6b.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; + +/** + * Created by andy on 11/24/18. + */ + +public interface Encoding4b6b { + + byte[] encode4b6b(byte[] data); + + + byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException; + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bAbstract.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bAbstract.java new file mode 100644 index 00000000000..3bab1a6d05e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bAbstract.java @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding; + +import org.slf4j.Logger; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + + +/** + * Created by andy on 11/24/18. + */ + +public abstract class Encoding4b6bAbstract implements Encoding4b6b { + + /** + * encode4b6bMap is an ordered list of translations 6bits -> 4 bits, in order from 0x0 to 0xF + * The 6 bit codes are what is used on the RF side of the RileyLink to communicate + * with a Medtronic pump. + */ + public static final byte[] encode4b6bList = new byte[] { + 0x15, 0x31, 0x32, 0x23, 0x34, 0x25, 0x26, 0x16, 0x1a, 0x19, 0x2a, 0x0b, 0x2c, 0x0d, 0x0e, 0x1c }; + + + // 21, 49, 50, 35, 52, 37, 38, 22, 26, 25, 42, 11, 44, 13, 14, 28 + + public abstract byte[] encode4b6b(byte[] data); + + + public abstract byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException; + + + protected short convertUnsigned(byte x) { + short ss = x; + + if (ss < 0) { + ss += 256; + } + + return ss; + } + + + /* O(n) lookup. Run on an O(n) translation of a byte-stream, gives O(n**2) performance. Sigh. */ + public static int encode4b6bListIndex(byte b) { + for (int i = 0; i < encode4b6bList.length; i++) { + if (b == encode4b6bList[i]) { + return i; + } + } + return -1; + } + + + public void writeError(Logger LOG, byte[] raw, String errorData) { + + LOG.error("\n=============================================================================\n" + // + " Decoded payload length is zero.\n" + + " encodedPayload: {}\n" + + " errors: {}\n" + + "=============================================================================", // + ByteUtil.getHex(raw), errorData); + + //FabricUtil.createEvent("MedtronicDecode4b6bError", null); + + return; + + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGeoff.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGeoff.java new file mode 100644 index 00000000000..40f24cf135e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGeoff.java @@ -0,0 +1,249 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +/** + * Created by andy on 11/24/18. + */ + +public class Encoding4b6bGeoff extends Encoding4b6bAbstract { + + public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGeoff.class); + + + public byte[] encode4b6b(byte[] data) { + // if ((data.length % 2) != 0) { + // LOG.error("Warning: data is odd number of bytes"); + // } + // use arraylists because byte[] is annoying. + List inData = ByteUtil.getListFromByteArray(data); + List outData = new ArrayList<>(); + + int acc = 0; + int bitcount = 0; + int i; + for (i = 0; i < inData.size(); i++) { + acc <<= 6; + acc |= encode4b6bList[(inData.get(i) >> 4) & 0x0f]; + bitcount += 6; + + acc <<= 6; + acc |= encode4b6bList[inData.get(i) & 0x0f]; + bitcount += 6; + + while (bitcount >= 8) { + byte outByte = (byte) (acc >> (bitcount - 8) & 0xff); + outData.add(outByte); + bitcount -= 8; + acc &= (0xffff >> (16 - bitcount)); + } + } + if (bitcount > 0) { + acc <<= 6; + acc |= 0x14; // marks uneven packet boundary. + bitcount += 6; + if (bitcount >= 8) { + byte outByte = (byte) ((acc >> (bitcount - 8)) & 0xff); + outData.add(outByte); + bitcount -= 8; + // acc &= (0xffff >> (16 - bitcount)); + } + while (bitcount >= 8) { + outData.add((byte) 0); + bitcount -= 8; + } + } + + // convert back to byte[] + byte[] rval = ByteUtil.getByteArrayFromList(outData); + + return rval; + + } + + + /** + * Decode by Geoff + * + * @param raw + * @return + * @throws NumberFormatException + */ + public byte[] decode4b6b(byte[] raw) throws RileyLinkCommunicationException { + + StringBuilder errorMessageBuilder = new StringBuilder(); + + errorMessageBuilder.append("Input data: " + ByteUtil.shortHexString(raw) + "\n"); + + if ((raw.length % 2) != 0) { + errorMessageBuilder.append("Warn: odd number of bytes.\n"); + } + + byte[] rval = new byte[]{}; + int availableBits = 0; + int codingErrors = 0; + int x = 0; + // Log.w(TAG,"decode4b6b: untested code"); + // Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw))); + for (int i = 0; i < raw.length; i++) { + int unsignedValue = raw[i]; + if (unsignedValue < 0) { + unsignedValue += 256; + } + x = (x << 8) + unsignedValue; + availableBits += 8; + if (availableBits >= 12) { + // take top six + int highcode = (x >> (availableBits - 6)) & 0x3F; + int highIndex = encode4b6bListIndex((byte) (highcode)); + // take bottom six + int lowcode = (x >> (availableBits - 12)) & 0x3F; + int lowIndex = encode4b6bListIndex((byte) (lowcode)); + // special case at end of transmission on uneven boundaries: + if ((highIndex >= 0) && (lowIndex >= 0)) { + byte decoded = (byte) ((highIndex << 4) + lowIndex); + rval = ByteUtil.concat(rval, decoded); + /* + * LOG.debug(String.format( + * "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s" + * , + * i,x,highcode,highIndex, lowcode, + * lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString + * (ByteUtil.substring(raw,i+1,raw.length-i-1)))); + */ + } else { + // LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits)); + errorMessageBuilder.append(String.format( + "decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n", + i, x, highcode, lowcode, availableBits)); + codingErrors++; + } + + availableBits -= 12; + x = x & (0x0000ffff >> (16 - availableBits)); + } + } + + if (availableBits != 0) { + if ((availableBits == 4) && (x == 0x05)) { + // normal end + } else { + // LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + + // ")"); + errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)(" + + availableBits + ")\n"); + codingErrors++; + } + } else { + // also normal end. + } + + if (codingErrors > 0) { + // LOG.error("decode4b6b: " + codingErrors + " coding errors encountered."); + errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered."); + writeError(LOG, raw, errorMessageBuilder.toString()); + throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString()); + } + return rval; + } + + // public static RFTools.DecodeResponseDto decode4b6bWithoutException(byte[] raw) { + // /* + // * if ((raw.length % 2) != 0) { + // * LOG.error("Warning: data is odd number of bytes"); + // * } + // */ + // + // RFTools.DecodeResponseDto response = new RFTools.DecodeResponseDto(); + // + // StringBuilder errorMessageBuilder = new StringBuilder(); + // + // errorMessageBuilder.append("Input data: " + ByteUtil.getHex(raw) + "\n"); + // + // if ((raw.length % 2) != 0) { + // errorMessageBuilder.append("Warn: odd number of bytes."); + // } + // + // byte[] rval = new byte[] {}; + // int availableBits = 0; + // int codingErrors = 0; + // int x = 0; + // // Log.w(TAG,"decode4b6b: untested code"); + // // Log.w(TAG,String.format("Decoding %d bytes: %s",raw.length,ByteUtil.shortHexString(raw))); + // for (int i = 0; i < raw.length; i++) { + // int unsignedValue = raw[i]; + // if (unsignedValue < 0) { + // unsignedValue += 256; + // } + // x = (x << 8) + unsignedValue; + // availableBits += 8; + // if (availableBits >= 12) { + // // take top six + // int highcode = (x >> (availableBits - 6)) & 0x3F; + // int highIndex = encode4b6bListIndex((byte)(highcode)); + // // take bottom six + // int lowcode = (x >> (availableBits - 12)) & 0x3F; + // int lowIndex = encode4b6bListIndex((byte)(lowcode)); + // // special case at end of transmission on uneven boundaries: + // if ((highIndex >= 0) && (lowIndex >= 0)) { + // byte decoded = (byte)((highIndex << 4) + lowIndex); + // rval = ByteUtil.concat(rval, decoded); + // /* + // * LOG.debug(String.format( + // * + // "i=%d,x=0x%08X,0x%02X->0x%02X, 0x%02X->0x%02X, result: 0x%02X, %d bits remaining, errors %d, bytes remaining: %s" + // * , + // * i,x,highcode,highIndex, lowcode, + // * lowIndex,decoded,availableBits,codingErrors,ByteUtil.shortHexString + // * (ByteUtil.substring(raw,i+1,raw.length-i-1)))); + // */ + // } else { + // // + // LOG.debug(String.format("i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining",i,x,highcode,lowcode,availableBits)); + // errorMessageBuilder.append(String.format( + // "decode4b6b: i=%d,x=%08X, coding error: highcode=0x%02X, lowcode=0x%02X, %d bits remaining.\n", + // i, x, highcode, lowcode, availableBits)); + // codingErrors++; + // } + // + // availableBits -= 12; + // x = x & (0x0000ffff >> (16 - availableBits)); + // } else { + // // LOG.debug(String.format("i=%d, skip: x=0x%08X, available bits %d",i,x,availableBits)); + // } + // } + // + // if (availableBits != 0) { + // if ((availableBits == 4) && (x == 0x05)) { + // // normal end + // } else { + // LOG.error("decode4b6b: failed clean decode -- extra bits available (not marker)(" + availableBits + ")"); + // errorMessageBuilder.append("decode4b6b: failed clean decode -- extra bits available (not marker)(" + // + availableBits + ")\n"); + // codingErrors++; + // } + // } else { + // // also normal end. + // } + // + // if (codingErrors > 0) { + // LOG.error("decode4b6b: " + codingErrors + " coding errors encountered."); + // errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered."); + // + // response.errorData = errorMessageBuilder.toString(); + // } else { + // response.data = rval; + // } + // + // return response; + // } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGo.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGo.java new file mode 100644 index 00000000000..6609138011c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bGo.java @@ -0,0 +1,163 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +/** + * Created by andy on 11/24/18. + */ + +public class Encoding4b6bGo extends Encoding4b6bAbstract { + + public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bGo.class); + private static Map decodeGoMap; + + + public byte[] encode4b6b(byte[] src) { + // 2 input bytes produce 3 output bytes. + // Odd final input byte, if any, produces 2 output bytes. + int n = src.length; + byte[] dst = new byte[3 * (n / 2) + 2 * (n % 2)]; + int j = 0; + + for (int i = 0; i < n; i += 2, j = j + 3) { + short x = convertUnsigned(src[i]); + short a = encode4b6bList[hi(4, x)]; + short b = encode4b6bList[lo(4, x)]; + dst[j] = (byte)(a << 2 | hi(4, b)); + if (i + 1 < n) { + short y = convertUnsigned(src[i + 1]); + short c = encode4b6bList[hi(4, y)]; + short d = encode4b6bList[lo(4, y)]; + dst[j + 1] = (byte)(lo(4, b) << 4 | hi(6, c)); + dst[j + 2] = (byte)(lo(2, c) << 6 | d); + } else { + // Fill final nibble with 5 to match pump behavior. + dst[j + 1] = (byte)(lo(4, b) << 4 | 0x5); + } + + } + return dst; + } + + + /** + * Decode from Go code by ecc1. NOT WORKING + * + * @param src + * @return + */ + public byte[] decode4b6b(byte[] src) throws RileyLinkCommunicationException { + int n = src.length; + + if (decodeGoMap == null) + initDecodeGo(); + + StringBuilder errorMessageBuilder = new StringBuilder(); + + errorMessageBuilder.append("Input data: " + ByteUtil.getHex(src) + "\n"); + int codingErrors = 0; + + // Check for valid packet length. + if (n % 3 == 1) { + errorMessageBuilder.append("Invalid package length " + n); + codingErrors++; + // return nil, ErrDecoding + } + // 3 input bytes produce 2 output bytes. + // Final 2 input bytes, if any, produce 1 output byte. + byte[] dst = new byte[2 * (n / 3) + (n % 3) / 2]; + + int j = 0; + for (int i = 0; i < n; i = i + 3, j = j + 2) { + if (i + 1 >= n) { + errorMessageBuilder.append("Overflow in i (" + i + ")"); + } + short x = convertUnsigned(src[i]); + short y = convertUnsigned(src[i + 1]); + short a = decode6b_goMap(hi(6, x)); + short b = decode6b_goMap(lo(2, x) << 4 | hi(4, y)); + if (a == 0xFF || b == 0xFF) { + errorMessageBuilder.append("Error decoding "); + codingErrors++; + } + dst[j] = (byte)(a << 4 | b); + if (i + 2 < n) { + short z = convertUnsigned(src[i + 2]); + short c = decode6b_goMap(lo(4, y) << 2 | hi(2, z)); + short d = decode6b_goMap(lo(6, z)); + if (c == 0xFF || d == 0xFF) { + errorMessageBuilder.append("Error decoding "); + codingErrors++; + } + dst[j + 1] = (byte)(c << 4 | d); + } + } + + if (codingErrors > 0) { + errorMessageBuilder.append("decode4b6b: " + codingErrors + " coding errors encountered."); + writeError(LOG, dst, errorMessageBuilder.toString()); + throw new RileyLinkCommunicationException(RileyLinkBLEError.CodingErrors, errorMessageBuilder.toString()); + } + + return dst; + } + + + static short hi(int n, short x) { + // x = convertUnsigned(x); + return (short)(x >> (8 - n)); + } + + + static short lo(int n, short x) { + // byte b = (byte)x; + return (short)(x & ((1 << n) - 1)); + } + + + public static void initDecodeGo() { + + decodeGoMap = new HashMap<>(); + + putToMap(0x0B, 0x0B); + putToMap(0x0D, 0x0D); + putToMap(0x0E, 0x0E); + putToMap(0x15, 0x00); + putToMap(0x16, 0x07); + putToMap(0x19, 0x09); + putToMap(0x1A, 0x08); + putToMap(0x1C, 0x0F); + putToMap(0x23, 0x03); + putToMap(0x25, 0x05); + putToMap(0x26, 0x06); + putToMap(0x2A, 0x0A); + putToMap(0x2C, 0x0C); + putToMap(0x31, 0x01); + putToMap(0x32, 0x02); + putToMap(0x34, 0x04); + + } + + + private static short decode6b_goMap(int value) { + short val = (short)value; + if (decodeGoMap.containsKey(val)) + return decodeGoMap.get(val); + else + return (short)0xff; + } + + + private static void putToMap(int val1, int val2) { + decodeGoMap.put((short)val1, (short)val2); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java new file mode 100644 index 00000000000..a7b005b9b8f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/data/encoding/Encoding4b6bLoop.java @@ -0,0 +1,136 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.encoding; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +/** + * Created by andy on 11/24/18. + */ + +public class Encoding4b6bLoop extends Encoding4b6bAbstract { + private static final Logger log = LoggerFactory.getLogger(Encoding4b6bLoop.class); + + public static final Logger LOG = LoggerFactory.getLogger(Encoding4b6bLoop.class); + public Map codesRev = null; + + + public Encoding4b6bLoop() { + createCodeRev(); + } + + + /** + * This method is almost 1:1 with same method from Loop, only change is unsigning of element and |05 added for + * last byte. It should work better than original one, which is really different than this one. + * + * @param data + * @return + */ + public byte[] encode4b6b(byte[] data) { + + List buffer = new ArrayList(); + int bitAccumulator = 0x0; + int bitcount = 0; + + for (byte element : data) { + + short element2 = element; + + if (element2 < 0) { + element2 += 256; + } + + bitAccumulator <<= 6; + bitAccumulator |= encode4b6bList[element2 >> 4]; + bitcount += 6; + + bitAccumulator <<= 6; + bitAccumulator |= encode4b6bList[element2 & 0x0f]; + bitcount += 6; + + while (bitcount >= 8) { + buffer.add((byte)((bitAccumulator >> (bitcount - 8)) & 0xff)); + bitcount -= 8; + bitAccumulator &= (0xffff >> (16 - bitcount)); + } + } + + if (bitcount > 0) { + bitAccumulator <<= (8 - bitcount); + buffer.add((byte)((bitAccumulator | 0x5) & 0xff)); + } + + return ByteUtil.getByteArrayFromList(buffer); + } + + + /** + * DOESN'T WORK YET + * + * @param data + * @return + * @throws RileyLinkCommunicationException + */ + public byte[] decode4b6b(byte[] data) throws RileyLinkCommunicationException { + List buffer = new ArrayList(); + int availBits = 0; + int bitAccumulator = 0; + + for (byte element2 : data) { + + short element = convertUnsigned(element2); + + // if (element < 0) { + // element += 255; + // } + + if (element == 0) { + break; + } + + bitAccumulator = (bitAccumulator << 8) + element; + availBits += 8; + + if (availBits >= 12) { + + int hiNibble; + int loNibble; + + try { + int index = (bitAccumulator >> (availBits - 6)); + int index2 = ((bitAccumulator >> (availBits - 12)) & 0b111111); + hiNibble = codesRev.get((bitAccumulator >> (availBits - 6))); + loNibble = codesRev.get(((bitAccumulator >> (availBits - 12)) & 0b111111)); + } catch (Exception e) { + log.error("Unhandled exception", e); + return null; + } + + int decoded = ((hiNibble << 4) + loNibble); + buffer.add((byte)decoded); + availBits -= 12; + bitAccumulator = bitAccumulator & (0xffff >> (16 - availBits)); + } + } + + return ByteUtil.getByteArrayFromList(buffer); + } + + + private void createCodeRev() { + codesRev = new HashMap<>(); + + for (int i = 0; i < encode4b6bList.length; i++) { + codesRev.put(i, encode4b6bList[i]); + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/CC111XRegister.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/CC111XRegister.java new file mode 100644 index 00000000000..02694979d99 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/CC111XRegister.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 21/05/2018. + */ + +public enum CC111XRegister { + + sync1(0x00), // + sync0(0x01), // + pktlen(0x02), // + pktctrl1(0x03), // + pktctrl0(0x04), // + fsctrl1(0x07), // + freq2(0x09), // + freq1(0x0a), // + freq0(0x0b), // + mdmcfg4(0x0c), // + mdmcfg3(0x0d), // + mdmcfg2(0x0e), // + mdmcfg1(0x0f), // + mdmcfg0(0x10), // + deviatn(0x11), // + mcsm0(0x14), // + foccfg(0x15), // + agcctrl2(0x17), // + agcctrl1(0x18), // + agcctrl0(0x19), // + frend1(0x1a), // + frend0(0x1b), // + fscal3(0x1c), // + fscal2(0x1d), // + fscal1(0x1e), // + fscal0(0x1f), // + test1(0x24), // + test0(0x25), // + paTable0(0x2e), // + + ; + + public byte value; + + + CC111XRegister(int value) { + this.value = (byte)value; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyCommand.java new file mode 100644 index 00000000000..b2bbe81e9a3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyCommand.java @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 22/05/2018. + */ + +public enum RFSpyCommand { + + GetState(1), // + GetVersion(2, false), // + GetPacket(3), // aka Listen, receive + Send(4), // + SendAndListen(5), // + UpdateRegister(6), // + Reset(7), // + + ; + + public byte code; + private boolean encoded = true; + + + RFSpyCommand(int code) { + this.code = (byte)code; + } + + + RFSpyCommand(int code, boolean encoded) { + this.code = (byte)code; + this.encoded = encoded; + } + + + public boolean isEncoded() { + return encoded; + } + + + public void setEncoded(boolean encoded) { + this.encoded = encoded; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyRLResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyRLResponse.java new file mode 100644 index 00000000000..d1435d470da --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RFSpyRLResponse.java @@ -0,0 +1,37 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +public enum RFSpyRLResponse { + // 0xaa == timeout + // 0xbb == interrupted + // 0xcc == zero-data + // 0xdd == success + // 0x11 == invalidParam + // 0x22 == unknownCommand + + Invalid(0), // default, just fail + Timeout(0xAA), + Interrupted(0xBB), + ZeroData(0xCC), + Success(0xDD), + OldSuccess(0x01), + InvalidParam(0x11), + UnknownCommand(0x22), ; + + byte value; + + + RFSpyRLResponse(int value) { + this.value = (byte)value; + } + + + public static RFSpyRLResponse fromByte(byte input) { + for (RFSpyRLResponse type : values()) { + if (type.value == input) { + return type; + } + } + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RLMessageType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RLMessageType.java new file mode 100644 index 00000000000..a55fa1db198 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RLMessageType.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 5/6/18. + */ + +public enum RLMessageType { + PowerOn, // for powering on the pump (wakeup) + ReadSimpleData, // for checking if pump is readable (for Medtronic we can use GetModel) + ; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RXFilterMode.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RXFilterMode.java new file mode 100644 index 00000000000..e50929ebfe7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RXFilterMode.java @@ -0,0 +1,19 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 21/05/2018. + */ + +public enum RXFilterMode { + + Wide(0x50), // + Narrow(0x90) // + ; + + public byte value; + + + RXFilterMode(int value) { + this.value = (byte)value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkBLEError.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkBLEError.java new file mode 100644 index 00000000000..8d32445718e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkBLEError.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 11/24/18. + */ + +public enum RileyLinkBLEError { + CodingErrors("Coding Errors encountered during decode of RileyLink packet."), // + Timeout("Timeout"), // + Interrupted("Interrupted"), + TooShortOrNullResponse("Too short or null decoded response."); + + private String description; + + + RileyLinkBLEError(String description) { + + this.description = description; + } + + + public String getDescription() { + return description; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkCommandType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkCommandType.java new file mode 100644 index 00000000000..e241f0a83a2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkCommandType.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 22/05/2018. + */ + +public enum RileyLinkCommandType { + + GetState(1), // + GetVersion(2), // + GetPacket(3), // aka Listen, receive + Send(4), // + SendAndListen(5), // + UpdateRegister(6), // + Reset(7), // + Led(8), + ReadRegister(9), + SetModeRegisters(10), + SetHardwareEncoding(11), + SetPreamble(12), + ResetRadioConfig(13), + GetStatistics(14), ; + + public byte code; + + + RileyLinkCommandType(int code) { + this.code = (byte)code; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkEncodingType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkEncodingType.java new file mode 100644 index 00000000000..5482e224e36 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkEncodingType.java @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.MainApp; + +public enum RileyLinkEncodingType { + + None(0x00, null), // No encoding on RL + Manchester(0x01, null), // Manchester encoding on RL (for Omnipod) + FourByteSixByteRileyLink(0x02, R.string.key_medtronic_pump_encoding_4b6b_rileylink), // 4b6b encoding on RL (for Medtronic) + FourByteSixByteLocal(0x00, R.string.key_medtronic_pump_encoding_4b6b_local), // No encoding on RL, but 4b6b encoding in code + ; + + public byte value; + public Integer resourceId; + public String description; + + private static Map encodingTypeMap; + + static { + encodingTypeMap = new HashMap<>(); + + for (RileyLinkEncodingType encType : values()) { + if (encType.resourceId!=null) { + encodingTypeMap.put(MainApp.gs(encType.resourceId), encType); + } + } + } + + + RileyLinkEncodingType(int value) { + this.value = (byte)value; + } + + + RileyLinkEncodingType(int value, Integer resourceId) { + this.value = (byte)value; + this.resourceId = resourceId; + } + + public static RileyLinkEncodingType getByDescription(String description) { + if (encodingTypeMap.containsKey(description)) { + return encodingTypeMap.get(description); + } + + return RileyLinkEncodingType.FourByteSixByteLocal; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkFirmwareVersion.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkFirmwareVersion.java new file mode 100644 index 00000000000..b6d0eab3cd4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkFirmwareVersion.java @@ -0,0 +1,104 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public enum RileyLinkFirmwareVersion { + + Version_0_0(0, 0, "0.0"), // just for defaulting + Version_0_9(0, 9, "0.9"), // + Version_1_0(1, 0, "1.0"), // + Version_2_0(2, 0, "2.0"), // + Version_2_2(2, 2, "2.2"), // + Version_3_0(3, 0, "3.0"), // + UnknownVersion(0, 0, "???"), // + Version1(Version_0_0, Version_0_9, Version_1_0), // + Version2(Version_2_0, Version_2_2), // + Version2AndHigher(Version_2_0, Version_2_2, Version_3_0), // + ; + + private static final String FIRMWARE_IDENTIFICATION_PREFIX = "subg_rfspy "; + private static final Pattern _version_pattern = Pattern.compile(FIRMWARE_IDENTIFICATION_PREFIX + + "([0-9]+)\\.([0-9]+)"); + static Map mapByVersion; + + static { + mapByVersion = new HashMap<>(); + for (RileyLinkFirmwareVersion version : values()) { + if (version.familyMembers == null) { + mapByVersion.put(version.versionKey, version); + } + } + } + + protected RileyLinkFirmwareVersion[] familyMembers; + private int major; + private int minor; + private String versionKey = ""; + + + RileyLinkFirmwareVersion(int major, int minor, String versionKey) { + this.major = major; + this.minor = minor; + this.versionKey = versionKey; + } + + + RileyLinkFirmwareVersion(RileyLinkFirmwareVersion... familyMembers) { + this.familyMembers = familyMembers; + } + + + public static boolean isSameVersion(RileyLinkFirmwareVersion versionWeCheck, RileyLinkFirmwareVersion versionSources) { + if (versionSources.familyMembers != null) { + for (RileyLinkFirmwareVersion vrs : versionSources.familyMembers) { + if (vrs == versionWeCheck) + return true; + } + } else { + return (versionWeCheck == versionSources); + } + return false; + } + + + public static RileyLinkFirmwareVersion getByVersionString(String versionString) { + if (versionString != null) { + Matcher m = _version_pattern.matcher(versionString); + if (m.find()) { + int major = Integer.parseInt(m.group(1)); + int minor = Integer.parseInt(m.group(2)); + String versionKey = major + "." + minor; + if (mapByVersion.containsKey(versionKey)) { + return mapByVersion.get(versionKey); + } else { + return defaultToLowestMajorVersion(major); // just in case there is new release that we don't cover + // example: 2.3 etc + } + } + } + + return RileyLinkFirmwareVersion.UnknownVersion; + } + + + private static RileyLinkFirmwareVersion defaultToLowestMajorVersion(int major) { + if (mapByVersion.containsKey(major + ".0")) { + return mapByVersion.get(major + ".0"); + } + return UnknownVersion; + } + + + public boolean isSameVersion(RileyLinkFirmwareVersion versionSources) { + return isSameVersion(this, versionSources); + } + + + @Override + public String toString() { + return FIRMWARE_IDENTIFICATION_PREFIX + versionKey; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkTargetFrequency.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkTargetFrequency.java new file mode 100644 index 00000000000..0d7f3415846 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/defs/RileyLinkTargetFrequency.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs; + +/** + * Created by andy on 6/7/18. + */ + +public enum RileyLinkTargetFrequency { + + Medtronic_WorldWide(868.25, 868.3, 868.35, 868.4, 868.45, 868.5, 868.55, 868.6, 868.65), // + Medtronic_US(916.45, 916.5, 916.55, 916.6, 916.65, 916.7, 916.75, 916.8), // + Omnipod(433.91), // + ; + + double[] frequencies; + + + RileyLinkTargetFrequency(double... frequencies) { + this.frequencies = frequencies; + } + + + public double[] getScanFrequencies() { + return frequencies; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperation.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperation.java new file mode 100644 index 00000000000..81fe52f8909 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperation.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations; + +import java.util.UUID; +import java.util.concurrent.Semaphore; + +import android.bluetooth.BluetoothGatt; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; + +/** + * Created by geoff on 5/26/16. + */ +public abstract class BLECommOperation { + + public boolean timedOut = false; + public boolean interrupted = false; + protected byte[] value; + protected BluetoothGatt gatt; + protected Semaphore operationComplete = new Semaphore(0, true); + + + // This is to be run on the main thread + public abstract void execute(RileyLinkBLE comm); + + + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + } + + + public int getGattOperationTimeout_ms() { + return 22000; + } + + + public byte[] getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperationResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperationResult.java new file mode 100644 index 00000000000..06f1e4e94e0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/BLECommOperationResult.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations; + +/** + * Created by geoff on 5/26/16. + */ +public class BLECommOperationResult { + + public static final int RESULT_NONE = 0; + public static final int RESULT_SUCCESS = 1; + public static final int RESULT_TIMEOUT = 2; + public static final int RESULT_BUSY = 3; + public static final int RESULT_INTERRUPTED = 4; + public static final int RESULT_NOT_CONFIGURED = 5; + public byte[] value; + public int resultCode; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicReadOperation.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicReadOperation.java new file mode 100644 index 00000000000..6c12a8ef77b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicReadOperation.java @@ -0,0 +1,71 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.os.SystemClock; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; + +/** + * Created by geoff on 5/26/16. + */ +public class CharacteristicReadOperation extends BLECommOperation { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + + private BluetoothGattCharacteristic characteristic; + + + public CharacteristicReadOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara) { + this.gatt = gatt; + this.characteristic = chara; + } + + + @Override + public void execute(RileyLinkBLE comm) { + gatt.readCharacteristic(characteristic); + // wait here for callback to notify us that value was read. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier + // understanding of the sequence of events. + // success + } else { + LOG.error("Timeout waiting for gatt write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + if (isLogEnabled()) + LOG.error("Interrupted while waiting for gatt write operation to complete"); + interrupted = true; + } + value = characteristic.getValue(); + } + + + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + super.gattOperationCompletionCallback(uuid, value); + if (!characteristic.getUuid().equals(uuid)) { + LOG.error(String.format( + "Completion callback: UUID does not match! out of sequence? Found: %s, should be %s", + GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid))); + } + operationComplete.release(); + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPBTCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicWriteOperation.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicWriteOperation.java new file mode 100644 index 00000000000..488b03af277 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/CharacteristicWriteOperation.java @@ -0,0 +1,74 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.os.SystemClock; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.GattAttributes; + +/** + * Created by geoff on 5/26/16. + */ +public class CharacteristicWriteOperation extends BLECommOperation { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + + private BluetoothGattCharacteristic characteristic; + + + public CharacteristicWriteOperation(BluetoothGatt gatt, BluetoothGattCharacteristic chara, byte[] value) { + this.gatt = gatt; + this.characteristic = chara; + this.value = value; + } + + + @Override + public void execute(RileyLinkBLE comm) { + + characteristic.setValue(value); + gatt.writeCharacteristic(characteristic); + // wait here for callback to notify us that value was written. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier + // understanding of the sequence of events. + // success + } else { + LOG.error("Timeout waiting for gatt write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + LOG.error("Interrupted while waiting for gatt write operation to complete"); + interrupted = true; + } + + } + + + // This will be run on the IBinder thread + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + if (!characteristic.getUuid().equals(uuid)) { + LOG.error(String.format( + "Completion callback: UUID does not match! out of sequence? Found: %s, should be %s", + GattAttributes.lookup(characteristic.getUuid()), GattAttributes.lookup(uuid))); + } + operationComplete.release(); + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPBTCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/DescriptorWriteOperation.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/DescriptorWriteOperation.java new file mode 100644 index 00000000000..9e631c1c7c6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/ble/operations/DescriptorWriteOperation.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.operations; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattDescriptor; +import android.os.SystemClock; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; + +/** + * Created by geoff on 5/26/16. + */ +public class DescriptorWriteOperation extends BLECommOperation { + + private static final Logger LOG = LoggerFactory.getLogger(DescriptorWriteOperation.class); + + private BluetoothGattDescriptor descr; + + + public DescriptorWriteOperation(BluetoothGatt gatt, BluetoothGattDescriptor descr, byte[] value) { + this.gatt = gatt; + this.descr = descr; + this.value = value; + } + + + @Override + public void gattOperationCompletionCallback(UUID uuid, byte[] value) { + super.gattOperationCompletionCallback(uuid, value); + operationComplete.release(); + } + + + @Override + public void execute(RileyLinkBLE comm) { + descr.setValue(value); + gatt.writeDescriptor(descr); + // wait here for callback to notify us that value was read. + try { + boolean didAcquire = operationComplete.tryAcquire(getGattOperationTimeout_ms(), TimeUnit.MILLISECONDS); + if (didAcquire) { + SystemClock.sleep(1); // This is to allow the IBinder thread to exit before we continue, allowing easier + // understanding of the sequence of events. + // success + } else { + LOG.error("Timeout waiting for descriptor write operation to complete"); + timedOut = true; + } + } catch (InterruptedException e) { + LOG.error("Interrupted while waiting for descriptor write operation to complete"); + interrupted = true; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/BleAdvertisedData.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/BleAdvertisedData.java new file mode 100644 index 00000000000..8a2bbdfec76 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/BleAdvertisedData.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data; + +import java.util.List; +import java.util.UUID; + +/** + * Created by andy on 9/10/18. + */ + +public class BleAdvertisedData { + + private List mUuids; + private String mName; + + + public BleAdvertisedData(List uuids, String name) { + mUuids = uuids; + mName = name; + } + + + public List getUuids() { + return mUuids; + } + + + public String getName() { + return mName; + } + + + public String toString() { + return "BleAdvertisedData [name=" + mName + ", UUIDs=" + mUuids + "]"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/CommandValueDefinition.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/CommandValueDefinition.java new file mode 100644 index 00000000000..fceb0609aa6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/CommandValueDefinition.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType; + +/** + * Created by andy on 4/5/19. + */ + +public class CommandValueDefinition { + + public CommandValueDefinitionType definitionType; + public String value; + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java new file mode 100644 index 00000000000..80cad6fa9af --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/data/RLHistoryItem.java @@ -0,0 +1,128 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data; + +import org.joda.time.LocalDateTime; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; + +/** + * Created by andy on 5/19/18. + */ + +public class RLHistoryItem { + + private MedtronicCommandType medtronicCommandType; + private LocalDateTime dateTime; + private RLHistoryItemSource source; + private RileyLinkServiceState serviceState; + private RileyLinkError errorCode; + + private RileyLinkTargetDevice targetDevice; + private PumpDeviceState pumpDeviceState; + private OmnipodCommandType omnipodCommandType; + + + public RLHistoryItem(RileyLinkServiceState serviceState, RileyLinkError errorCode, + RileyLinkTargetDevice targetDevice) { + this.targetDevice = targetDevice; + this.dateTime = new LocalDateTime(); + this.serviceState = serviceState; + this.errorCode = errorCode; + this.source = RLHistoryItemSource.RileyLink; + } + + + public RLHistoryItem(PumpDeviceState pumpDeviceState, RileyLinkTargetDevice targetDevice) { + this.pumpDeviceState = pumpDeviceState; + this.dateTime = new LocalDateTime(); + this.targetDevice = targetDevice; + this.source = RLHistoryItemSource.MedtronicPump; + } + + + public RLHistoryItem(MedtronicCommandType medtronicCommandType) { + this.dateTime = new LocalDateTime(); + this.medtronicCommandType = medtronicCommandType; + source = RLHistoryItemSource.MedtronicCommand; + } + + + public RLHistoryItem(OmnipodCommandType omnipodCommandType) { + this.dateTime = new LocalDateTime(); + this.omnipodCommandType = omnipodCommandType; + source = RLHistoryItemSource.OmnipodCommand; + } + + + public LocalDateTime getDateTime() { + return dateTime; + } + + + public RileyLinkServiceState getServiceState() { + return serviceState; + } + + + public RileyLinkError getErrorCode() { + return errorCode; + } + + + public String getDescription() { + + // TODO extend when we have Omnipod + switch (this.source) { + case RileyLink: + return "State: " + MainApp.gs(serviceState.getResourceId(targetDevice)) + + (this.errorCode == null ? "" : ", Error Code: " + errorCode); + + case MedtronicPump: + return MainApp.gs(pumpDeviceState.getResourceId()); + + case MedtronicCommand: + return medtronicCommandType.name(); + + case OmnipodCommand: + return omnipodCommandType.name(); + + default: + return "Unknown Description"; + } + } + + + public RLHistoryItemSource getSource() { + return source; + } + + + public PumpDeviceState getPumpDeviceState() { + return pumpDeviceState; + } + + public enum RLHistoryItemSource { + RileyLink("RileyLink"), // + MedtronicPump("Medtronic"), // + MedtronicCommand("Medtronic"), // + OmnipodCommand("Omnipod"); + + private String desc; + + + RLHistoryItemSource(String desc) { + this.desc = desc; + } + + + public String getDesc() { + return desc; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionRLType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionRLType.java new file mode 100644 index 00000000000..5be3087b494 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionRLType.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs; + +/** + * Created by andy on 4/5/19. + */ + +public enum CommandValueDefinitionRLType implements CommandValueDefinitionType { + Name, // + Firmware, // + SignalStrength, // + ConnectionState, // + Frequency, // + ; + + @Override + public String getName() { + return this.name(); + } + + + @Override + public String getDescription() { + return null; + } + + + @Override + public String commandAction() { + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionType.java new file mode 100644 index 00000000000..db5c5b2b91f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/CommandValueDefinitionType.java @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs; + +/** + * Created by andy on 4/5/19. + */ + +public interface CommandValueDefinitionType { + + String getName(); + + + String getDescription(); + + + String commandAction(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkError.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkError.java new file mode 100644 index 00000000000..07867a781aa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkError.java @@ -0,0 +1,52 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 14/05/2018. + */ + +public enum RileyLinkError { + + // Configuration + + // BT + NoBluetoothAdapter(R.string.rileylink_error_no_bt_adapter), // + BluetoothDisabled(R.string.rileylink_error_bt_disabled), // + + // RileyLink + RileyLinkUnreachable(R.string.rileylink_error_unreachable), // + DeviceIsNotRileyLink(R.string.rileylink_error_not_rl), // + + // Device + TuneUpOfDeviceFailed(R.string.rileylink_error_tuneup_failed), // + NoContactWithDevice(R.string.rileylink_error_pump_unreachable, R.string.rileylink_error_pod_unreachable), // + ; + + int resourceId; + Integer resourceIdPod; + + + RileyLinkError(int resourceId) { + this.resourceId = resourceId; + } + + + RileyLinkError(int resourceId, int resourceIdPod) { + this.resourceId = resourceId; + this.resourceIdPod = resourceIdPod; + } + + + public int getResourceId(RileyLinkTargetDevice targetDevice) { + if (this.resourceIdPod != null) { + + return targetDevice == RileyLinkTargetDevice.MedtronicPump ? // + this.resourceId + : this.resourceIdPod; + } else { + return this.resourceId; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkServiceState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkServiceState.java new file mode 100644 index 00000000000..2ae66b53c94 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkServiceState.java @@ -0,0 +1,95 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 14/05/2018. + */ + +public enum RileyLinkServiceState { + + NotStarted(R.string.rileylink_state_not_started), // + + // Bluetooth + BluetoothInitializing(R.string.rileylink_state_bt_init), // (S) init BT (if error no BT interface -> Disabled, BT + // not enabled -> BluetoothError) + // BluetoothNotAvailable, // (E) BT not available, would happen only if device has no BT + BluetoothError(R.string.rileylink_state_bt_error), // (E) if BT gets disabled ( -> EnableBluetooth) + BluetoothReady(R.string.rileylink_state_bt_ready), // (OK) + + // RileyLink + RileyLinkInitializing(R.string.rileylink_state_rl_init), // (S) start Gatt discovery (OK -> RileyLinkReady, Error -> + // BluetoothEnabled) ?? + RileyLinkError(R.string.rileylink_state_rl_error), // (E) + RileyLinkReady(R.string.rileylink_state_connected), // (OK) if tunning was already done we go to PumpConnectorReady + + // Tunning + TuneUpDevice(R.string.rileylink_state_pc_tune_up), // (S) + PumpConnectorError(R.string.rileylink_state_pc_error), // either TuneUp Error or pump couldn't not be contacted + // error + PumpConnectorReady(R.string.rileylink_state_connected), // (OK) RileyLink Ready for Pump Communication + + // Initializing, // get all parameters required for connection (if not possible -> Disabled, if sucessful -> + // EnableBluetooth) + + // EnableBlueTooth, // enable BT (if error no BT interface -> Disabled, BT not enabled -> BluetoothError) + // BlueToothEnabled, // -> InitializeRileyLink + // RileyLinkInitialized, // + + // RileyLinkConnected, // -> TuneUpPump (on 1st), else PumpConnectorReady + + // PumpConnected, // + + ; + + int resourceId; + Integer resourceIdPod; + + + RileyLinkServiceState(int resourceId) { + this.resourceId = resourceId; + } + + + RileyLinkServiceState(int resourceId, int resourceIdPod) { + this.resourceId = resourceId; + this.resourceIdPod = resourceIdPod; + } + + + public static boolean isReady(RileyLinkServiceState serviceState) { + return (/* serviceState == RileyLinkReady || */serviceState == PumpConnectorReady); + } + + + public int getResourceId(RileyLinkTargetDevice targetDevice) { + if (this.resourceIdPod != null) { + + return (targetDevice == null || targetDevice == RileyLinkTargetDevice.MedtronicPump) ? // + this.resourceId + : this.resourceIdPod; + } else { + return this.resourceId; + } + } + + + public boolean isConnecting() { + + return (this == RileyLinkServiceState.BluetoothInitializing || // + // this == RileyLinkServiceState.BluetoothError || // + this == RileyLinkServiceState.BluetoothReady || // + this == RileyLinkServiceState.RileyLinkInitializing || // + this == RileyLinkReady + // this == RileyLinkServiceState.RileyLinkBLEError + ); + } + + + public boolean isError() { + + return (this == RileyLinkServiceState.BluetoothError || // + // this == RileyLinkServiceState.PumpConnectorError || // + this == RileyLinkServiceState.RileyLinkError); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkTargetDevice.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkTargetDevice.java new file mode 100644 index 00000000000..2d0dea69f73 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/defs/RileyLinkTargetDevice.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 5/19/18. + */ + +public enum RileyLinkTargetDevice { + MedtronicPump(R.string.rileylink_target_device_medtronic, true), // + Omnipod(R.string.rileylink_target_device_omnipod, false), // + ; + + private int resourceId; + private boolean tuneUpEnabled; + + + RileyLinkTargetDevice(int resourceId, boolean tuneUpEnabled) { + this.resourceId = resourceId; + this.tuneUpEnabled = tuneUpEnabled; + } + + + public boolean isTuneUpEnabled() { + return tuneUpEnabled; + } + + + public int getResourceId() { + return resourceId; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.java new file mode 100644 index 00000000000..37a6656a6e3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusActivity.java @@ -0,0 +1,161 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog; + +import android.os.Bundle; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.tabs.TabLayout; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; +import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; + +public class RileyLinkStatusActivity extends NoSplashAppCompatActivity { + + TextView connectionStatus; + TextView configuredAddress; + TextView connectedDevice; + TextView connectionError; + RileyLinkServiceData rileyLinkServiceData; + + private SectionsPagerAdapter mSectionsPagerAdapter; + private FloatingActionButton floatingActionButton; + private TabLayout tabLayout; + /** + * The {@link ViewPager} that will host the section contents. + */ + private ViewPager mViewPager; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.rileylink_status); + + // Create the adapter that will return a fragment for each of the three + // primary sections of the activity. + + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) findViewById(R.id.rileylink_settings_container); + // mViewPager.setAdapter(mSectionsPagerAdapter); + setupViewPager(mViewPager); + + tabLayout = (TabLayout) findViewById(R.id.rileylink_settings_tabs); + tabLayout.setupWithViewPager(mViewPager); + + floatingActionButton = (FloatingActionButton) findViewById(R.id.rileylink_settings_fab); + floatingActionButton.setOnClickListener(v -> { + + RefreshableInterface selectableInterface = (RefreshableInterface) mSectionsPagerAdapter + .getItem(tabLayout.getSelectedTabPosition()); + selectableInterface.refreshData(); + + // refreshData(tabLayout.getSelectedTabPosition()); + + // Toast.makeText(getApplicationContext(), "Test pos: " + tabLayout.getSelectedTabPosition(), + // Toast.LENGTH_LONG); + }); + + this.connectionStatus = findViewById(R.id.rls_t1_connection_status); + this.configuredAddress = findViewById(R.id.rls_t1_configured_address); + this.connectedDevice = findViewById(R.id.rls_t1_connected_device); + this.connectionError = findViewById(R.id.rls_t1_connection_error); + + rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData(); + + // // 7-12 + // int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, // + // R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12}; + // + // for (int id : ids) { + // + // TextView tv = (TextView) findViewById(id); + // tv.setText(tv.getText() + ":"); + // } + + // refreshData(0); + // refreshData(1); + + } + + + public void refreshData(int position) { + if (position == 0) { + // FIXME i18n + this.connectionStatus.setText(rileyLinkServiceData.serviceState.name()); + this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress); + // FIXME + this.connectedDevice.setText("???"); + // FIXME i18n + this.connectionError.setText(rileyLinkServiceData.errorCode.name()); + } else { + + } + + } + + + public void setupViewPager(ViewPager pager) { + + mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + + mSectionsPagerAdapter.addFragment(new RileyLinkStatusGeneral(), MainApp.gs(R.string.rileylink_settings_tab1)); + mSectionsPagerAdapter.addFragment(new RileyLinkStatusHistory(), MainApp.gs(R.string.rileylink_settings_tab2)); + //mSectionsPagerAdapter.addFragment(new RileyLinkStatusDevice(), "Medtronic"); + + mViewPager.setAdapter(mSectionsPagerAdapter); + } + + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + List fragmentList = new ArrayList<>(); + List fragmentTitle = new ArrayList<>(); + int lastSelectedPosition = 0; + + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + + @Override + public Fragment getItem(int position) { + this.lastSelectedPosition = position; + return fragmentList.get(position); + } + + + @Override + public int getCount() { + // Show 3 total pages. + return fragmentList.size(); + } + + + public void addFragment(Fragment fragment, String title) { + this.fragmentList.add(fragment); + this.fragmentTitle.add(title); + } + + + @Override + public CharSequence getPageTitle(int position) { + return fragmentTitle.get(position); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusDevice.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusDevice.java new file mode 100644 index 00000000000..226d7fc622f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusDevice.java @@ -0,0 +1,154 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType; +//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.CommandValueDefinition; +//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +//import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType; +//import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; + +/** + * Created by andy on 5/19/18. + */ + +// FIXME needs to be implemented + +public class RileyLinkStatusDevice extends Fragment implements RefreshableInterface { + + ListView listView; + + RileyLinkCommandListAdapter adapter; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.rileylink_status_device, container, false); + + adapter = new RileyLinkCommandListAdapter(); + + return rootView; + } + + + @Override + public void onStart() { + super.onStart(); + + this.listView = (ListView)getActivity().findViewById(R.id.rileyLinkDeviceList); + + listView.setAdapter(adapter); + + setElements(); + } + + + private void setElements() { + + } + + + @Override + public void refreshData() { + // adapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory()); + } + + static class ViewHolder { + + TextView itemDescription; + Button itemValue; + } + + private class RileyLinkCommandListAdapter extends BaseAdapter { + + private List commandValueList; + private Map commandValueMap; + private LayoutInflater mInflator; + + + public RileyLinkCommandListAdapter() { + super(); + commandValueList = new ArrayList<>(); + mInflator = RileyLinkStatusDevice.this.getLayoutInflater(); + } + + + public void addItems(List list) { + commandValueList.addAll(list); + + for (CommandValueDefinition commandValueDefinition : list) { + commandValueMap.put(commandValueDefinition.definitionType, commandValueDefinition); + } + + notifyDataSetChanged(); + } + + + public CommandValueDefinition getCommandValueItem(int position) { + return commandValueList.get(position); + } + + + public void clear() { + commandValueList.clear(); + notifyDataSetChanged(); + } + + + @Override + public int getCount() { + return commandValueList.size(); + } + + + @Override + public Object getItem(int i) { + return commandValueList.get(i); + } + + + @Override + public long getItemId(int i) { + return i; + } + + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + RileyLinkStatusDevice.ViewHolder viewHolder; + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.rileylink_status_device_item, null); + viewHolder = new RileyLinkStatusDevice.ViewHolder(); + viewHolder.itemDescription = (TextView)view.findViewById(R.id.rileylink_device_label); + viewHolder.itemValue = (Button)view.findViewById(R.id.rileylink_device_action); + view.setTag(viewHolder); + } else { + viewHolder = (RileyLinkStatusDevice.ViewHolder)view.getTag(); + } + // Z + // RLHistoryItem item = historyItemList.get(i); + // viewHolder.itemTime.setText(StringUtil.toDateTimeString(item.getDateTime())); + // viewHolder.itemSource.setText("Riley Link"); // for now + // viewHolder.itemDescription.setText(item.getDescription()); + + return view; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusGeneral.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusGeneral.java new file mode 100644 index 00000000000..59938605c4e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusGeneral.java @@ -0,0 +1,185 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; + +import org.joda.time.LocalDateTime; + +import java.util.Locale; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 5/19/18. + */ + +public class RileyLinkStatusGeneral extends Fragment implements RefreshableInterface { + + TextView connectionStatus; + TextView configuredAddress; + TextView connectedDevice; + TextView connectionError; + TextView deviceType; + TextView deviceModel; + TextView serialNumber; + TextView pumpFrequency; + TextView lastUsedFrequency; + TextView lastDeviceContact; + TextView firmwareVersion; + + RileyLinkServiceData rileyLinkServiceData; + + MedtronicPumpStatus medtronicPumpStatus; + OmnipodPumpStatus omnipodPumpStatus; + boolean first = false; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.rileylink_status_general, container, false); + + return rootView; + } + + + @Override + public void onStart() { + super.onStart(); + rileyLinkServiceData = RileyLinkUtil.getRileyLinkServiceData(); + + this.connectionStatus = getActivity().findViewById(R.id.rls_t1_connection_status); + this.configuredAddress = getActivity().findViewById(R.id.rls_t1_configured_address); + this.connectedDevice = getActivity().findViewById(R.id.rls_t1_connected_device); + this.connectionError = getActivity().findViewById(R.id.rls_t1_connection_error); + this.deviceType = getActivity().findViewById(R.id.rls_t1_device_type); + this.deviceModel = getActivity().findViewById(R.id.rls_t1_device_model); + this.serialNumber = getActivity().findViewById(R.id.rls_t1_serial_number); + this.pumpFrequency = getActivity().findViewById(R.id.rls_t1_pump_frequency); + this.lastUsedFrequency = getActivity().findViewById(R.id.rls_t1_last_used_frequency); + this.lastDeviceContact = getActivity().findViewById(R.id.rls_t1_last_device_contact); + this.firmwareVersion = getActivity().findViewById(R.id.rls_t1_firmware_version); + + if (!first) { + + // 7-12 + int[] ids = {R.id.rls_t1_tv02, R.id.rls_t1_tv03, R.id.rls_t1_tv04, R.id.rls_t1_tv05, R.id.rls_t1_tv07, // + R.id.rls_t1_tv08, R.id.rls_t1_tv09, R.id.rls_t1_tv10, R.id.rls_t1_tv11, R.id.rls_t1_tv12, R.id.rls_t1_tv13}; + + for (int id : ids) { + + TextView tv = (TextView) getActivity().findViewById(id); + tv.setText(tv.getText() + ":"); + } + + first = true; + } + + refreshData(); + } + + + public void refreshData() { + + RileyLinkTargetDevice targetDevice = RileyLinkUtil.getTargetDevice(); + + if (RileyLinkUtil.getServiceState() == null) + this.connectionStatus.setText(MainApp.gs(RileyLinkServiceState.NotStarted.getResourceId(targetDevice))); + else + this.connectionStatus.setText(MainApp.gs(RileyLinkUtil.getServiceState().getResourceId(targetDevice))); + + if (rileyLinkServiceData != null) { + this.configuredAddress.setText(rileyLinkServiceData.rileylinkAddress); + this.connectionError.setText(rileyLinkServiceData.errorCode == null ? // + "-" + : MainApp.gs(rileyLinkServiceData.errorCode.getResourceId(targetDevice))); + + + RileyLinkFirmwareVersion firmwareVersion = rileyLinkServiceData.versionCC110; + + if (firmwareVersion == null) { + this.firmwareVersion.setText("BLE113: -\nCC110: -"); + } else { + this.firmwareVersion.setText("BLE113: " + rileyLinkServiceData.versionBLE113 + // + "\nCC110: " + firmwareVersion.toString()); + } + + } + + if (MedtronicUtil.isMedtronicPump()) { + + this.medtronicPumpStatus = MedtronicUtil.getPumpStatus(); + + if (medtronicPumpStatus != null) { + this.deviceType.setText(MainApp.gs(RileyLinkTargetDevice.MedtronicPump.getResourceId())); + this.deviceModel.setText(medtronicPumpStatus.pumpType.getDescription()); + this.serialNumber.setText(medtronicPumpStatus.serialNumber); + this.pumpFrequency.setText(MainApp.gs(medtronicPumpStatus.pumpFrequency.equals("medtronic_pump_frequency_us_ca") ? R.string.medtronic_pump_frequency_us_ca : R.string.medtronic_pump_frequency_worldwide)); + + if (MedtronicUtil.getMedtronicPumpModel() != null) + this.connectedDevice.setText("Medtronic " + MedtronicUtil.getMedtronicPumpModel().getPumpModel()); + else + this.connectedDevice.setText("???"); + + if (rileyLinkServiceData.lastGoodFrequency != null) + this.lastUsedFrequency.setText(String.format(Locale.ENGLISH, "%.2f MHz", + rileyLinkServiceData.lastGoodFrequency)); + + if (medtronicPumpStatus.lastConnection != 0) + this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime( + medtronicPumpStatus.lastDataTime))); + else + this.lastDeviceContact.setText(MainApp.gs(R.string.common_never)); + } + } else { + + // if (OmnipodUtil.isOmnipodDash()) + // TODO add handling for Omnipod Dash pump status + this.omnipodPumpStatus = OmnipodUtil.getPumpStatus(); + + if (omnipodPumpStatus != null) { + this.deviceType.setText(MainApp.gs(RileyLinkTargetDevice.Omnipod.getResourceId())); + this.deviceModel.setText(omnipodPumpStatus.pumpType == PumpType.Insulet_Omnipod ? "Eros" : "Dash"); + this.pumpFrequency.setText(MainApp.gs(R.string.omnipod_frequency)); + + if (omnipodPumpStatus.podAvailable) { + this.serialNumber.setText(omnipodPumpStatus.podLotNumber); + this.connectedDevice.setText(omnipodPumpStatus.pumpType == PumpType.Insulet_Omnipod ? "Eros Pod" : "Dash Pod"); + } else { + this.serialNumber.setText("??"); + this.connectedDevice.setText("-"); + } + + if (rileyLinkServiceData.lastGoodFrequency != null) + this.lastUsedFrequency.setText(String.format(Locale.ENGLISH, "%.2f MHz", + rileyLinkServiceData.lastGoodFrequency)); + + if (omnipodPumpStatus.lastConnection != 0) + this.lastDeviceContact.setText(StringUtil.toDateTimeString(new LocalDateTime( + omnipodPumpStatus.lastDataTime))); + else + this.lastDeviceContact.setText(MainApp.gs(R.string.common_never)); + } + + } + + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusHistory.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusHistory.java new file mode 100644 index 00000000000..d6af7995507 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/dialog/RileyLinkStatusHistory.java @@ -0,0 +1,161 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog; + +import java.util.ArrayList; +import java.util.List; + +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; + +/** + * Created by andy on 5/19/18. + */ + +public class RileyLinkStatusHistory extends Fragment implements RefreshableInterface { + + RecyclerView recyclerView; + RecyclerViewAdapter recyclerViewAdapter; + + LinearLayoutManager llm; + List filteredHistoryList = new ArrayList<>(); + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.rileylink_status_history, container, false); + + recyclerView = (RecyclerView)rootView.findViewById(R.id.rileylink_history_list); + + recyclerView.setHasFixedSize(true); + llm = new LinearLayoutManager(getActivity().getApplicationContext()); + recyclerView.setLayoutManager(llm); + + recyclerViewAdapter = new RecyclerViewAdapter(filteredHistoryList); + recyclerView.setAdapter(recyclerViewAdapter); + + return rootView; + } + + + @Override + public void onStart() { + super.onStart(); + + refreshData(); + } + + + @Override + public void refreshData() { + if (RileyLinkUtil.getRileyLinkHistory()!=null) { + recyclerViewAdapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory()); + } + } + + + public static class RecyclerViewAdapter extends RecyclerView.Adapter { + + List historyList; + + + RecyclerViewAdapter(List historyList) { + this.historyList = historyList; + } + + + public void setHistoryList(List historyList) { + this.historyList = historyList; + } + + + public void addItemsAndClean(List items) { + this.historyList.clear(); + + for (RLHistoryItem item : items) { + + if (!historyList.contains(item) && isValidItem(item)) { + historyList.add(item); + } + } + + notifyDataSetChanged(); + } + + + private boolean isValidItem(RLHistoryItem item) { + + PumpDeviceState pumpState = item.getPumpDeviceState(); + + if ((pumpState != null) && // + (pumpState == PumpDeviceState.Sleeping || // + pumpState == PumpDeviceState.Active || // + pumpState == PumpDeviceState.WakingUp // + )) + return false; + + return true; + + } + + + @Override + public RecyclerViewAdapter.HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rileylink_status_history_item, // + viewGroup, false); + return new RecyclerViewAdapter.HistoryViewHolder(v); + } + + + @Override + public void onBindViewHolder(RecyclerViewAdapter.HistoryViewHolder holder, int position) { + RLHistoryItem item = historyList.get(position); + + if (item != null) { + holder.timeView.setText(StringUtil.toDateTimeString(item.getDateTime())); + holder.typeView.setText(item.getSource().getDesc()); + holder.valueView.setText(item.getDescription()); + } + } + + + @Override + public int getItemCount() { + return historyList.size(); + } + + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + } + + static class HistoryViewHolder extends RecyclerView.ViewHolder { + + TextView timeView; + TextView typeView; + TextView valueView; + + + HistoryViewHolder(View itemView) { + super(itemView); + + timeView = (TextView)itemView.findViewById(R.id.rileylink_history_time); + typeView = (TextView)itemView.findViewById(R.id.rileylink_history_source); + valueView = (TextView)itemView.findViewById(R.id.rileylink_history_description); + } + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBluetoothStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBluetoothStateReceiver.java new file mode 100644 index 00000000000..78b7def1afb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBluetoothStateReceiver.java @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; + +public class RileyLinkBluetoothStateReceiver extends BroadcastReceiver { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump(); + + if (action != null && activePump != null) { + + final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + switch (state) { + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + case BluetoothAdapter.STATE_TURNING_ON: + break; + + case BluetoothAdapter.STATE_ON: { + LOG.debug("RileyLinkBluetoothStateReceiver: Bluetooth back on. Sending broadcast to RileyLink Framework"); + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.Intents.BluetoothReconnected); + } + break; + } + } + } + + + public void unregisterBroadcasts() { + MainApp.instance().unregisterReceiver(this); + } + + + public void registerBroadcasts() { + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + MainApp.instance().registerReceiver(this, filter); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBroadcastReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBroadcastReceiver.java new file mode 100644 index 00000000000..d8a897955ec --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkBroadcastReceiver.java @@ -0,0 +1,260 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service; + +/** + * Created by andy on 10/23/18. + */ + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.DiscoverGattServicesTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.InitializePumpManagerTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask; +import info.nightscout.androidaps.utils.SP; + +/** + * I added this class outside of RileyLinkService, because for now it's very important part of RL framework and + * where we get a lot of problems. Especially merging between AAPS and RileyLinkAAPS. I might put it back at + * later time + */ +public class RileyLinkBroadcastReceiver extends BroadcastReceiver { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + RileyLinkService serviceInstance; + protected Map> broadcastIdentifiers = null; + String deviceSpecificPrefix; + Context context; + + + public RileyLinkBroadcastReceiver(RileyLinkService serviceInstance, Context context) { + this.serviceInstance = serviceInstance; + this.context = context; + + createBroadcastIdentifiers(); + } + + + private void createBroadcastIdentifiers() { + + this.broadcastIdentifiers = new HashMap<>(); + + // Bluetooth + this.broadcastIdentifiers.put("Bluetooth", Arrays.asList( // + RileyLinkConst.Intents.BluetoothConnected, // + RileyLinkConst.Intents.BluetoothReconnected)); + + // TuneUp + this.broadcastIdentifiers.put("TuneUp", Arrays.asList( // + RileyLinkConst.IPC.MSG_PUMP_tunePump, // + RileyLinkConst.IPC.MSG_PUMP_quickTune)); + + // RileyLink + this.broadcastIdentifiers.put("RileyLink", Arrays.asList( // + RileyLinkConst.Intents.RileyLinkDisconnected, // + RileyLinkConst.Intents.RileyLinkReady, // + RileyLinkConst.Intents.RileyLinkDisconnected, // + RileyLinkConst.Intents.RileyLinkNewAddressSet, // + RileyLinkConst.Intents.RileyLinkDisconnect)); + + // Device Specific + deviceSpecificPrefix = serviceInstance.getDeviceSpecificBroadcastsIdentifierPrefix(); + + // Application specific + + } + + + @Override + public void onReceive(Context context, Intent intent) { + + if (intent == null) { + LOG.error("onReceive: received null intent"); + } else { + String action = intent.getAction(); + if (action == null) { + LOG.error("onReceive: null action"); + } else { + if (isLoggingEnabled()) + LOG.debug("Received Broadcast: " + action); + + if (!processBluetoothBroadcasts(action) && // + !processRileyLinkBroadcasts(action) && // + !processTuneUpBroadcasts(action) && // + !processDeviceSpecificBroadcasts(action, intent) && // + !processApplicationSpecificBroadcasts(action, intent) // + ) { + LOG.error("Unhandled broadcast: action=" + action); + } + } + } + } + + + public void registerBroadcasts() { + + IntentFilter intentFilter = new IntentFilter(); + + for (Map.Entry> stringListEntry : broadcastIdentifiers.entrySet()) { + + for (String intentKey : stringListEntry.getValue()) { + intentFilter.addAction(intentKey); + } + } + + if (deviceSpecificPrefix != null) { + serviceInstance.registerDeviceSpecificBroadcasts(intentFilter); + } + + LocalBroadcastManager.getInstance(context).registerReceiver(this, intentFilter); + } + + + private boolean processRileyLinkBroadcasts(String action) { + + if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnected)) { + if (BluetoothAdapter.getDefaultAdapter().isEnabled()) { + RileyLinkUtil + .setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.RileyLinkUnreachable); + } else { + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled); + } + + return true; + } else if (action.equals(RileyLinkConst.Intents.RileyLinkReady)) { + + if (isLoggingEnabled()) + LOG.warn("MedtronicConst.Intents.RileyLinkReady"); + // sendIPCNotification(RT2Const.IPC.MSG_note_WakingPump); + + if (this.serviceInstance.rileyLinkBLE == null) + return false; + + this.serviceInstance.rileyLinkBLE.enableNotifications(); + this.serviceInstance.rfspy.startReader(); // call startReader from outside? + + this.serviceInstance.rfspy.initializeRileyLink(); + String bleVersion = this.serviceInstance.rfspy.getBLEVersionCached(); + RileyLinkFirmwareVersion rlVersion = this.serviceInstance.rfspy.getRLVersionCached(); + +// if (isLoggingEnabled()) + LOG.debug("RfSpy version (BLE113): " + bleVersion); + this.serviceInstance.rileyLinkServiceData.versionBLE113 = bleVersion; + +// if (isLoggingEnabled()) + LOG.debug("RfSpy Radio version (CC110): " + rlVersion.name()); + this.serviceInstance.rileyLinkServiceData.versionCC110 = rlVersion; + + ServiceTask task = new InitializePumpManagerTask(RileyLinkUtil.getTargetDevice()); + ServiceTaskExecutor.startTask(task); + if (isLoggingEnabled()) + LOG.info("Announcing RileyLink open For business"); + + return true; + } else if (action.equals(RileyLinkConst.Intents.RileyLinkNewAddressSet)) { + String RileylinkBLEAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, ""); + if (RileylinkBLEAddress.equals("")) { + LOG.error("No Rileylink BLE Address saved in app"); + } else { + // showBusy("Configuring Service", 50); + // rileyLinkBLE.findRileyLink(RileylinkBLEAddress); + this.serviceInstance.reconfigureRileyLink(RileylinkBLEAddress); + // MainApp.getServiceClientConnection().setThisRileylink(RileylinkBLEAddress); + } + + return true; + } else if (action.equals(RileyLinkConst.Intents.RileyLinkDisconnect)) { + this.serviceInstance.disconnectRileyLink(); + + return true; + } else { + return false; + } + + } + + + public boolean processBluetoothBroadcasts(String action) { + + if (action.equals(RileyLinkConst.Intents.BluetoothConnected)) { + if (isLoggingEnabled()) + LOG.debug("Bluetooth - Connected"); + ServiceTaskExecutor.startTask(new DiscoverGattServicesTask()); + + return true; + + } else if (action.equals(RileyLinkConst.Intents.BluetoothReconnected)) { + if (isLoggingEnabled()) + LOG.debug("Bluetooth - Reconnecting"); + + serviceInstance.bluetoothInit(); + ServiceTaskExecutor.startTask(new DiscoverGattServicesTask(true)); + + return true; + } else { + + return false; + } + + } + + + private boolean processTuneUpBroadcasts(String action) { + + if (this.broadcastIdentifiers.get("TuneUp").contains(action)) { + if (serviceInstance.getRileyLinkTargetDevice().isTuneUpEnabled()) { + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + } + return true; + } else { + return false; + } + } + + + public boolean processDeviceSpecificBroadcasts(String action, Intent intent) { + + if (this.deviceSpecificPrefix == null) { + return false; + } + + if (action.startsWith(this.deviceSpecificPrefix)) { + return this.serviceInstance.handleDeviceSpecificBroadcasts(intent); + } else + return false; + } + + + public boolean processApplicationSpecificBroadcasts(String action, Intent intent) { + return false; + } + + + public boolean isLoggingEnabled() { + return (L.isEnabled(L.PUMPCOMM)); + } + + public void unregisterBroadcasts() { + LocalBroadcastManager.getInstance(context).unregisterReceiver(this); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkService.java new file mode 100644 index 00000000000..29781d191b0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkService.java @@ -0,0 +1,289 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service; + +import static info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil.getRileyLinkCommunicationManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceResult; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 5/6/18. + * Split from original file and renamed. + */ +public abstract class RileyLinkService extends Service { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public RileyLinkBLE rileyLinkBLE; // android-bluetooth management + protected BluetoothAdapter bluetoothAdapter; + protected RFSpy rfspy; // interface for RL xxx Mhz radio. + protected Context context; + protected RileyLinkBroadcastReceiver mBroadcastReceiver; + protected RileyLinkServiceData rileyLinkServiceData; + protected RileyLinkBluetoothStateReceiver bluetoothStateReceiver; + + public RileyLinkService(Context context) { + super(); + this.context = context; + RileyLinkUtil.setContext(this.context); + RileyLinkUtil.setRileyLinkService(this); + RileyLinkUtil.setEncoding(getEncoding()); + initRileyLinkServiceData(); + } + + + /** + * Get Encoding for RileyLink communication + */ + public abstract RileyLinkEncodingType getEncoding(); + + + /** + * If you have customized RileyLinkServiceData you need to override this + */ + public abstract void initRileyLinkServiceData(); + + + @Override + public boolean onUnbind(Intent intent) { + //LOG.warn("onUnbind"); + return super.onUnbind(intent); + } + + + @Override + public void onRebind(Intent intent) { + //LOG.warn("onRebind"); + super.onRebind(intent); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + //LOG.error("I die! I die!"); + + if (rileyLinkBLE != null) { + rileyLinkBLE.disconnect(); // dispose of Gatt (disconnect and close) + rileyLinkBLE = null; + } + + if (mBroadcastReceiver!=null) { + mBroadcastReceiver.unregisterBroadcasts(); + } + + if (bluetoothStateReceiver!=null) { + bluetoothStateReceiver.unregisterBroadcasts(); + } + + } + + + @Override + public void onCreate() { + super.onCreate(); + //LOG.debug("onCreate"); + + mBroadcastReceiver = new RileyLinkBroadcastReceiver(this, this.context); + mBroadcastReceiver.registerBroadcasts(); + + + bluetoothStateReceiver = new RileyLinkBluetoothStateReceiver(); + bluetoothStateReceiver.registerBroadcasts(); + + //LOG.debug("onCreate(): It's ALIVE!"); + } + + + /** + * Prefix for Device specific broadcast identifier prefix (for example MSG_PUMP_ for pump or + * MSG_POD_ for Omnipod) + * + * @return + */ + public abstract String getDeviceSpecificBroadcastsIdentifierPrefix(); + + + public abstract boolean handleDeviceSpecificBroadcasts(Intent intent); + + + public abstract void registerDeviceSpecificBroadcasts(IntentFilter intentFilter); + + + public abstract RileyLinkCommunicationManager getDeviceCommunicationManager(); + + + // Here is where the wake-lock begins: + // We've received a service startCommand, we grab the lock. + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + RileyLinkUtil.setContext(getApplicationContext()); + return (START_STICKY); + } + + + public boolean bluetoothInit() { + if (isLogEnabled()) + LOG.debug("bluetoothInit: attempting to get an adapter"); + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing); + + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (bluetoothAdapter == null) { + LOG.error("Unable to obtain a BluetoothAdapter."); + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.NoBluetoothAdapter); + } else { + + if (!bluetoothAdapter.isEnabled()) { + LOG.error("Bluetooth is not enabled."); + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothError, RileyLinkError.BluetoothDisabled); + } else { + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady); + return true; + } + } + + return false; + } + + + // returns true if our Rileylink configuration changed + public boolean reconfigureRileyLink(String deviceAddress) { + + if (rileyLinkBLE == null) { + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothInitializing); + return false; + } + + RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkInitializing); + + if (rileyLinkBLE.isConnected()) { + if (deviceAddress.equals(rileyLinkServiceData.rileylinkAddress)) { + if (isLogEnabled()) + LOG.info("No change to RL address. Not reconnecting."); + return false; + } else { + if (isLogEnabled()) + LOG.warn("Disconnecting from old RL (" + rileyLinkServiceData.rileylinkAddress + + "), reconnecting to new: " + deviceAddress); + + rileyLinkBLE.disconnect(); + // prolly need to shut down listening thread too? + // SP.putString(MedtronicConst.Prefs.RileyLinkAddress, deviceAddress); + + rileyLinkServiceData.rileylinkAddress = deviceAddress; + rileyLinkBLE.findRileyLink(rileyLinkServiceData.rileylinkAddress); + return true; + } + } else { + if (isLogEnabled()) + LOG.debug("Using RL " + deviceAddress); + + if (RileyLinkUtil.getServiceState() == RileyLinkServiceState.NotStarted) { + if (!bluetoothInit()) { + LOG.error("RileyLink can't get activated, Bluetooth is not functioning correctly. {}", + RileyLinkUtil.getError() != null ? RileyLinkUtil.getError().name() : "Unknown error (null)"); + return false; + } + } + + rileyLinkBLE.findRileyLink(deviceAddress); + + return true; + } + } + + + public void sendServiceTransportResponse(ServiceTransport transport, ServiceResult serviceResult) { + } + + + // FIXME: This needs to be run in a session so that is interruptable, has a separate thread, etc. + public void doTuneUpDevice() { + + RileyLinkUtil.setServiceState(RileyLinkServiceState.TuneUpDevice); + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + double lastGoodFrequency = 0.0d; + + if (rileyLinkServiceData.lastGoodFrequency == null) { + lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d); + } else { + lastGoodFrequency = rileyLinkServiceData.lastGoodFrequency; + } + + double newFrequency; + + newFrequency = getDeviceCommunicationManager().tuneForDevice(); + + if ((newFrequency != 0.0) && (newFrequency != lastGoodFrequency)) { + if (isLogEnabled()) + LOG.info("Saving new pump frequency of {} MHz", newFrequency); + SP.putDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, newFrequency); + rileyLinkServiceData.lastGoodFrequency = newFrequency; + rileyLinkServiceData.tuneUpDone = true; + rileyLinkServiceData.lastTuneUpTime = System.currentTimeMillis(); + } + + if (newFrequency == 0.0d) { + // error tuning pump, pump not present ?? + RileyLinkUtil + .setServiceState(RileyLinkServiceState.PumpConnectorError, RileyLinkError.TuneUpOfDeviceFailed); + } else { + getRileyLinkCommunicationManager().clearNotConnectedCount(); + RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady); + } + } + + + public void disconnectRileyLink() { + + if (this.rileyLinkBLE != null && this.rileyLinkBLE.isConnected()) { + this.rileyLinkBLE.disconnect(); + rileyLinkServiceData.rileylinkAddress = null; + } + + RileyLinkUtil.setServiceState(RileyLinkServiceState.BluetoothReady); + } + + + /** + * Get Target Device for Service + */ + public RileyLinkTargetDevice getRileyLinkTargetDevice() { + return this.rileyLinkServiceData.targetDevice; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + + public void changeRileyLinkEncoding(RileyLinkEncodingType encodingType) { + if (rfspy != null) { + rfspy.setRileyLinkEncoding(encodingType); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkServiceData.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkServiceData.java new file mode 100644 index 00000000000..dd66255da4a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/RileyLinkServiceData.java @@ -0,0 +1,43 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkFirmwareVersion; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; + +/** + * Created by andy on 16/05/2018. + */ + +public class RileyLinkServiceData { + + public boolean tuneUpDone = false; + public RileyLinkError errorCode; + public RileyLinkServiceState serviceState = RileyLinkServiceState.NotStarted; + public String rileylinkAddress; + public long lastTuneUpTime = 0L; + public Double lastGoodFrequency; + + // bt version + public String versionBLE113; + // radio version + public RileyLinkFirmwareVersion versionCC110; + + public RileyLinkTargetDevice targetDevice; + + // Medtronic Pump + public String pumpID; + public byte[] pumpIDBytes; + + + public RileyLinkServiceData(RileyLinkTargetDevice targetDevice) { + this.targetDevice = targetDevice; + } + + + public void setPumpID(String pumpId, byte[] pumpIdBytes) { + this.pumpID = pumpId; + this.pumpIDBytes = pumpIdBytes; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceCommand.java new file mode 100644 index 00000000000..4f43013328e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceCommand.java @@ -0,0 +1,67 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +import android.os.Bundle; + +/** + * Created by geoff on 6/25/16. + */ +public class ServiceCommand extends ServiceMessage { + + public ServiceCommand() { + map = new Bundle(); + } + + + // commandID is a string that the client can set on the message. + // The service does not use this value, but passes it back with the result + // so that the client can identify it. + public ServiceCommand(String commandName, String commandID) { + init(); + map.putString("command", commandName); + map.putString("commandID", commandID); + } + + + public ServiceCommand(Bundle commandBundle) { + if (commandBundle != null) { + map = commandBundle; + } else { + map = new Bundle(); + init(); + map.putString("command", "(null)"); + map.putString("commandID", "(null"); + } + } + + + @Override + public void init() { + map.putString("ServiceMessageType", "ServiceCommand"); + } + + + public String getCommandID() { + return map.getString("commandID"); + } + + + public String getCommandName() { + return map.getString("command"); + } + + + public boolean isPumpCommand() { + switch (getCommandName()) { + case "FetchPumpHistory": + case "ReadPumpClock": + case "RetrieveHistoryPage": + case "ReadISFProfile": + case "ReadBolusWizardCarbProfile": + case "UpdatePumpStatus": + case "WakeAndTune": + return true; + default: + return false; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceMessage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceMessage.java new file mode 100644 index 00000000000..83e2f57adec --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceMessage.java @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +import android.os.Bundle; + +/** + * Created by geoff on 7/4/16. + *

+ * Base class for all messages passed between service and client + */ +public class ServiceMessage { + + protected Bundle map = new Bundle(); + + + public ServiceMessage() { + init(); + } + + + public void init() { + map.putString("ServiceMessageClass", this.getClass().getCanonicalName()); + map.putString("ServiceMessageType", this.getClass().getSimpleName()); + } + + + public Bundle getMap() { + return map; + } + + + public void setMap(Bundle map) { + this.map = map; + } + + + public String getServiceMessageType() { + return map.getString("ServiceMessageType"); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceNotification.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceNotification.java new file mode 100644 index 00000000000..aad45093f1c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceNotification.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +import android.os.Bundle; + +/** + * Created by geoff on 7/6/16. + *

+ * These are "one liner" messages between client and service. Must still be contained within ServiceTransports + */ +public class ServiceNotification extends ServiceMessage { + + public ServiceNotification() { + } + + + public ServiceNotification(Bundle b) { + if (b != null) { + if ("ServiceNotification".equals(b.getString("ServiceMessageType"))) { + setMap(b); + } else { + throw new IllegalArgumentException(); + } + } + } + + + public ServiceNotification(String notificationType) { + setNotificationType(notificationType); + } + + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType", "ServiceNotification"); + } + + + public String getNotificationType() { + return map.getString("NotificationType", ""); + } + + + public void setNotificationType(String notificationType) { + map.putString("NotificationType", notificationType); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceResult.java new file mode 100644 index 00000000000..ecf7d54f99a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceResult.java @@ -0,0 +1,96 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +import android.os.Bundle; + +/** + * Created by geoff on 6/25/16. + */ +public class ServiceResult extends ServiceMessage { + + public static final int ERROR_MALFORMED_PUMP_RESPONSE = 1; + public static final int ERROR_NULL_PUMP_RESPONSE = 2; + public static final int ERROR_INVALID_PUMP_RESPONSE = 3; + public static final int ERROR_PUMP_BUSY = 4; + + + public ServiceResult() { + init(); + } + + + public ServiceResult(Bundle resultBundle) { + if (resultBundle != null) { + setMap(resultBundle); + } else { + init(); + } + } + + + public static final String getErrorDescription(int errorCode) { + switch (errorCode) { + case ERROR_MALFORMED_PUMP_RESPONSE: + return "Malformed Pump Response"; + case ERROR_NULL_PUMP_RESPONSE: + return "Null pump response"; + case ERROR_INVALID_PUMP_RESPONSE: + return "Invalid pump response"; + case ERROR_PUMP_BUSY: + return "A pump command session is already in progress"; + default: + return "Unknown error code (" + errorCode + ")"; + } + } + + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType", "ServiceResult"); + setServiceResultType(this.getClass().getSimpleName()); + setResultError(0, "Uninitialized ServiceResult"); + } + + + public String getServiceResultType() { + return map.getString("ServiceResultType", "ServiceResult"); + } + + + public void setServiceResultType(String serviceResultType) { + map.putString("ServiceResultType", serviceResultType); + } + + + public void setResultOK() { + map.putString("result", "OK"); + } + + + public void setResultError(int errorCode) { + setResultError(errorCode, getErrorDescription(errorCode)); + } + + + public void setResultError(int errorCode, String errorDescription) { + map.putString("result", "error"); + map.putInt("errorCode", errorCode); + map.putString("errorDescription", errorDescription); + } + + + public boolean resultIsOK() { + return ("OK".equals(map.getString("result", ""))); + } + + + public String getErrorDescription() { + return map.getString("errorDescription", ""); + } + + + public String getResult() { + return map.getString("result", ""); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransport.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransport.java new file mode 100644 index 00000000000..c2043708fea --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransport.java @@ -0,0 +1,150 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +import android.os.Bundle; +import android.os.Parcel; + +/** + * Created by geoff on 7/6/16. + *

+ * This class exists to hold a ServiceCommand along with transport variables such as time sent, time received, sender. + * May also contain result, if the command is completed. + */ +public class ServiceTransport extends ServiceMessage { + + private ServiceTransportType serviceTransportType = ServiceTransportType.Undefined; + + + public ServiceTransport() { + } + + + public ServiceTransport(Bundle b) { + if (b != null) { + if ("ServiceTransport".equals(b.getString("ServiceMessageType"))) { + setMap(b); + } else { + throw new IllegalArgumentException(); + } + } + } + + + @Override + public void init() { + super.init(); + map.putString("ServiceMessageType", "ServiceTransport"); + setTransportType("unknown"); + setSenderHashcode(0); + } + + + public Integer getSenderHashcode() { + return map.getInt("senderHashCode", 0); + } + + + public void setSenderHashcode(Integer senderHashcode) { + map.putInt("senderHashcode", senderHashcode); + } + + + public ServiceCommand getServiceCommand() { + return new ServiceCommand(map.getBundle("ServiceCommand")); + } + + + public void setServiceCommand(ServiceCommand serviceCommand) { + map.putBundle("ServiceCommand", serviceCommand.getMap()); + this.serviceTransportType = ServiceTransportType.ServiceCommand; + } + + + public boolean hasServiceCommand() { + return (getMap().containsKey("ServiceCommand")); + } + + + public String getTransportType() { + return map.getString("transportType", "unknown"); + } + + + // On remote end, this will be converted to the "action" of a local Intent, + // so can be used for separating types of messages to different internal handlers. + public void setTransportType(String transportType) { + map.putString("transportType", transportType); + } + + + public ServiceResult getServiceResult() { + return new ServiceResult(map.getBundle("ServiceResult")); + } + + + public void setServiceResult(ServiceResult serviceResult) { + map.putBundle("ServiceResult", serviceResult.getMap()); + this.serviceTransportType = ServiceTransportType.ServiceResult; + } + + + public boolean hasServiceResult() { + return (getMap().containsKey("ServiceResult")); + } + + + public ServiceNotification getServiceNotification() { + return new ServiceNotification(map.getBundle("ServiceNotification")); + } + + + public void setServiceNotification(ServiceNotification notification) { + map.putBundle("ServiceNotification", notification.getMap()); + this.serviceTransportType = ServiceTransportType.ServiceNotification; + } + + + public boolean hasServiceNotification() { + return (map.containsKey("ServiceNotification")); + } + + + public boolean commandDidCompleteOK() { + return getServiceResult().resultIsOK(); + } + + + public String getOriginalCommandName() { + return getServiceCommand().getCommandName(); + } + + + public String describeContentsShort() { + String rval = ""; + rval += getTransportType(); + + if (this.serviceTransportType == ServiceTransportType.ServiceNotification) { + rval += "note: " + getServiceNotification().getNotificationType(); + } else if (this.serviceTransportType == ServiceTransportType.ServiceCommand) { + rval += ", cmd=" + getOriginalCommandName(); + } else if (this.serviceTransportType == ServiceTransportType.ServiceResult) { + rval += ", cmd=" + getOriginalCommandName(); + rval += ", rslt=" + getServiceResult().getResult(); + rval += ", err=" + getServiceResult().getErrorDescription(); + } + return rval; + } + + + public ServiceTransport clone() { + Parcel p = Parcel.obtain(); + Parcel p2 = Parcel.obtain(); + getMap().writeToParcel(p, 0); + byte[] bytes = p.marshall(); + p2.unmarshall(bytes, 0, bytes.length); + p2.setDataPosition(0); + Bundle b = p2.readBundle(); + ServiceTransport rval = new ServiceTransport(); + rval.setMap(b); + return rval; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransportType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransportType.java new file mode 100644 index 00000000000..135c2179961 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/data/ServiceTransportType.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data; + +/** + * Created by andy on 31/05/18. + */ + +public enum ServiceTransportType { + + Undefined, // + ServiceNotification, // + + ServiceCommand, // + ServiceResult + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/DiscoverGattServicesTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/DiscoverGattServicesTask.java new file mode 100644 index 00000000000..5f807c3e896 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/DiscoverGattServicesTask.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; + +/** + * Created by geoff on 7/9/16. + */ +public class DiscoverGattServicesTask extends ServiceTask { + + public boolean needToConnect = false; + + + public DiscoverGattServicesTask() { + } + + + public DiscoverGattServicesTask(boolean needToConnect) { + this.needToConnect = needToConnect; + } + + + @Override + public void run() { + + if (needToConnect) + RileyLinkUtil.getRileyLinkBLE().connectGatt(); + + RileyLinkUtil.getRileyLinkBLE().discoverServices(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/InitializePumpManagerTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/InitializePumpManagerTask.java new file mode 100644 index 00000000000..64318cf0bda --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/InitializePumpManagerTask.java @@ -0,0 +1,112 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import android.util.Log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by geoff on 7/9/16. + *

+ * This class is intended to be run by the Service, for the Service. Not intended for clients to run. + */ +public class InitializePumpManagerTask extends ServiceTask { + + private static final String TAG = "InitPumpManagerTask"; + private RileyLinkTargetDevice targetDevice; + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public InitializePumpManagerTask(RileyLinkTargetDevice targetDevice) { + super(); + this.targetDevice = targetDevice; + } + + + public InitializePumpManagerTask(ServiceTransport transport) { + super(transport); + } + + + @Override + public void run() { + + double lastGoodFrequency = 0.0d; + + if (RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency == null) { + + lastGoodFrequency = SP.getDouble(RileyLinkConst.Prefs.LastGoodDeviceFrequency, 0.0d); + lastGoodFrequency = Math.round(lastGoodFrequency * 1000d) / 1000d; + + RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency = lastGoodFrequency; + +// if (RileyLinkUtil.getRileyLinkTargetFrequency() == null) { +// String pumpFrequency = SP.getString(MedtronicConst.Prefs.PumpFrequency, null); +// } + } else { + lastGoodFrequency = RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency; + } + + + if (MedtronicUtil.isMedtronicPump()) { + + if ((lastGoodFrequency > 0.0d) + && RileyLinkUtil.getRileyLinkCommunicationManager().isValidFrequency(lastGoodFrequency)) { + + RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkReady); + + if (L.isEnabled(L.PUMPCOMM)) + LOG.info("Setting radio frequency to {} MHz", lastGoodFrequency); + + RileyLinkUtil.getRileyLinkCommunicationManager().setRadioFrequencyForPump(lastGoodFrequency); + + boolean foundThePump = RileyLinkUtil.getRileyLinkCommunicationManager().tryToConnectToDevice(); + + if (foundThePump) { + RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady); + } else { + RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorError, + RileyLinkError.NoContactWithDevice); + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump); + } + + } else { + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump); + } + } else { + + if (!RileyLinkUtil.isSame(lastGoodFrequency, RileyLinkTargetFrequency.Omnipod.getScanFrequencies()[0])) { + lastGoodFrequency = RileyLinkTargetFrequency.Omnipod.getScanFrequencies()[0]; + lastGoodFrequency = Math.round(lastGoodFrequency * 1000d) / 1000d; + + RileyLinkUtil.getRileyLinkServiceData().lastGoodFrequency = lastGoodFrequency; + } + + + RileyLinkUtil.setServiceState(RileyLinkServiceState.RileyLinkReady); + RileyLinkUtil.setRileyLinkTargetFrequency(RileyLinkTargetFrequency.Omnipod); + + if (L.isEnabled(L.PUMPCOMM)) + LOG.info("Setting radio frequency to {} MHz", lastGoodFrequency); + + RileyLinkUtil.getRileyLinkCommunicationManager().setRadioFrequencyForPump(lastGoodFrequency); + + + LOG.error("TRYYYYYY TO CONNECT IF AVAILABLE"); + + RileyLinkUtil.setServiceState(RileyLinkServiceState.PumpConnectorReady); + + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/PumpTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/PumpTask.java new file mode 100644 index 00000000000..c307a43590a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/PumpTask.java @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; + +/** + * Created by geoff on 7/10/16. + */ +public class PumpTask extends ServiceTask { + + public PumpTask() { + super(); + } + + + public PumpTask(ServiceTransport transport) { + super(transport); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ResetRileyLinkConfigurationTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ResetRileyLinkConfigurationTask.java new file mode 100644 index 00000000000..6b3da92b567 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ResetRileyLinkConfigurationTask.java @@ -0,0 +1,45 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState; +import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by geoff on 7/16/16. + */ +public class ResetRileyLinkConfigurationTask extends PumpTask { + + private static final String TAG = "ResetRileyLinkTask"; + + + public ResetRileyLinkConfigurationTask() { + } + + + public ResetRileyLinkConfigurationTask(ServiceTransport transport) { + super(transport); + } + + + @Override + public void run() { + RxBus.INSTANCE.send(new EventRefreshButtonState(false)); + if (MedtronicUtil.isMedtronicPump()) { + MedtronicPumpPlugin.isBusy = true; + RileyLinkMedtronicService.getInstance().resetRileyLinkConfiguration(); + MedtronicPumpPlugin.isBusy = false; + } else if (OmnipodUtil.isOmnipodEros()) { + OmnipodPumpPlugin.isBusy = true; + RileyLinkOmnipodService.getInstance().resetRileyLinkConfiguration(); + OmnipodPumpPlugin.isBusy = false; + } + RxBus.INSTANCE.send(new EventRefreshButtonState(true)); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTask.java new file mode 100644 index 00000000000..e287b63e179 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTask.java @@ -0,0 +1,54 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; + +/** + * Created by geoff on 7/9/16. + */ +public class ServiceTask implements Runnable { + + private static final String TAG = "ServiceTask(base)"; + public boolean completed = false; + protected ServiceTransport mTransport; + + + public ServiceTask() { + init(new ServiceTransport()); + } + + + public ServiceTask(ServiceTransport transport) { + init(transport); + } + + + public void init(ServiceTransport transport) { + mTransport = transport; + } + + + @Override + public void run() { + } + + + public void preOp() { + // This function is called by UI thread before running asynch thread. + } + + + public void postOp() { + // This function is called by UI thread after running asynch thread. + } + + + public ServiceTransport getServiceTransport() { + return mTransport; + } + + /* + * protected void sendResponse(ServiceResult result) { + * RoundtripService.getInstance().sendServiceTransportResponse(mTransport,result); + * } + */ +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTaskExecutor.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTaskExecutor.java new file mode 100644 index 00000000000..1c6f33b071e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/ServiceTaskExecutor.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import android.util.Log; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; + +/** + * Created by geoff on 7/9/16. + */ +public class ServiceTaskExecutor extends ThreadPoolExecutor { + + private static final String TAG = "ServiceTaskExecutor"; + private static ServiceTaskExecutor instance; + private static LinkedBlockingQueue taskQueue = new LinkedBlockingQueue<>(); + + static { + instance = new ServiceTaskExecutor(); + } + + + private ServiceTaskExecutor() { + super(1, 1, 10000, TimeUnit.MILLISECONDS, taskQueue); + } + + + public static ServiceTaskExecutor getInstance() { + return instance; + } + + + public static ServiceTask startTask(ServiceTask task) { + instance.execute(task); // task will be run on async thread from pool. + return task; + } + + + // FIXME + protected void beforeExecute(Thread t, Runnable r) { + // This is run on either caller UI thread or Service UI thread. + ServiceTask task = (ServiceTask)r; + Log.v(TAG, "About to run task " + task.getClass().getSimpleName()); + RileyLinkUtil.setCurrentTask(task); + task.preOp(); + } + + + // FIXME + protected void afterExecute(Runnable r, Throwable t) { + // This is run on either caller UI thread or Service UI thread. + ServiceTask task = (ServiceTask)r; + task.postOp(); + Log.v(TAG, "Finishing task " + task.getClass().getSimpleName()); + RileyLinkUtil.finishCurrentTask(task); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/WakeAndTuneTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/WakeAndTuneTask.java new file mode 100644 index 00000000000..5feb5ab4057 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/hw/rileylink/service/tasks/WakeAndTuneTask.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks; + +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.data.ServiceTransport; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState; +import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; + +/** + * Created by geoff on 7/16/16. + */ +public class WakeAndTuneTask extends PumpTask { + + private static final String TAG = "WakeAndTuneTask"; + + + public WakeAndTuneTask() { + } + + + public WakeAndTuneTask(ServiceTransport transport) { + super(transport); + } + + + @Override + public void run() { + RxBus.INSTANCE.send(new EventRefreshButtonState(false)); + MedtronicPumpPlugin.isBusy = true; + RileyLinkMedtronicService.getInstance().doTuneUpDevice(); + MedtronicPumpPlugin.isBusy = false; + RxBus.INSTANCE.send(new EventRefreshButtonState(true)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/ui/RileyLinkSelectPreference.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/ui/RileyLinkSelectPreference.java new file mode 100644 index 00000000000..de9d28d46b8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/ui/RileyLinkSelectPreference.java @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.pump.common.ui; + +import android.content.Context; +import android.preference.Preference; +import android.util.AttributeSet; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 10/18/18. + */ + +public class RileyLinkSelectPreference extends Preference { + + public RileyLinkSelectPreference(Context context) { + super(context); + setInitialSummaryValue(); + + MedtronicUtil.setRileyLinkSelectPreference(this); + } + + + public RileyLinkSelectPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setInitialSummaryValue(); + + MedtronicUtil.setRileyLinkSelectPreference(this); + } + + + private void setInitialSummaryValue() { + String value = SP.getString("pref_rileylink_mac_address", null); + + setSummary(value == null ? MainApp.gs(R.string.rileylink_error_address_not_set_short) : value); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ByteUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ByteUtil.java new file mode 100644 index 00000000000..6923b42020d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ByteUtil.java @@ -0,0 +1,474 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by geoff on 4/28/15. + */ +public class ByteUtil { + + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private final static String HEX_DIGITS_STR = "0123456789ABCDEF"; + + + public static byte highByte(short s) { + return (byte) (s / 256); + } + + + public static byte lowByte(short s) { + return (byte) (s % 256); + } + + + public static int asUINT8(byte b) { + return (b < 0) ? b + 256 : b; + } + + public static byte[] getBytesFromInt16(int value) { + byte[] array = getBytesFromInt(value); + return new byte[] {array[2], array[3]}; + } + + public static byte[] getBytesFromInt(int value) { + return ByteBuffer.allocate(4).putInt(value).array(); + } + + /* For Reference: static void System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) */ + + public static byte[] concat(byte[] a, byte[] b) { + + if (b == null) { + return a; + } + + int aLen = a.length; + int bLen = b.length; + byte[] c = new byte[aLen + bLen]; + System.arraycopy(a, 0, c, 0, aLen); + System.arraycopy(b, 0, c, aLen, bLen); + return c; + } + + + public static byte[] concat(byte[] a, byte b) { + int aLen = a.length; + byte[] c = new byte[aLen + 1]; + System.arraycopy(a, 0, c, 0, aLen); + c[aLen] = b; + return c; + } + + + public static byte[] concat(byte a, byte[] b) { + int aLen = b.length; + byte[] c = new byte[aLen + 1]; + c[0] = a; + System.arraycopy(b, 0, c, 1, aLen); + + return c; + } + + + public static byte[] substring(byte[] a, int start, int len) { + byte[] rval = new byte[len]; + System.arraycopy(a, start, rval, 0, len); + return rval; + } + + public static byte[] substring(List a, int start, int len) { + byte[] rval = new byte[len]; + + for (int i = start, j = 0; i < start + len; i++, j++) { + rval[j] = a.get(i); + } + return rval; + } + + + public static byte[] substring(byte[] a, int start) { + int len = a.length - start; + byte[] rval = new byte[len]; + System.arraycopy(a, start, rval, 0, len); + return rval; + } + + + public static String shortHexString(byte[] ra) { + String rval = ""; + if (ra == null) { + return rval; + } + if (ra.length == 0) { + return rval; + } + for (int i = 0; i < ra.length; i++) { + rval = rval + HEX_DIGITS[(ra[i] & 0xF0) >> 4]; + rval = rval + HEX_DIGITS[(ra[i] & 0x0F)]; + if (i < ra.length - 1) { + rval = rval + " "; + } + } + return rval; + } + + public static String shortHexStringWithoutSpaces(byte[] byteArray) { + String hexString = ""; + if (byteArray == null) { + return hexString; + } + if (byteArray.length == 0) { + return hexString; + } + for (byte b : byteArray) { + hexString = hexString + HEX_DIGITS[(b & 0xF0) >> 4]; + hexString = hexString + HEX_DIGITS[(b & 0x0F)]; + } + return hexString; + } + + public static String shortHexString(List list) { + + byte[] abyte0 = getByteArrayFromList(list); + + return shortHexString(abyte0); + } + + + public static String shortHexString(byte val) { + return getHexCompact(val); + } + + + public static String showPrintable(byte[] ra) { + String s = new String(); + for (int i = 0; i < ra.length; i++) { + char c = (char) ra[i]; + if (((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) { + s = s + c; + } else { + s = s + '.'; + } + } + return s; + } + + + public static byte[] fromHexString(String src) { + String s = src.toUpperCase(); + byte[] rval = new byte[]{}; + if ((s.length() % 2) != 0) { + // invalid hex string! + return null; + } + for (int i = 0; i < s.length(); i += 2) { + int highNibbleOrd = HEX_DIGITS_STR.indexOf(s.charAt(i)); + if (highNibbleOrd < 0) { + // Not a hex digit. + return null; + } + int lowNibbleOrd = HEX_DIGITS_STR.indexOf(s.charAt(i + 1)); + if (lowNibbleOrd < 0) { + // Not a hex digit + return null; + } + rval = concat(rval, (byte) (highNibbleOrd * 16 + lowNibbleOrd)); + } + return rval; + } + + + // public static byte[] fromByteList(List byteArray) { + // byte[] rval = new byte[byteArray.size()]; + // for (int i = 0; i < byteArray.size(); i++) { + // rval[i] = byteArray.get(i); + // } + // return rval; + // } + + // public static List toByteList(byte[] data) { + // ArrayList rval = new ArrayList<>(data.length); + // for (int i = 0; i < data.length; i++) { + // rval.add(i, new Byte(data[i])); + // } + // return rval; + // } + + public static List getListFromByteArray(byte[] array) { + List listOut = new ArrayList(); + + for (byte val : array) { + listOut.add(val); + } + + return listOut; + } + + + public static byte[] getByteArrayFromList(List list) { + byte[] out = new byte[list.size()]; + + for (int i = 0; i < list.size(); i++) { + out[i] = list.get(i); + } + + return out; + } + + + // compares byte strings like strcmp + public static int compare(byte[] s1, byte[] s2) { + int i; + int len1 = s1.length; + int len2 = s2.length; + if (len1 > len2) { + return 1; + } + if (len2 > len1) { + return -1; + } + int acc = 0; + for (i = 0; i < len1; i++) { + acc += s1[i]; + acc -= s2[i]; + if (acc != 0) { + return acc; + } + } + return 0; + } + + + /** + * Converts 4 (or less) ints into int. (Shorts are objects, so you can send null if you have less parameters) + * + * @param b1 short 1 + * @param b2 short 2 + * @param b3 short 3 + * @param b4 short 4 + * @param flag Conversion Flag (Big Endian, Little endian) + * @return int value + */ + public static int toInt(Integer b1, Integer b2, Integer b3, Integer b4, BitConversion flag) { + switch (flag) { + case LITTLE_ENDIAN: { + if (b4 != null) { + return (b4 & 0xff) << 24 | (b3 & 0xff) << 16 | (b2 & 0xff) << 8 | b1 & 0xff; + } else if (b3 != null) { + return (b3 & 0xff) << 16 | (b2 & 0xff) << 8 | b1 & 0xff; + } else if (b2 != null) { + return (b2 & 0xff) << 8 | b1 & 0xff; + } else { + return b1 & 0xff; + } + } + + default: + case BIG_ENDIAN: { + if (b4 != null) { + return (b1 & 0xff) << 24 | (b2 & 0xff) << 16 | (b3 & 0xff) << 8 | b4 & 0xff; + } else if (b3 != null) { + return (b1 & 0xff) << 16 | (b2 & 0xff) << 8 | b3 & 0xff; + } else if (b2 != null) { + return (b1 & 0xff) << 8 | b2 & 0xff; + } else { + return b1 & 0xff; + } + } + } + } + + + public static int toInt(int b1, int b2) { + return toInt(b1, b2, null, null, BitConversion.BIG_ENDIAN); + } + + + public static int toInt(int b1, int b2, int b3) { + return toInt(b1, b2, b3, null, BitConversion.BIG_ENDIAN); + } + + + public static int toInt(int b1, int b2, BitConversion flag) { + return toInt(b1, b2, null, null, flag); + } + + + public static int makeUnsignedShort(int i, int j) { + int k = (i & 0xff) << 8 | j & 0xff; + return k; + } + + + /** + * Gets the correct hex value. + * + * @param inp the inp + * @return the correct hex value + */ + public static String getCorrectHexValue(int inp) { + String hx = Integer.toHexString((char) inp); + + if (hx.length() == 0) + return "00"; + else if (hx.length() == 1) + return "0" + hx; + else if (hx.length() == 2) + return hx; + else if (hx.length() == 4) + return hx.substring(2); + else { + System.out.println("Hex Error: " + inp); + } + + return null; + } + + + public static String getHex(byte[] abyte0) { + return abyte0 != null ? getHex(abyte0, abyte0.length) : null; + } + + + public static String getString(short[] abyte0) { + StringBuilder sb = new StringBuilder(); + + for (short i : abyte0) { + sb.append(i); + sb.append(" "); + } + + return sb.toString(); + } + + + public static String getHex(List list) { + + byte[] abyte0 = getByteArrayFromList(list); + + return abyte0 != null ? getHex(abyte0, abyte0.length) : null; + } + + + public static String getHex(byte[] abyte0, int i) { + StringBuffer stringbuffer = new StringBuffer(); + if (abyte0 != null) { + i = Math.min(i, abyte0.length); + for (int j = 0; j < i; j++) { + stringbuffer.append(shortHexString(abyte0[j])); + if (j < i - 1) { + stringbuffer.append(" "); + } + } + + } + return new String(stringbuffer); + } + + + public static String getHex(byte byte0) { + String s = byte0 != -1 ? "0x" : ""; + return s + getHexCompact(byte0); + } + + + public static String getHexCompact(byte byte0) { + int i = byte0 != -1 ? convertUnsignedByteToInt(byte0) : (int) byte0; + return getHexCompact(i); + } + + + public static int convertUnsignedByteToInt(byte data) { + return data & 0xff; + } + + + // public String getHexCompact(int i) { + // long l = i != -1 ? convertUnsignedIntToLong(i) : i; + // return getHexCompact(l); + // } + + public static String getHexCompact(int l) { + String s = Long.toHexString(l).toUpperCase(); + String s1 = isOdd(s.length()) ? "0" : ""; + return l != -1L ? s1 + s : "-1"; + } + + + public static boolean isEven(int i) { + return i % 2 == 0; + } + + + public static boolean isOdd(int i) { + return !isEven(i); + } + + public enum BitConversion { + LITTLE_ENDIAN, // 20 0 0 0 = reverse + BIG_ENDIAN // 0 0 0 20 = normal - java + } + + + public static String getCompactString(byte[] data) { + if (data == null) + return "null"; + + String vval2 = ByteUtil.getHex(data); + vval2 = vval2.replace(" 0x", ""); + vval2 = vval2.replace("0x", ""); + return vval2; + } + + + // 000300050100C800A0 + public static byte[] createByteArrayFromCompactString(String dataFull) { + return createByteArrayFromCompactString(dataFull, 0, dataFull.length()); + } + + + // 00 03 00 05 01 00 C8 00 A0 + public static byte[] createByteArrayFromString(String dataFull) { + + String data = dataFull.replace(" ", ""); + + return createByteArrayFromCompactString(data, 0, data.length()); + } + + + public static byte[] createByteArrayFromHexString(String dataFull) { + + String data = dataFull.replace(" 0x", ""); + data = data.replace("0x", ""); + + return createByteArrayFromCompactString(data, 0, data.length()); + } + + + public static byte[] createByteArrayFromCompactString(String dataFull, int startIndex) { + return createByteArrayFromCompactString(dataFull, startIndex, dataFull.length()); + } + + + public static byte[] createByteArrayFromCompactString(String dataFull, int startIndex, int length) { + + String data = dataFull.substring(startIndex); + + data = data.substring(0, length); + + int len = data.length(); + byte[] outArray = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + outArray[i / 2] = (byte) ((Character.digit(data.charAt(i), 16) << 4) + Character.digit(data.charAt(i + 1), + 16)); + } + + return outArray; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/CRC.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/CRC.java new file mode 100644 index 00000000000..ac90d1edb8a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/CRC.java @@ -0,0 +1,153 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +/** + * Created by geoff on 4/27/15. + */ +public class CRC { + + static final int[] crc8lookup = new int[] { + 0, 155, 173, 54, 193, 90, 108, 247, 25, + 130, + 180, + 47, + 216, + 67, + 117, + 238, + 50, + 169, // + 159, 4, 243, 104, 94, 197, 43, 176, 134, 29, 234, 113, 71, 220, 100, 255, 201, + 82, + 165, + 62, + 8, + 147, + 125, + 230, + 208, + 75, // + 188, 39, 17, 138, 86, 205, 251, 96, 151, 12, 58, 161, 79, 212, 226, 121, 142, 21, + 35, + 184, + 200, + 83, + 101, + 254, + 9, + 146, // + 164, 63, 209, 74, 124, 231, 16, 139, 189, 38, 250, 97, 87, 204, 59, 160, 150, 13, 227, + 120, + 78, + 213, + 34, + 185, + 143, + 20, // + 172, 55, 1, 154, 109, 246, 192, 91, 181, 46, 24, 131, 116, 239, 217, 66, 158, 5, 51, 168, 95, + 196, + 242, + 105, + 135, + 28, + 42, // + 177, 70, 221, 235, 112, 11, 144, 166, 61, 202, 81, 103, 252, 18, 137, 191, 36, 211, 72, 126, 229, + 57, + 162, + 148, + 15, + 248, // + 99, 85, 206, 32, 187, 141, 22, 225, 122, 76, 215, 111, 244, 194, 89, 174, 53, 3, 152, 118, 237, 219, 64, + 183, + 44, + 26, + 129, // + 93, 198, 240, 107, 156, 7, 49, 170, 68, 223, 233, 114, 133, 30, 40, 179, 195, 88, 110, 245, 2, 153, 175, 52, + 218, + 65, + 119, // + 236, 27, 128, 182, 45, 241, 106, 92, 199, 48, 171, 157, 6, 232, 115, 69, 222, 41, 178, 132, 31, 167, 60, 10, + 145, 102, + 253, // + 203, 80, 190, 37, 19, 136, 127, 228, 210, 73, 149, 14, 56, 163, 84, 207, 249, 98, 140, 23, 33, 186, 77, 214, + 224, 123 }; + + static final int[] crc16lookup = new int[] { + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, + 0x8027, 0x0022, 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, 0x0050, 0x8055, 0x805f, 0x005a, + 0x804b, 0x004e, 0x0044, 0x8041, 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, 0x00f0, 0x80f5, + 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, + 0x8197, 0x0192, 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, 0x01e0, 0x81e5, 0x81ef, 0x01ea, + 0x81fb, 0x01fe, 0x01f4, 0x81f1, 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, 0x0140, 0x8145, + 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, + 0x0104, 0x8101, 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, 0x0330, 0x8335, 0x833f, 0x033a, + 0x832b, 0x032e, 0x0324, 0x8321, 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, 0x8353, 0x0356, + 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, + 0x83b7, 0x03b2, 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, 0x0280, 0x8285, 0x828f, 0x028a, + 0x829b, 0x029e, 0x0294, 0x8291, 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, 0x82e3, 0x02e6, + 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, + 0x0264, 0x8261, 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, 0x8213, 0x0216, 0x021c, 0x8219, + 0x0208, 0x820d, 0x8207, 0x0202 }; + + + public static byte crc8(byte[] data, int len) { + byte result = 0; + if (data == null) { + return 0; + } + if (len > data.length) { + len = data.length; + } + for (int i = 0; i < len; i++) { + int tmp = result; + int tmp2 = tmp ^ data[i]; + int tmp3 = tmp2 & 0xFF; + int idx = tmp3; + result = (byte)crc8lookup[idx]; + // log(String.format("iter=%d,tmp=0x%02x, tmp2=0x%02x, tmp3=0x%02x, lookup=0x%02x",i,tmp,tmp2,tmp3,result)); + } + // orig python: + // result = klass.lookup[ ( result ^ block[ i ] & 0xFF ) ] + return result; + + } + + + public static byte crc8(byte[] data) { + return crc8(data, data.length); + } + + + public static byte[] calculate16CCITT(byte[] data) { + int crc = 0xFFFF; + int polynomial = 0x1021; + if (data != null) { + if (data.length > 0) { + for (int j = 0; j < data.length; j++) { + byte b = data[j]; + for (int i = 0; i < 8; i++) { + boolean bit = ((b >> (7 - i) & 1) == 1); + boolean c15 = ((crc >> 15 & 1) == 1); + crc <<= 1; + if (c15 ^ bit) + crc ^= polynomial; + } + } + } + } + crc &= 0xffff; + return new byte[] { (byte)((crc & 0xFF00) >> 8), (byte)(crc & 0xFF) }; + } + + + public static int crc16(byte[] bytes) { + int crc = 0x0000; + for (byte b : bytes) { + crc = (crc >>> 8) ^ crc16lookup[(crc ^ b) & 0xff]; + } + return crc; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java new file mode 100644 index 00000000000..7c3e3eb7679 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/DateTimeUtil.java @@ -0,0 +1,303 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +/** + * Created by andy on 10/25/18. + */ + +import org.joda.time.LocalDateTime; +import org.joda.time.Minutes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +import info.nightscout.androidaps.logging.L; + +/** + * This is simple version of ATechDate, limited only to one format (yyyymmddHHMIss) + */ +public class DateTimeUtil { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + /** + * DateTime is packed as long: yyyymmddHHMMss + * + * @param atechDateTime + * @return + */ + public static LocalDateTime toLocalDateTime(long atechDateTime) { + int year = (int) (atechDateTime / 10000000000L); + atechDateTime -= year * 10000000000L; + + int month = (int) (atechDateTime / 100000000L); + atechDateTime -= month * 100000000L; + + int dayOfMonth = (int) (atechDateTime / 1000000L); + atechDateTime -= dayOfMonth * 1000000L; + + int hourOfDay = (int) (atechDateTime / 10000L); + atechDateTime -= hourOfDay * 10000L; + + int minute = (int) (atechDateTime / 100L); + atechDateTime -= minute * 100L; + + int second = (int) atechDateTime; + + try { + return new LocalDateTime(year, month, dayOfMonth, hourOfDay, minute, second); + } catch (Exception ex) { + if (L.isEnabled(L.PUMPCOMM)) + LOG.error("Error creating LocalDateTime from values [atechDateTime={}, year={}, month={}, day={}, hour={}, minute={}, second={}]. Exception: {}", atechDateTime, year, month, dayOfMonth, hourOfDay, minute, second, ex.getMessage()); + //return null; + throw ex; + } + } + + + /** + * DateTime is packed as long: yyyymmddHHMMss + * + * @param atechDateTime + * @return + */ + public static GregorianCalendar toGregorianCalendar(long atechDateTime) { + int year = (int) (atechDateTime / 10000000000L); + atechDateTime -= year * 10000000000L; + + int month = (int) (atechDateTime / 100000000L); + atechDateTime -= month * 100000000L; + + int dayOfMonth = (int) (atechDateTime / 1000000L); + atechDateTime -= dayOfMonth * 1000000L; + + int hourOfDay = (int) (atechDateTime / 10000L); + atechDateTime -= hourOfDay * 10000L; + + int minute = (int) (atechDateTime / 100L); + atechDateTime -= minute * 100L; + + int second = (int) atechDateTime; + + try { + return new GregorianCalendar(year, month - 1, dayOfMonth, hourOfDay, minute, second); + } catch (Exception ex) { + if (L.isEnabled(L.PUMPCOMM)) + LOG.error("DateTimeUtil", String.format("Error creating GregorianCalendar from values [atechDateTime=%d, year=%d, month=%d, day=%d, hour=%d, minute=%d, second=%d]", atechDateTime, year, month, dayOfMonth, hourOfDay, minute, second)); + //return null; + throw ex; + } + } + + + public static long toATechDate(LocalDateTime ldt) { + long atechDateTime = 0L; + + atechDateTime += ldt.getYear() * 10000000000L; + atechDateTime += ldt.getMonthOfYear() * 100000000L; + atechDateTime += ldt.getDayOfMonth() * 1000000L; + atechDateTime += ldt.getHourOfDay() * 10000L; + atechDateTime += ldt.getMinuteOfHour() * 100L; + atechDateTime += ldt.getSecondOfMinute(); + + return atechDateTime; + } + + + public static long toATechDate(GregorianCalendar gc) { + long atechDateTime = 0L; + + atechDateTime += gc.get(Calendar.YEAR) * 10000000000L; + atechDateTime += (gc.get(Calendar.MONTH) + 1) * 100000000L; + atechDateTime += gc.get(Calendar.DAY_OF_MONTH) * 1000000L; + atechDateTime += gc.get(Calendar.HOUR_OF_DAY) * 10000L; + atechDateTime += gc.get(Calendar.MINUTE) * 100L; + atechDateTime += gc.get(Calendar.SECOND); + + return atechDateTime; + } + + + public static long toATechDate(long timeInMillis) { + GregorianCalendar gc = new GregorianCalendar(); + gc.setTimeInMillis(timeInMillis); + + return toATechDate(gc); + } + + + public static boolean isSameDay(LocalDateTime ldt1, LocalDateTime ldt2) { + + return (ldt1.getYear() == ldt2.getYear() && // + ldt1.getMonthOfYear() == ldt2.getMonthOfYear() && // + ldt1.getDayOfMonth() == ldt2.getDayOfMonth()); + + } + + + public static boolean isSameDay(long ldt1, long ldt2) { + + long day1 = ldt1 / 10000L; + long day2 = ldt2 / 10000L; + + return day1 == day2; + } + + + public static long toATechDate(int year, int month, int dayOfMonth, int hour, int minutes, int seconds) { + + long atechDateTime = 0L; + + atechDateTime += year * 10000000000L; + atechDateTime += month * 100000000L; + atechDateTime += dayOfMonth * 1000000L; + atechDateTime += hour * 10000L; + atechDateTime += minutes * 100L; + atechDateTime += seconds; + + return atechDateTime; + } + + +// public static long toATechDate(Date date) { +// +// long atechDateTime = 0L; +// +// atechDateTime += (date.getYear() + 1900) * 10000000000L; +// atechDateTime += (date.getMonth() + 1) * 100000000L; +// atechDateTime += date.getDate() * 1000000L; +// atechDateTime += date.getHours() * 10000L; +// atechDateTime += date.getMinutes() * 100L; +// atechDateTime += date.getSeconds(); +// +// return atechDateTime; +// } + + + public static String toString(long atechDateTime) { + int year = (int) (atechDateTime / 10000000000L); + atechDateTime -= year * 10000000000L; + + int month = (int) (atechDateTime / 100000000L); + atechDateTime -= month * 100000000L; + + int dayOfMonth = (int) (atechDateTime / 1000000L); + atechDateTime -= dayOfMonth * 1000000L; + + int hourOfDay = (int) (atechDateTime / 10000L); + atechDateTime -= hourOfDay * 10000L; + + int minute = (int) (atechDateTime / 100L); + atechDateTime -= minute * 100L; + + int second = (int) atechDateTime; + + return getZeroPrefixed(dayOfMonth) + "." + getZeroPrefixed(month) + "." + year + " " + // + getZeroPrefixed(hourOfDay) + ":" + getZeroPrefixed(minute) + ":" + getZeroPrefixed(second); + } + + + public static String toString(GregorianCalendar gc) { + + return getZeroPrefixed(gc.get(Calendar.DAY_OF_MONTH)) + "." + getZeroPrefixed(gc.get(Calendar.MONTH) + 1) + "." + + gc.get(Calendar.YEAR) + " " + + // + getZeroPrefixed(gc.get(Calendar.HOUR_OF_DAY)) + ":" + getZeroPrefixed(gc.get(Calendar.MINUTE)) + ":" + + getZeroPrefixed(gc.get(Calendar.SECOND)); + } + + + public static String toStringFromTimeInMillis(long timeInMillis) { + + GregorianCalendar gc = new GregorianCalendar(); + gc.setTimeInMillis(timeInMillis); + + return toString(gc); + } + + + private static String getZeroPrefixed(int number) { + return (number < 10) ? "0" + number : "" + number; + } + + + public static int getYear(Long atechDateTime) { + + if (atechDateTime == null || atechDateTime == 0) { + return 2000; + } + + int year = (int) (atechDateTime / 10000000000L); + return year; + } + + + public static boolean isSameDayATDAndMillis(long atechDateTime, long timeInMillis) { + + GregorianCalendar dt = new GregorianCalendar(); + dt.setTimeInMillis(timeInMillis); + + long entryDate = toATechDate(dt); + + return (isSameDay(atechDateTime, entryDate)); + } + + + public static long toMillisFromATD(long atechDateTime) { + + GregorianCalendar gc = toGregorianCalendar(atechDateTime); + + return gc.getTimeInMillis(); + } + + + public static int getATechDateDiferenceAsMinutes(Long date1, Long date2) { + + Minutes minutes = Minutes.minutesBetween(toLocalDateTime(date1), toLocalDateTime(date2)); + + return minutes.getMinutes(); + } + + + public static long getMillisFromATDWithAddedMinutes(long atd, int minutesDiff) { + GregorianCalendar oldestEntryTime = DateTimeUtil.toGregorianCalendar(atd); + oldestEntryTime.add(Calendar.MINUTE, minutesDiff); + + return oldestEntryTime.getTimeInMillis(); + } + + + public static long getATDWithAddedMinutes(long atd, int minutesDiff) { + GregorianCalendar oldestEntryTime = DateTimeUtil.toGregorianCalendar(atd); + oldestEntryTime.add(Calendar.MINUTE, minutesDiff); + + return oldestEntryTime.getTimeInMillis(); + } + + + public static long getATDWithAddedMinutes(GregorianCalendar oldestEntryTime, int minutesDiff) { + oldestEntryTime.add(Calendar.MINUTE, minutesDiff); + + return toATechDate(oldestEntryTime); + } + + + public static long getTimeInFutureFromMinutes(long startTime, int minutes) { + return startTime + getTimeInMs(minutes); + } + + public static long getTimeInFutureFromMinutes(int minutes) { + return System.currentTimeMillis() + getTimeInMs(minutes); + } + + + public static long getTimeInMs(int minutes) { + return getTimeInS(minutes) * 1000L; + } + + public static int getTimeInS(int minutes) { + return minutes * 60; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/LocationHelper.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/LocationHelper.java new file mode 100644 index 00000000000..065f0ec4d6a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/LocationHelper.java @@ -0,0 +1,70 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.location.LocationManager; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.utils.OKDialog; + +/** + * Helper for checking if location services are enabled on the device. + */ +public class LocationHelper { + + /** + * Determine if GPS is currently enabled. + *

+ * On Android 6 (Marshmallow), location needs to be enabled for Bluetooth discovery to work. + * + * @param context The current app context. + * @return true if location is enabled, false otherwise. + */ + public static boolean isLocationEnabled(Context context) { + LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + return (locationManager != null && // + (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || // + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))); + + // return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + } + + + /** + * Prompt the user to enable GPS location if it isn't already on. + * + * @param parent The currently visible activity. + */ + public static void requestLocation(final Activity parent) { + if (LocationHelper.isLocationEnabled(parent)) { + return; + } + + // Shamelessly borrowed from http://stackoverflow.com/a/10311877/868533 + OKDialog.showConfirmation(parent, MainApp.gs(R.string.location_not_found_title), MainApp.gs(R.string.location_not_found_message), () -> { + parent.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + }); + } + + + /** + * Prompt the user to enable GPS location on devices that need it for Bluetooth discovery. + *

+ * Android 6 (Marshmallow) needs GPS enabled for Bluetooth discovery to work. + * + * @param activity The currently visible activity. + */ + public static void requestLocationForBluetooth(Activity activity) { + // Location needs to be enabled for Bluetooth discovery on Marshmallow. + LocationHelper.requestLocation(activity); + } + + // public static Boolean locationPermission(ActivityWithMenu act) { + // return ActivityCompat.checkSelfPermission(act, Manifest.permission.ACCESS_FINE_LOCATION) == + // PackageManager.PERMISSION_GRANTED; + // } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ProfileUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ProfileUtil.java new file mode 100644 index 00000000000..5f1dd3c6efa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ProfileUtil.java @@ -0,0 +1,54 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +import java.util.Locale; + +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; + +public class ProfileUtil { + + + public static String getProfileDisplayable(Profile profile, PumpType pumpType) { + + StringBuilder stringBuilder = new StringBuilder(); + + for (Profile.ProfileValue basalValue : profile.getBasalValues()) { + + double basalValueValue = pumpType.determineCorrectBasalSize(basalValue.value); + + int hour = basalValue.timeAsSeconds / (60 * 60); + + stringBuilder.append((hour < 10 ? "0" : "") + hour + ":00"); + + stringBuilder.append(String.format(Locale.ENGLISH, "%.3f", basalValueValue)); + stringBuilder.append(", "); + } + if (stringBuilder.length() > 3) + return stringBuilder.toString().substring(0, stringBuilder.length() - 2); + else + return stringBuilder.toString(); + } + + public static String getBasalProfilesDisplayable(Profile.ProfileValue[] profiles, PumpType pumpType) { + + StringBuilder stringBuilder = new StringBuilder(); + + for (Profile.ProfileValue basalValue : profiles) { + + double basalValueValue = pumpType.determineCorrectBasalSize(basalValue.value); + + int hour = basalValue.timeAsSeconds / (60 * 60); + + stringBuilder.append((hour < 10 ? "0" : "") + hour + ":00"); + + stringBuilder.append(String.format(Locale.ENGLISH, "%.3f", basalValueValue)); + stringBuilder.append(", "); + } + if (stringBuilder.length() > 3) + return stringBuilder.toString().substring(0, stringBuilder.length() - 2); + else + return stringBuilder.toString(); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/StringUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/StringUtil.java new file mode 100644 index 00000000000..ed7e4e26346 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/StringUtil.java @@ -0,0 +1,121 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +import org.joda.time.LocalDateTime; + +import java.nio.charset.Charset; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by geoff on 4/28/15. + * modified by Andy + */ +public class StringUtil { + + public static DecimalFormat[] DecimalFormaters = { + new DecimalFormat("#0"), new DecimalFormat("#0.0"), new DecimalFormat("#0.00"), new DecimalFormat("#0.000")}; + + + public static String fromBytes(byte[] ra) { + if (ra == null) + return "null array"; + else + return new String(ra, Charset.forName("UTF-8")); + } + + + // these should go in some project-wide string utils package + public static String join(ArrayList ra, String joiner) { + int sz = ra.size(); + String rval = ""; + int n; + for (n = 0; n < sz; n++) { + rval = rval + ra.get(n); + if (n < sz - 1) { + rval = rval + joiner; + } + } + return rval; + } + + + public static String testJoin() { + ArrayList ra = new ArrayList(); + ra.add("one"); + ra.add("two"); + ra.add("three"); + return join(ra, "+"); + } + + + /** + * Append To StringBuilder + * + * @param stringBuilder + * @param stringToAdd + * @param delimiter + * @return + */ + public static void appendToStringBuilder(StringBuilder stringBuilder, String stringToAdd, String delimiter) { + if (stringBuilder.length() > 0) { + stringBuilder.append(delimiter + stringToAdd); + } else { + stringBuilder.append(stringToAdd); + } + } + + + public static String getFormatedValueUS(Number value, int decimals) { + return DecimalFormaters[decimals].format(value).replace(",", "."); + } + + + public static String getLeadingZero(int number, int places) { + String nn = "" + number; + + while (nn.length() < places) { + nn = "0" + nn; + } + + return nn; + } + + + public static String toDateTimeString(LocalDateTime localDateTime) { + return localDateTime.toString("dd.MM.yyyy HH:mm:ss"); + } + + + public static String getStringInLength(String value, int length) { + StringBuilder val = new StringBuilder(value); + + if (val.length() > length) { + return val.substring(0, length); + } + + for (int i = val.length(); i < length; i++) { + val.append(" "); + } + + return val.toString(); + } + + + public static List splitString(String s, int characters) { + + List outString = new ArrayList<>(); + + do { + if (s.length() > characters) { + String token = s.substring(0, characters); + outString.add(token); + s = s.substring(characters); + } + } while (s.length() > characters); + + outString.add(s); + + return outString; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ThreadUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ThreadUtil.java new file mode 100644 index 00000000000..d1781da72a7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/common/utils/ThreadUtil.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.common.utils; + +/** + * Created by geoff on 5/27/16. + */ +public class ThreadUtil { + + public static long getThreadId() { + return Thread.currentThread().getId(); + } + + + public static String getThreadName() { + return Thread.currentThread().getName(); + } + + + public static String sig() { + Thread t = Thread.currentThread(); + return t.getName() + "[" + t.getId() + "]"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java index 749307e1a7a..b57510dc073 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/AbstractDanaRPlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR; -import android.support.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; import org.json.JSONException; import org.json.JSONObject; @@ -14,7 +14,6 @@ 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.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; @@ -24,10 +23,11 @@ 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.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; @@ -41,12 +41,13 @@ import info.nightscout.androidaps.utils.DecimalFormatter; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.TimeChangeType; /** * Created by mike on 28.01.2018. */ -public abstract class AbstractDanaRPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { +public abstract class AbstractDanaRPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface { protected Logger log = LoggerFactory.getLogger(L.PUMP); protected AbstractDanaRExecutionService sExecutionService; @@ -76,6 +77,11 @@ public void onStateChange(PluginType type, State oldState, State newState) { } } + @Override + public void switchAllowed(boolean newState, FragmentActivity activity, PluginType type) { + confirmPumpPluginActivation(newState, activity, type); + } + @Override public boolean isSuspended() { return DanaRPump.getInstance().pumpSuspended; @@ -100,22 +106,22 @@ public PumpEnactResult setNewBasalProfile(Profile profile) { if (!isInitialized()) { 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)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = MainApp.gs(R.string.pumpNotInitializedProfileNotSet); return result; } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } if (!sExecutionService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = MainApp.gs(R.string.failedupdatebasalprofile); return result; } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.success = true; result.enacted = true; result.comment = "OK"; @@ -135,7 +141,6 @@ public boolean isThisProfileSet(Profile profile) { for (int h = 0; h < basalValues; h++) { Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; Double profileValue = profile.getBasalTimeFromMidnight(h * basalIncrement); - if (profileValue == null) return true; if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { if (L.isEnabled(L.PUMP)) log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); @@ -186,8 +191,8 @@ public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMi if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; long now = System.currentTimeMillis(); - TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); - if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); + if (activeTemp != null && activeTemp.percentRate == percent && activeTemp.getPlannedRemainingMinutes() > 4 && !enforceNew) { result.enacted = false; result.success = true; result.isTempCancel = false; @@ -376,7 +381,12 @@ public JSONObject getJSONStatus(Profile profile, String profilename) { } @Override - public String deviceID() { + public ManufacturerType manufacturer() { + return ManufacturerType.Sooil; + } + + @Override + public String serialNumber() { return DanaRPump.getInstance().serialNumber; } @@ -423,24 +433,6 @@ public Constraint applyExtendedBolusConstraints(Constraint insul return applyBolusConstraints(insulin); } - @Nullable - @Override - public ProfileStore getProfile() { - if (DanaRPump.getInstance().lastSettingsRead == 0) - return null; // no info now - return DanaRPump.getInstance().createConvertedProfile(); - } - - @Override - public String getUnits() { - return DanaRPump.getInstance().getUnits(); - } - - @Override - public String getProfileName() { - return DanaRPump.getInstance().createConvertedProfileName(); - } - @Override public PumpEnactResult loadTDDs() { return loadHistory(RecordTypes.RECORD_TYPE_DAILY); @@ -469,7 +461,6 @@ public String shortStatus(boolean veryShort) { if (!veryShort) { ret += "TDD: " + DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U\n"; } - ret += "IOB: " + pump.iob + "U\n"; ret += "Reserv: " + DecimalFormatter.to0Decimal(pump.reservoirRemainingUnits) + "U\n"; ret += "Batt: " + pump.batteryRemaining + "\n"; return ret; @@ -493,7 +484,10 @@ public boolean canHandleDST() { return false; } + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java deleted file mode 100644 index e044a2db271..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.java +++ /dev/null @@ -1,304 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR; - - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.FragmentManager; -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.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.activities.TDDStatsActivity; -import info.nightscout.androidaps.db.ExtendedBolus; -import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.events.EventExtendedBolusChange; -import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.events.EventTempBasalChange; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.pump.danaR.dialogs.ProfileViewDialog; -import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRHistoryActivity; -import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRUserOptionsActivity; -import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRNewStatus; -import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.queue.events.EventQueueChanged; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.SetWarnColor; - -public class DanaRFragment extends SubscriberFragment { - private static Logger log = LoggerFactory.getLogger(L.PUMP); - - private Handler loopHandler = new Handler(); - private Runnable refreshLoop = new Runnable() { - @Override - public void run() { - updateGUI(); - loopHandler.postDelayed(refreshLoop, 60 * 1000L); - } - }; - - @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.danar_user_options) - Button danar_user_options; - - public DanaRFragment() { - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - loopHandler.postDelayed(refreshLoop, 60 * 1000L); - } - - @Override - public void onDestroy() { - super.onDestroy(); - loopHandler.removeCallbacks(refreshLoop); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.danar_fragment, container, false); - unbinder = ButterKnife.bind(this, view); - - pumpStatusView.setBackgroundColor(MainApp.gc(R.color.colorInitializingBorder)); - - return view; - } - - @OnClick(R.id.danar_history) - void onHistoryClick() { - startActivity(new Intent(getContext(), DanaRHistoryActivity.class)); - } - - @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(), TDDStatsActivity.class)); - } - - @OnClick(R.id.danar_user_options) - void onUserOptionsClick() { - startActivity(new Intent(getContext(), DanaRUserOptionsActivity.class)); - } - - @OnClick(R.id.danar_btconnection) - void onBtConnectionClick() { - if (L.isEnabled(L.PUMP)) - log.debug("Clicked connect to pump"); - DanaRPump.getInstance().lastConnection = 0; - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("Clicked connect to pump", null); - } - - @Subscribe - public void onStatusEvent(final EventPumpStatusChanged c) { - Activity activity = getActivity(); - final String status = c.textStatus(); - if (activity != null) { - activity.runOnUiThread( - () -> { - synchronized (DanaRFragment.this) { - - if (btConnectionView == null || pumpStatusView == null || pumpStatusLayout == null) - return; - - if (c.sStatus == EventPumpStatusChanged.CONNECTING) - btConnectionView.setText("{fa-bluetooth-b spin} " + c.sSecondsElapsed + "s"); - else if (c.sStatus == EventPumpStatusChanged.CONNECTED) - btConnectionView.setText("{fa-bluetooth}"); - else if (c.sStatus == EventPumpStatusChanged.DISCONNECTED) - btConnectionView.setText("{fa-bluetooth-b}"); - - if (!status.equals("")) { - pumpStatusView.setText(status); - pumpStatusLayout.setVisibility(View.VISIBLE); - } else { - pumpStatusLayout.setVisibility(View.GONE); - } - } - } - ); - } - } - - @Subscribe - public void onStatusEvent(final EventDanaRNewStatus s) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventTempBasalChange s) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventExtendedBolusChange s) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventQueueChanged s) { - updateGUI(); - } - - // GUI functions - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null && basaBasalRateView != null) - activity.runOnUiThread(() -> { - synchronized (DanaRFragment.this) { - if (!isBound()) return; - - DanaRPump pump = DanaRPump.getInstance(); - if (pump.lastConnection != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection; - int agoMin = (int) (agoMsec / 60d / 1000d); - lastConnectionView.setText(DateUtil.timeString(pump.lastConnection) + " (" + String.format(MainApp.gs(R.string.minago), agoMin) + ")"); - SetWarnColor.setColor(lastConnectionView, agoMin, 16d, 31d); - } - if (pump.lastBolusTime != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastBolusTime; - double agoHours = agoMsec / 60d / 60d / 1000d; - if (agoHours < 6) // max 6h back - lastBolusView.setText(DateUtil.timeString(pump.lastBolusTime) + " " + DateUtil.sinceString(pump.lastBolusTime) + " " + DecimalFormatter.to2Decimal(DanaRPump.getInstance().lastBolusAmount) + " U"); - else lastBolusView.setText(""); - } - - dailyUnitsView.setText(DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U"); - SetWarnColor.setColor(dailyUnitsView, pump.dailyTotalUnits, pump.maxDailyTotalUnits * 0.75d, pump.maxDailyTotalUnits * 0.9d); - basaBasalRateView.setText("( " + (pump.activeProfile + 1) + " ) " + DecimalFormatter.to2Decimal(ConfigBuilderPlugin.getPlugin().getActivePump().getBaseBasalRate()) + " U/h"); - // DanaRPlugin, DanaRKoreanPlugin - if (ConfigBuilderPlugin.getPlugin().getActivePump().isFakingTempsByExtendedBoluses()) { - if (TreatmentsPlugin.getPlugin().isInHistoryRealTempBasalInProgress()) { - tempBasalView.setText(TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull()); - } else { - tempBasalView.setText(""); - } - } else { - // v2 plugin - TemporaryBasal tb = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); - if (tb != null) { - tempBasalView.setText(tb.toStringFull()); - } else { - tempBasalView.setText(""); - } - } - ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (activeExtendedBolus != null) { - extendedBolusView.setText(activeExtendedBolus.toString()); - } else { - extendedBolusView.setText(""); - } - reservoirView.setText(DecimalFormatter.to0Decimal(pump.reservoirRemainingUnits) + " / 300 U"); - SetWarnColor.setColorInverse(reservoirView, pump.reservoirRemainingUnits, 50d, 20d); - batteryView.setText("{fa-battery-" + (pump.batteryRemaining / 25) + "}"); - SetWarnColor.setColorInverse(batteryView, pump.batteryRemaining, 51d, 26d); - iobView.setText(pump.iob + " U"); - if (pump.model != 0 || pump.protocol != 0 || pump.productCode != 0) { - firmwareView.setText(String.format(MainApp.gs(R.string.danar_model), pump.model, pump.protocol, pump.productCode)); - } else { - firmwareView.setText("OLD"); - } - basalStepView.setText("" + pump.basalStep); - bolusStepView.setText("" + pump.bolusStep); - serialNumberView.setText("" + pump.serialNumber); - if (queueView != null) { - Spanned status = ConfigBuilderPlugin.getPlugin().getCommandQueue().spannedStatus(); - if (status.toString().equals("")) { - queueView.setVisibility(View.GONE); - } else { - queueView.setVisibility(View.VISIBLE); - queueView.setText(status); - } - } - //hide user options button if not an RS pump or old firmware - // also excludes pump with model 03 because of untested error - boolean isKorean = DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PUMP); - if (isKorean || firmwareView.getText() == "OLD" || pump.model == 3) { - danar_user_options.setVisibility(View.GONE); - } - } - }); - } - - private boolean isBound() { - return lastConnectionView != null - && lastBolusView != null - && dailyUnitsView != null - && basaBasalRateView != null - && tempBasalView != null - && extendedBolusView != null - && reservoirView != null - && batteryView != null - && iobView != null - && firmwareView != null - && basalStepView != null - && bolusStepView != null - && serialNumberView != null - && danar_user_options != null - && queueView != null; - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt new file mode 100644 index 00000000000..af1e80602e2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRFragment.kt @@ -0,0 +1,199 @@ +package info.nightscout.androidaps.plugins.pump.danaR + +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.TDDStatsActivity +import info.nightscout.androidaps.dialogs.ProfileViewerDialog +import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.interfaces.PumpInterface +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRHistoryActivity +import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRUserOptionsActivity +import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRNewStatus +import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.events.EventQueueChanged +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.SetWarnColor +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.plusAssign +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.danar_fragment.* +import org.slf4j.LoggerFactory + +class DanaRFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.PUMP) + private var disposable: CompositeDisposable = CompositeDisposable() + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + init { + refreshLoop = Runnable { + activity?.runOnUiThread { updateGUI() } + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.danar_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + dana_pumpstatus.setBackgroundColor(MainApp.gc(R.color.colorInitializingBorder)) + + danar_history.setOnClickListener { startActivity(Intent(context, DanaRHistoryActivity::class.java)) } + danar_viewprofile.setOnClickListener { + fragmentManager?.let { fragmentManager -> + val profile = DanaRPump.getInstance().createConvertedProfile()?.getDefaultProfile() + ?: return@let + val profileName = DanaRPump.getInstance().createConvertedProfile()?.getDefaultProfileName() + ?: return@let + val args = Bundle() + args.putLong("time", DateUtil.now()) + args.putInt("mode", ProfileViewerDialog.Mode.CUSTOM_PROFILE.ordinal) + args.putString("customProfile", profile.data.toString()) + args.putString("customProfileUnits", profile.units) + args.putString("customProfileName", profileName) + val pvd = ProfileViewerDialog() + pvd.arguments = args + pvd.show(fragmentManager, "ProfileViewDialog") + } + } + danar_stats.setOnClickListener { startActivity(Intent(context, TDDStatsActivity::class.java)) } + danar_user_options.setOnClickListener { startActivity(Intent(context, DanaRUserOptionsActivity::class.java)) } + danar_btconnection.setOnClickListener { + if (L.isEnabled(L.PUMP)) + log.debug("Clicked connect to pump") + DanaRPump.getInstance().lastConnection = 0 + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("Clicked connect to pump", null) + } + } + + @Synchronized + override fun onResume() { + super.onResume() + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + disposable += RxBus + .toObservable(EventDanaRNewStatus::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventTempBasalChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventQueueChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventPumpStatusChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + when { + it.sStatus == EventPumpStatusChanged.Status.CONNECTING -> danar_btconnection?.text = "{fa-bluetooth-b spin} " + it.sSecondsElapsed + "s" + it.sStatus == EventPumpStatusChanged.Status.CONNECTED -> danar_btconnection?.text = "{fa-bluetooth}" + it.sStatus == EventPumpStatusChanged.Status.DISCONNECTED -> danar_btconnection?.text = "{fa-bluetooth-b}" + } + if (it.getStatus() != "") { + dana_pumpstatus?.text = it.getStatus() + dana_pumpstatuslayout?.visibility = View.VISIBLE + } else { + dana_pumpstatuslayout?.visibility = View.GONE + } + }, { FabricPrivacy.logException(it) }) + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + loopHandler.removeCallbacks(refreshLoop) + } + + // GUI functions + @Synchronized + internal fun updateGUI() { + if (danar_dailyunits == null) return + val pump = DanaRPump.getInstance() + val plugin: PumpInterface = ConfigBuilderPlugin.getPlugin().activePump ?: return + if (pump.lastConnection != 0L) { + val agoMsec = System.currentTimeMillis() - pump.lastConnection + val agoMin = (agoMsec.toDouble() / 60.0 / 1000.0).toInt() + danar_lastconnection.text = DateUtil.timeString(pump.lastConnection) + " (" + String.format(MainApp.gs(R.string.minago), agoMin) + ")" + SetWarnColor.setColor(danar_lastconnection, agoMin.toDouble(), 16.0, 31.0) + } + if (pump.lastBolusTime != 0L) { + val agoMsec = System.currentTimeMillis() - pump.lastBolusTime + val agoHours = agoMsec.toDouble() / 60.0 / 60.0 / 1000.0 + if (agoHours < 6) + // max 6h back + danar_lastbolus.text = DateUtil.timeString(pump.lastBolusTime) + " " + DateUtil.sinceString(pump.lastBolusTime) + " " + MainApp.gs(R.string.formatinsulinunits, pump.lastBolusAmount) + else + danar_lastbolus.text = "" + } + + danar_dailyunits.text = MainApp.gs(R.string.reservoirvalue, pump.dailyTotalUnits, pump.maxDailyTotalUnits) + SetWarnColor.setColor(danar_dailyunits, pump.dailyTotalUnits, pump.maxDailyTotalUnits * 0.75, pump.maxDailyTotalUnits * 0.9) + danar_basabasalrate.text = "( " + (pump.activeProfile + 1) + " ) " + MainApp.gs(R.string.pump_basebasalrate, plugin.baseBasalRate) + // DanaRPlugin, DanaRKoreanPlugin + if (ConfigBuilderPlugin.getPlugin().activePump!!.isFakingTempsByExtendedBoluses) { + danar_tempbasal.text = TreatmentsPlugin.getPlugin() + .getRealTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "" + } else { + // v2 plugin + danar_tempbasal.text = TreatmentsPlugin.getPlugin() + .getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "" + } + danar_extendedbolus.text = TreatmentsPlugin.getPlugin() + .getExtendedBolusFromHistory(System.currentTimeMillis())?.toString() ?: "" + danar_reservoir.text = MainApp.gs(R.string.reservoirvalue, pump.reservoirRemainingUnits, 300) + SetWarnColor.setColorInverse(danar_reservoir, pump.reservoirRemainingUnits, 50.0, 20.0) + danar_battery.text = "{fa-battery-" + pump.batteryRemaining / 25 + "}" + SetWarnColor.setColorInverse(danar_battery, pump.batteryRemaining.toDouble(), 51.0, 26.0) + danar_iob.text = MainApp.gs(R.string.formatinsulinunits, pump.iob) + if (pump.model != 0 || pump.protocol != 0 || pump.productCode != 0) { + danar_firmware.text = String.format(MainApp.gs(R.string.danar_model), pump.model, pump.protocol, pump.productCode) + } else { + danar_firmware.text = "OLD" + } + danar_basalstep.text = pump.basalStep.toString() + danar_bolusstep.text = pump.bolusStep.toString() + danar_serialnumber.text = pump.serialNumber + val status = ConfigBuilderPlugin.getPlugin().commandQueue.spannedStatus() + if (status.toString() == "") { + danar_queue.visibility = View.GONE + } else { + danar_queue.visibility = View.VISIBLE + danar_queue.text = status + } + //hide user options button if not an RS pump or old firmware + // also excludes pump with model 03 because of untested error + val isKorean = DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PUMP) + if (isKorean || danar_firmware.text === "OLD" || pump.model == 3) { + danar_user_options.visibility = View.GONE + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java index 2f7484cd6f0..e9293b39a63 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/DanaRPlugin.java @@ -5,10 +5,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; - -import com.squareup.otto.Subscribe; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -22,19 +18,23 @@ import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStartWithSpeed; import info.nightscout.androidaps.plugins.pump.danaR.services.DanaRExecutionService; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 05.08.2016. */ public class DanaRPlugin extends AbstractDanaRPlugin { + private CompositeDisposable disposable = new CompositeDisposable(); private static DanaRPlugin plugin = null; @@ -50,35 +50,32 @@ public DanaRPlugin() { pumpDescription.setPumpDescription(PumpType.DanaR); } - @Override - public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity context) { - boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); - if (allowHardwarePump || context == null) { - pluginSwitcher.invoke(); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(R.string.allow_hardware_pump_text) - .setPositiveButton(R.string.yes, (dialog, id) -> { - pluginSwitcher.invoke(); - SP.putBoolean("allow_hardware_pump", true); - if (L.isEnabled(L.PUMP)) - log.debug("First time HW pump allowed!"); - }) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - pluginSwitcher.cancel(); - if (L.isEnabled(L.PUMP)) - log.debug("User does not allow switching to HW pump!"); - }); - builder.create().show(); - } - } - @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); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (isEnabled(PluginType.PUMP)) { + boolean previousValue = useExtendedBoluses; + useExtendedBoluses = SP.getBoolean(R.string.key_danar_useextended, false); + + if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { + sExecutionService.extendedBolusStop(); + } + } + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + MainApp.instance().getApplicationContext().unbindService(mConnection); + }, FabricPrivacy::logException) + ); super.onStart(); } @@ -87,7 +84,8 @@ protected void onStop() { Context context = MainApp.instance().getApplicationContext(); context.unbindService(mConnection); - MainApp.bus().unregister(this); + disposable.clear(); + super.onStop(); } private ServiceConnection mConnection = new ServiceConnection() { @@ -106,24 +104,6 @@ public void onServiceConnected(ComponentName name, IBinder service) { } }; - @SuppressWarnings("UnusedParameters") - @Subscribe - public void onStatusEvent(final EventAppExit e) { - MainApp.instance().getApplicationContext().unbindService(mConnection); - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange s) { - if (isEnabled(PluginType.PUMP)) { - boolean previousValue = useExtendedBoluses; - useExtendedBoluses = SP.getBoolean(R.string.key_danar_useextended, false); - - if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { - sExecutionService.extendedBolusStop(); - } - } - } - // Plugin base interface @Override public String getName() { @@ -262,7 +242,7 @@ public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer duratio // Correct basal already set ? if (L.isEnabled(L.PUMP)) log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); - if (activeTemp.percentRate == percentRate) { + if (activeTemp.percentRate == percentRate && activeTemp.getPlannedRemainingMinutes() > 4) { if (enforceNew) { cancelTempBasal(true); } else { @@ -360,6 +340,11 @@ public PumpEnactResult cancelTempBasal(boolean force) { return result; } + @Override + public PumpType model() { + return PumpType.DanaR; + } + private PumpEnactResult cancelRealTempBasal() { PumpEnactResult result = new PumpEnactResult(); TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java deleted file mode 100644 index 2b09b180cf8..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/Dialogs/ProfileViewDialog.java +++ /dev/null @@ -1,91 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.dialogs; - -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.TextView; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileStore; -import info.nightscout.androidaps.interfaces.ProfileInterface; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.treatments.fragments.ProfileGraph; -import info.nightscout.androidaps.utils.DecimalFormatter; - -/** - * Created by mike on 10.07.2016. - */ -public class ProfileViewDialog extends DialogFragment { - 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 ProfileGraph basalGraph; - - - private Button refreshButton; - - public ProfileViewDialog() { - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View layout = inflater.inflate(R.layout.profileviewer_fragment, container, false); - - 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.VISIBLE); - refreshButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("ProfileViewDialog", null); - dismiss(); - } - }); - basalGraph = (ProfileGraph) layout.findViewById(R.id.basal_graph); - setContent(); - return layout; - } - - @Override - public void onResume() { - super.onResume(); - getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - private void setContent() { - ProfileStore store = ((ProfileInterface)ConfigBuilderPlugin.getPlugin().getActivePump()).getProfile(); - if (store != null) { - noProfile.setVisibility(View.GONE); - Profile profile = store.getDefaultProfile(); - units.setText(profile.getUnits()); - dia.setText(DecimalFormatter.to2Decimal(profile.getDia()) + " h"); - activeProfile.setText(((ProfileInterface) ConfigBuilderPlugin.getPlugin().getActivePump()).getProfileName()); - ic.setText(profile.getIcList()); - 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/pump/danaR/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/SerialIOThread.java index c8141d12652..782ec42eed6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/SerialIOThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/SerialIOThread.java @@ -12,7 +12,7 @@ import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; -import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageHashTable; +import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageHashTableBase; import info.nightscout.androidaps.plugins.pump.danaR.services.AbstractSerialIOThread; import info.nightscout.androidaps.utils.CRC; @@ -30,9 +30,11 @@ public class SerialIOThread extends AbstractSerialIOThread { private byte[] mReadBuff = new byte[0]; private MessageBase processedMessage; + private MessageHashTableBase hashTable; - public SerialIOThread(BluetoothSocket rfcommSocket) { + public SerialIOThread(BluetoothSocket rfcommSocket, MessageHashTableBase hashTable) { super(); + this.hashTable = hashTable; mRfCommSocket = rfcommSocket; try { @@ -68,11 +70,11 @@ public final void run() { message = processedMessage; } else { // get it from hash table - message = MessageHashTable.findMessage(command); + message = hashTable.findMessage(command); } if (L.isEnabled(L.PUMPBTCOMM)) - log.debug("<<<<< " + message.getMessageName() + " " + message.toHexString(extractedBuff)); + log.debug("<<<<< " + message.getMessageName() + " " + MessageBase.toHexString(extractedBuff)); // process the message content message.received = true; @@ -83,14 +85,14 @@ public final void run() { } } } catch (Exception e) { - if (e.getMessage().indexOf("bt socket closed") < 0) + if (!e.getMessage().contains("bt socket closed")) log.error("Thread exception: ", e); mKeepRunning = false; } disconnect("EndOfLoop"); } - void appendToBuffer(byte[] newData, int gotBytes) { + private void appendToBuffer(byte[] newData, int gotBytes) { // add newData to mReadBuff byte[] newReadBuff = new byte[mReadBuff.length + gotBytes]; System.arraycopy(mReadBuff, 0, newReadBuff, 0, mReadBuff.length); @@ -98,7 +100,7 @@ void appendToBuffer(byte[] newData, int gotBytes) { mReadBuff = newReadBuff; } - byte[] cutMessageFromBuffer() { + private byte[] cutMessageFromBuffer() { if (mReadBuff[0] == (byte) 0x7E && mReadBuff[1] == (byte) 0x7E) { int length = (mReadBuff[2] & 0xFF) + 7; // Check if we have enough data @@ -148,7 +150,7 @@ public synchronized void sendMessage(MessageBase message) { byte[] messageBytes = message.getRawMessageBytes(); if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(">>>>> " + message.getMessageName() + " " + message.toHexString(messageBytes)); + log.debug(">>>>> " + message.getMessageName() + " " + MessageBase.toHexString(messageBytes)); try { mOutputStream.write(messageBytes); @@ -165,8 +167,10 @@ public synchronized void sendMessage(MessageBase message) { } SystemClock.sleep(200); - if (!message.received) { - log.warn("Reply not received " + message.getMessageName()); + if (!message.isReceived()) { + message.handleMessageNotReceived(); + if (L.isEnabled(L.PUMPBTCOMM)) + log.error("Reply not received " + message.getMessageName()); if (message.getCommand() == 0xF0F1) { DanaRPump.getInstance().isNewPump = false; if (L.isEnabled(L.PUMPCOMM)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java deleted file mode 100644 index 95c9f25e377..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.java +++ /dev/null @@ -1,367 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.activities; - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -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.squareup.otto.Subscribe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -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.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; -import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRSyncStatus; -import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; -import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; -import info.nightscout.androidaps.queue.Callback; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.ToastUtils; - -public class DanaRHistoryActivity extends Activity { - private static Logger log = LoggerFactory.getLogger(L.PUMP); - - private Handler mHandler; - - static Profile profile = null; - - Spinner historyTypeSpinner; - TextView statusView; - Button reloadButton; - Button syncButton; - RecyclerView recyclerView; - LinearLayoutManager llm; - - static byte showingType = RecordTypes.RECORD_TYPE_ALARM; - List historyList = new ArrayList<>(); - - public static class TypeList { - public byte type; - String name; - - TypeList(byte type, String name) { - this.type = type; - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - public DanaRHistoryActivity() { - super(); - HandlerThread mHandlerThread = new HandlerThread(DanaRHistoryActivity.class.getSimpleName()); - mHandlerThread.start(); - this.mHandler = new Handler(mHandlerThread.getLooper()); - } - - - @Override - protected void onResume() { - super.onResume(); - MainApp.bus().register(this); - } - - @Override - protected void onPause() { - super.onPause(); - MainApp.bus().unregister(this); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.danar_historyactivity); - - historyTypeSpinner = (Spinner) findViewById(R.id.danar_historytype); - statusView = (TextView) findViewById(R.id.danar_historystatus); - reloadButton = (Button) findViewById(R.id.danar_historyreload); - syncButton = (Button) findViewById(R.id.danar_historysync); - recyclerView = (RecyclerView) findViewById(R.id.danar_history_recyclerview); - - recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(this); - recyclerView.setLayoutManager(llm); - - RecyclerViewAdapter adapter = new RecyclerViewAdapter(historyList); - recyclerView.setAdapter(adapter); - - statusView.setVisibility(View.GONE); - - boolean isKorean = DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PUMP); - boolean isRS = DanaRSPlugin.getPlugin().isEnabled(PluginType.PUMP); - - // Types - - ArrayList typeList = new ArrayList<>(); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_ALARM, MainApp.gs(R.string.danar_history_alarm))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_BASALHOUR, MainApp.gs(R.string.danar_history_basalhours))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_BOLUS, MainApp.gs(R.string.danar_history_bolus))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_CARBO, MainApp.gs(R.string.danar_history_carbohydrates))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_DAILY, MainApp.gs(R.string.danar_history_dailyinsulin))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_GLUCOSE, MainApp.gs(R.string.danar_history_glucose))); - if (!isKorean && !isRS) { - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_ERROR, MainApp.gs(R.string.danar_history_errors))); - } - if (isRS) - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_PRIME, MainApp.gs(R.string.danar_history_prime))); - if (!isKorean) { - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_REFILL, MainApp.gs(R.string.danar_history_refill))); - typeList.add(new TypeList(RecordTypes.RECORD_TYPE_SUSPEND, MainApp.gs(R.string.danar_history_syspend))); - } - ArrayAdapter spinnerAdapter = new ArrayAdapter<>(this, - R.layout.spinner_centered, typeList); - historyTypeSpinner.setAdapter(spinnerAdapter); - - reloadButton.setOnClickListener(v -> { - final TypeList selected = (TypeList) historyTypeSpinner.getSelectedItem(); - runOnUiThread(() -> { - reloadButton.setVisibility(View.GONE); - syncButton.setVisibility(View.GONE); - statusView.setVisibility(View.VISIBLE); - }); - clearCardView(); - ConfigBuilderPlugin.getPlugin().getCommandQueue().loadHistory(selected.type, new Callback() { - @Override - public void run() { - loadDataFromDB(selected.type); - runOnUiThread(() -> { - reloadButton.setVisibility(View.VISIBLE); - syncButton.setVisibility(View.VISIBLE); - statusView.setVisibility(View.GONE); - }); - } - }); - }); - - syncButton.setOnClickListener(v -> mHandler.post(new Runnable() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - reloadButton.setVisibility(View.GONE); - syncButton.setVisibility(View.GONE); - statusView.setVisibility(View.VISIBLE); - } - }); - DanaRNSHistorySync sync = new DanaRNSHistorySync(historyList); - sync.sync(DanaRNSHistorySync.SYNC_ALL); - runOnUiThread(new Runnable() { - @Override - public void run() { - reloadButton.setVisibility(View.VISIBLE); - syncButton.setVisibility(View.VISIBLE); - statusView.setVisibility(View.GONE); - } - }); - } - })); - - historyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - TypeList selected = (TypeList) historyTypeSpinner.getSelectedItem(); - loadDataFromDB(selected.type); - showingType = selected.type; - } - - @Override - public void onNothingSelected(AdapterView parent) { - clearCardView(); - } - }); - profile = ProfileFunctions.getInstance().getProfile(); - if (profile == null) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.noprofile)); - finish(); - } - } - - public static class RecyclerViewAdapter extends RecyclerView.Adapter { - - List historyList; - - RecyclerViewAdapter(List historyList) { - this.historyList = historyList; - } - - @Override - public HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.danar_history_item, viewGroup, false); - return new HistoryViewHolder(v); - } - - @Override - public void onBindViewHolder(HistoryViewHolder holder, int position) { - DanaRHistoryRecord record = historyList.get(position); - holder.time.setText(DateUtil.dateAndTimeString(record.recordDate)); - holder.value.setText(DecimalFormatter.to2Decimal(record.recordValue)); - holder.stringvalue.setText(record.stringRecordValue); - holder.bolustype.setText(record.bolusType); - holder.duration.setText(DecimalFormatter.to0Decimal(record.recordDuration)); - holder.alarm.setText(record.recordAlarm); - switch (showingType) { - case RecordTypes.RECORD_TYPE_ALARM: - holder.time.setVisibility(View.VISIBLE); - holder.value.setVisibility(View.VISIBLE); - holder.stringvalue.setVisibility(View.GONE); - holder.bolustype.setVisibility(View.GONE); - holder.duration.setVisibility(View.GONE); - holder.dailybasal.setVisibility(View.GONE); - holder.dailybolus.setVisibility(View.GONE); - holder.dailytotal.setVisibility(View.GONE); - holder.alarm.setVisibility(View.VISIBLE); - break; - case RecordTypes.RECORD_TYPE_BOLUS: - holder.time.setVisibility(View.VISIBLE); - holder.value.setVisibility(View.VISIBLE); - holder.stringvalue.setVisibility(View.GONE); - holder.bolustype.setVisibility(View.VISIBLE); - holder.duration.setVisibility(View.VISIBLE); - holder.dailybasal.setVisibility(View.GONE); - holder.dailybolus.setVisibility(View.GONE); - holder.dailytotal.setVisibility(View.GONE); - holder.alarm.setVisibility(View.GONE); - break; - case RecordTypes.RECORD_TYPE_DAILY: - holder.dailybasal.setText(DecimalFormatter.to2Decimal(record.recordDailyBasal) + "U"); - holder.dailybolus.setText(DecimalFormatter.to2Decimal(record.recordDailyBolus) + "U"); - holder.dailytotal.setText(DecimalFormatter.to2Decimal(record.recordDailyBolus + record.recordDailyBasal) + "U"); - holder.time.setText(DateUtil.dateString(record.recordDate)); - holder.time.setVisibility(View.VISIBLE); - holder.value.setVisibility(View.GONE); - holder.stringvalue.setVisibility(View.GONE); - holder.bolustype.setVisibility(View.GONE); - holder.duration.setVisibility(View.GONE); - holder.dailybasal.setVisibility(View.VISIBLE); - holder.dailybolus.setVisibility(View.VISIBLE); - holder.dailytotal.setVisibility(View.VISIBLE); - holder.alarm.setVisibility(View.GONE); - break; - case RecordTypes.RECORD_TYPE_GLUCOSE: - holder.value.setText(Profile.toUnitsString(record.recordValue, record.recordValue * Constants.MGDL_TO_MMOLL, profile.getUnits())); - // rest is the same - case RecordTypes.RECORD_TYPE_CARBO: - case RecordTypes.RECORD_TYPE_BASALHOUR: - case RecordTypes.RECORD_TYPE_ERROR: - case RecordTypes.RECORD_TYPE_PRIME: - case RecordTypes.RECORD_TYPE_REFILL: - case RecordTypes.RECORD_TYPE_TB: - holder.time.setVisibility(View.VISIBLE); - holder.value.setVisibility(View.VISIBLE); - holder.stringvalue.setVisibility(View.GONE); - holder.bolustype.setVisibility(View.GONE); - holder.duration.setVisibility(View.GONE); - holder.dailybasal.setVisibility(View.GONE); - holder.dailybolus.setVisibility(View.GONE); - holder.dailytotal.setVisibility(View.GONE); - holder.alarm.setVisibility(View.GONE); - break; - case RecordTypes.RECORD_TYPE_SUSPEND: - holder.time.setVisibility(View.VISIBLE); - holder.value.setVisibility(View.GONE); - holder.stringvalue.setVisibility(View.VISIBLE); - holder.bolustype.setVisibility(View.GONE); - holder.duration.setVisibility(View.GONE); - holder.dailybasal.setVisibility(View.GONE); - holder.dailybolus.setVisibility(View.GONE); - holder.dailytotal.setVisibility(View.GONE); - holder.alarm.setVisibility(View.GONE); - break; - } - } - - @Override - public int getItemCount() { - return historyList.size(); - } - - @Override - public void onAttachedToRecyclerView(RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - } - - static class HistoryViewHolder extends RecyclerView.ViewHolder { - CardView cv; - TextView time; - TextView value; - TextView bolustype; - TextView stringvalue; - TextView duration; - TextView dailybasal; - TextView dailybolus; - TextView dailytotal; - TextView alarm; - - HistoryViewHolder(View itemView) { - super(itemView); - cv = (CardView) itemView.findViewById(R.id.danar_history_cardview); - time = (TextView) itemView.findViewById(R.id.danar_history_time); - value = (TextView) itemView.findViewById(R.id.danar_history_value); - bolustype = (TextView) itemView.findViewById(R.id.danar_history_bolustype); - stringvalue = (TextView) itemView.findViewById(R.id.danar_history_stringvalue); - duration = (TextView) itemView.findViewById(R.id.danar_history_duration); - dailybasal = (TextView) itemView.findViewById(R.id.danar_history_dailybasal); - dailybolus = (TextView) itemView.findViewById(R.id.danar_history_dailybolus); - dailytotal = (TextView) itemView.findViewById(R.id.danar_history_dailytotal); - alarm = (TextView) itemView.findViewById(R.id.danar_history_alarm); - } - } - } - - private void loadDataFromDB(byte type) { - historyList = MainApp.getDbHelper().getDanaRHistoryRecordsByType(type); - - runOnUiThread(() -> recyclerView.swapAdapter(new RecyclerViewAdapter(historyList), false)); - } - - private void clearCardView() { - historyList = new ArrayList<>(); - runOnUiThread(() -> recyclerView.swapAdapter(new RecyclerViewAdapter(historyList), false)); - } - - @Subscribe - public void onStatusEvent(final EventDanaRSyncStatus s) { - if (L.isEnabled(L.PUMP)) - log.debug("EventDanaRSyncStatus: " + s.message); - runOnUiThread( - () -> statusView.setText(s.message)); - } - - @Subscribe - public void onStatusEvent(final EventPumpStatusChanged s) { - runOnUiThread( - () -> statusView.setText(s.textStatus()) - ); - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.kt new file mode 100644 index 00000000000..d63ab0eb140 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRHistoryActivity.kt @@ -0,0 +1,248 @@ +package info.nightscout.androidaps.plugins.pump.danaR.activities + +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.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.db.DanaRHistoryRecord +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes +import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRSyncStatus +import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.FabricPrivacy +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.danar_historyactivity.* +import org.slf4j.LoggerFactory +import java.util.* + +class DanaRHistoryActivity : NoSplashAppCompatActivity() { + private val log = LoggerFactory.getLogger(L.PUMP) + private val disposable = CompositeDisposable() + + private var showingType = RecordTypes.RECORD_TYPE_ALARM + private var historyList: List = ArrayList() + + class TypeList internal constructor(var type: Byte, var name: String) { + override fun toString(): String = name + } + + override fun onResume() { + super.onResume() + disposable.add(toObservable(EventPumpStatusChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ danar_history_status.text = it.getStatus() }) { FabricPrivacy.logException(it) } + ) + disposable.add(toObservable(EventDanaRSyncStatus::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (L.isEnabled(L.PUMP)) + log.debug("EventDanaRSyncStatus: " + it.message) + danar_history_status.text = it.message + }) { FabricPrivacy.logException(it) } + ) + } + + override fun onPause() { + super.onPause() + disposable.clear() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.danar_historyactivity) + + danar_history_recyclerview.setHasFixedSize(true) + danar_history_recyclerview.layoutManager = LinearLayoutManager(this) + danar_history_recyclerview.adapter = RecyclerViewAdapter(historyList) + danar_history_status.visibility = View.GONE + + val isKorean = DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PUMP) + val isRS = DanaRSPlugin.getPlugin().isEnabled(PluginType.PUMP) + + // Types + val typeList = ArrayList() + typeList.add(TypeList(RecordTypes.RECORD_TYPE_ALARM, MainApp.gs(R.string.danar_history_alarm))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_BASALHOUR, MainApp.gs(R.string.danar_history_basalhours))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_BOLUS, MainApp.gs(R.string.danar_history_bolus))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_CARBO, MainApp.gs(R.string.danar_history_carbohydrates))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_DAILY, MainApp.gs(R.string.danar_history_dailyinsulin))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_GLUCOSE, MainApp.gs(R.string.danar_history_glucose))) + if (!isKorean && !isRS) { + typeList.add(TypeList(RecordTypes.RECORD_TYPE_ERROR, MainApp.gs(R.string.danar_history_errors))) + } + if (isRS) typeList.add(TypeList(RecordTypes.RECORD_TYPE_PRIME, MainApp.gs(R.string.danar_history_prime))) + if (!isKorean) { + typeList.add(TypeList(RecordTypes.RECORD_TYPE_REFILL, MainApp.gs(R.string.danar_history_refill))) + typeList.add(TypeList(RecordTypes.RECORD_TYPE_SUSPEND, MainApp.gs(R.string.danar_history_syspend))) + } + danar_history_spinner.adapter = ArrayAdapter(this, R.layout.spinner_centered, typeList) + + danar_history_reload.setOnClickListener { + val selected = danar_history_spinner.selectedItem as TypeList + runOnUiThread { + danar_history_reload?.visibility = View.GONE + danar_history_status?.visibility = View.VISIBLE + } + clearCardView() + ConfigBuilderPlugin.getPlugin().commandQueue.loadHistory(selected.type, object : Callback() { + override fun run() { + loadDataFromDB(selected.type) + runOnUiThread { + danar_history_reload?.visibility = View.VISIBLE + danar_history_status?.visibility = View.GONE + } + } + }) + } + danar_history_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { + val selected = danar_history_spinner?.selectedItem as TypeList? ?: return + loadDataFromDB(selected.type) + showingType = selected.type + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + clearCardView() + } + } + } + + inner class RecyclerViewAdapter internal constructor(private var historyList: List) : RecyclerView.Adapter() { + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): HistoryViewHolder = + HistoryViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.danar_history_item, viewGroup, false)) + + override fun onBindViewHolder(holder: HistoryViewHolder, position: Int) { + val record = historyList[position] + holder.time.text = DateUtil.dateAndTimeString(record.recordDate) + holder.value.text = DecimalFormatter.to2Decimal(record.recordValue) + holder.stringValue.text = record.stringRecordValue + holder.bolusType.text = record.bolusType + holder.duration.text = DecimalFormatter.to0Decimal(record.recordDuration.toDouble()) + holder.alarm.text = record.recordAlarm + when (showingType) { + RecordTypes.RECORD_TYPE_ALARM -> { + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.VISIBLE + holder.stringValue.visibility = View.GONE + holder.bolusType.visibility = View.GONE + holder.duration.visibility = View.GONE + holder.dailyBasal.visibility = View.GONE + holder.dailyBolus.visibility = View.GONE + holder.dailyTotal.visibility = View.GONE + holder.alarm.visibility = View.VISIBLE + } + + RecordTypes.RECORD_TYPE_BOLUS -> { + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.VISIBLE + holder.stringValue.visibility = View.GONE + holder.bolusType.visibility = View.VISIBLE + holder.duration.visibility = View.VISIBLE + holder.dailyBasal.visibility = View.GONE + holder.dailyBolus.visibility = View.GONE + holder.dailyTotal.visibility = View.GONE + holder.alarm.visibility = View.GONE + } + + RecordTypes.RECORD_TYPE_DAILY -> { + holder.dailyBasal.text = MainApp.gs(R.string.formatinsulinunits, record.recordDailyBasal) + holder.dailyBolus.text = MainApp.gs(R.string.formatinsulinunits, record.recordDailyBolus) + holder.dailyTotal.text = MainApp.gs(R.string.formatinsulinunits, record.recordDailyBolus + record.recordDailyBasal) + holder.time.text = DateUtil.dateString(record.recordDate) + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.GONE + holder.stringValue.visibility = View.GONE + holder.bolusType.visibility = View.GONE + holder.duration.visibility = View.GONE + holder.dailyBasal.visibility = View.VISIBLE + holder.dailyBolus.visibility = View.VISIBLE + holder.dailyTotal.visibility = View.VISIBLE + holder.alarm.visibility = View.GONE + } + + RecordTypes.RECORD_TYPE_GLUCOSE -> { + holder.value.text = Profile.toUnitsString(record.recordValue, record.recordValue * Constants.MGDL_TO_MMOLL, ProfileFunctions.getSystemUnits()) + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.VISIBLE + holder.stringValue.visibility = View.GONE + holder.bolusType.visibility = View.GONE + holder.duration.visibility = View.GONE + holder.dailyBasal.visibility = View.GONE + holder.dailyBolus.visibility = View.GONE + holder.dailyTotal.visibility = View.GONE + holder.alarm.visibility = View.GONE + } + + RecordTypes.RECORD_TYPE_CARBO, RecordTypes.RECORD_TYPE_BASALHOUR, RecordTypes.RECORD_TYPE_ERROR, RecordTypes.RECORD_TYPE_PRIME, RecordTypes.RECORD_TYPE_REFILL, RecordTypes.RECORD_TYPE_TB -> { + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.VISIBLE + holder.stringValue.visibility = View.GONE + holder.bolusType.visibility = View.GONE + holder.duration.visibility = View.GONE + holder.dailyBasal.visibility = View.GONE + holder.dailyBolus.visibility = View.GONE + holder.dailyTotal.visibility = View.GONE + holder.alarm.visibility = View.GONE + } + + RecordTypes.RECORD_TYPE_SUSPEND -> { + holder.time.visibility = View.VISIBLE + holder.value.visibility = View.GONE + holder.stringValue.visibility = View.VISIBLE + holder.bolusType.visibility = View.GONE + holder.duration.visibility = View.GONE + holder.dailyBasal.visibility = View.GONE + holder.dailyBolus.visibility = View.GONE + holder.dailyTotal.visibility = View.GONE + holder.alarm.visibility = View.GONE + } + } + } + + override fun getItemCount(): Int { + return historyList.size + } + + inner class HistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var time: TextView = itemView.findViewById(R.id.danar_history_time) + var value: TextView = itemView.findViewById(R.id.danar_history_value) + var bolusType: TextView = itemView.findViewById(R.id.danar_history_bolustype) + var stringValue: TextView = itemView.findViewById(R.id.danar_history_stringvalue) + var duration: TextView = itemView.findViewById(R.id.danar_history_duration) + var dailyBasal: TextView = itemView.findViewById(R.id.danar_history_dailybasal) + var dailyBolus: TextView = itemView.findViewById(R.id.danar_history_dailybolus) + var dailyTotal: TextView = itemView.findViewById(R.id.danar_history_dailytotal) + var alarm: TextView = itemView.findViewById(R.id.danar_history_alarm) + } + } + + private fun loadDataFromDB(type: Byte) { + historyList = MainApp.getDbHelper().getDanaRHistoryRecordsByType(type) + runOnUiThread { danar_history_recyclerview?.swapAdapter(RecyclerViewAdapter(historyList), false) } + } + + private fun clearCardView() { + historyList = ArrayList() + runOnUiThread { danar_history_recyclerview?.swapAdapter(RecyclerViewAdapter(historyList), false) } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRNSHistorySync.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRNSHistorySync.java index a15c8d2883e..bf6f80af0b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRNSHistorySync.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRNSHistorySync.java @@ -14,6 +14,7 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.DanaRHistoryRecord; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRSyncStatus; @@ -28,13 +29,13 @@ public class DanaRNSHistorySync { private static Logger log = LoggerFactory.getLogger(L.PUMP); private List historyRecords; - public final static int SYNC_BOLUS = 0b00000001; - public final static int SYNC_ERROR = 0b00000010; - public final static int SYNC_REFILL = 0b00000100; - public final static int SYNC_GLUCOSE = 0b00001000; - public final static int SYNC_CARBO = 0b00010000; - public final static int SYNC_ALARM = 0b00100000; - public final static int SYNC_BASALHOURS = 0b01000000; + private final static int SYNC_BOLUS = 0b00000001; + private final static int SYNC_ERROR = 0b00000010; + private final static int SYNC_REFILL = 0b00000100; + private final static int SYNC_GLUCOSE = 0b00001000; + private final static int SYNC_CARBO = 0b00010000; + private final static int SYNC_ALARM = 0b00100000; + private final static int SYNC_BASALHOURS = 0b01000000; public final static int SYNC_ALL = 0b11111111; public final static String DANARSIGNATURE = "DANARMESSAGE"; @@ -52,13 +53,13 @@ public void sync(int what) { long uploaded = 0; if (L.isEnabled(L.PUMP)) log.debug("Database contains " + records + " records"); - EventDanaRSyncStatus ev = new EventDanaRSyncStatus(); + EventDanaRSyncStatus ev = new EventDanaRSyncStatus(""); for (DanaRHistoryRecord record : historyRecords) { processing++; if (record._id != null) continue; //log.debug(record.bytes); JSONObject nsrec = new JSONObject(); - ev.message = MainApp.gs(R.string.uploading) + " " + processing + "/" + records + " "; // TODO: translations + ev.setMessage(MainApp.gs(R.string.uploading) + " " + processing + "/" + records + " "); // TODO: translations switch (record.recordCode) { case RecordTypes.RECORD_TYPE_BOLUS: if ((what & SYNC_BOLUS) == 0) break; @@ -73,7 +74,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_sbolus); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_sbolus)); break; case "E": if (record.recordDuration > 0) { @@ -92,7 +93,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_ebolus); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_ebolus)); } else { if (L.isEnabled(L.PUMP)) log.debug("NOT Syncing extended bolus record " + record.recordValue + "U " + DateUtil.toISOString(record.recordDate) + " zero duration"); @@ -110,7 +111,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_dsbolus); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_dsbolus)); break; case "DE": if (L.isEnabled(L.PUMP)) @@ -127,7 +128,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_debolus); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_debolus)); break; default: log.error("Unknown bolus record"); @@ -145,7 +146,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_error); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_error)); break; case RecordTypes.RECORD_TYPE_REFILL: if ((what & SYNC_REFILL) == 0) break; @@ -158,7 +159,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_refill); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_refill)); break; case RecordTypes.RECORD_TYPE_BASALHOUR: if ((what & SYNC_BASALHOURS) == 0) break; @@ -172,7 +173,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_basalhour); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_basalhour)); break; case RecordTypes.RECORD_TYPE_TB: //log.debug("Ignoring TB record " + record.bytes + " " + DateUtil.toISOString(record.recordDate)); @@ -183,13 +184,13 @@ public void sync(int what) { log.debug("Syncing glucose record " + record.recordValue + " " + DateUtil.toISOString(record.recordDate)); nsrec.put(DANARSIGNATURE, record.bytes); nsrec.put("eventType", "BG Check"); - nsrec.put("glucose", Profile.fromMgdlToUnits(record.recordValue, ProfileFunctions.getInstance().getProfileUnits())); + nsrec.put("glucose", Profile.fromMgdlToUnits(record.recordValue, ProfileFunctions.getSystemUnits())); nsrec.put("glucoseType", "Finger"); nsrec.put("created_at", DateUtil.toISOString(record.recordDate)); nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_glucose); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_glucose)); break; case RecordTypes.RECORD_TYPE_CARBO: if ((what & SYNC_CARBO) == 0) break; @@ -202,7 +203,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_carbohydrate); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_carbohydrate)); break; case RecordTypes.RECORD_TYPE_ALARM: if ((what & SYNC_ALARM) == 0) break; @@ -215,7 +216,7 @@ public void sync(int what) { nsrec.put("enteredBy", "openaps://" + MainApp.gs(R.string.app_name)); NSUpload.uploadCareportalEntryToNS(nsrec); uploaded++; - ev.message += MainApp.gs(R.string.danar_alarm); + ev.setMessage(ev.getMessage() + MainApp.gs(R.string.danar_alarm)); break; case RecordTypes.RECORD_TYPE_SUSPEND: // TODO: this too case RecordTypes.RECORD_TYPE_DAILY: @@ -226,10 +227,10 @@ public void sync(int what) { log.error("Unknown record type"); break; } - MainApp.bus().post(ev); + RxBus.INSTANCE.send(ev); } - ev.message = String.format(MainApp.gs(R.string.danar_totaluploaded), uploaded); - MainApp.bus().post(ev); + ev.setMessage(String.format(MainApp.gs(R.string.danar_totaluploaded), uploaded)); + RxBus.INSTANCE.send(ev); } catch (JSONException e) { log.error("Unhandled exception", e); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.java deleted file mode 100644 index 0f2ec5f4d23..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.java +++ /dev/null @@ -1,212 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.activities; - -import android.app.Activity; -import android.os.Bundle; -import android.widget.Button; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.Switch; - -import com.squareup.otto.Subscribe; - -import org.slf4j.Logger; -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.events.EventInitializationChanged; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; -import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; -import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; -import info.nightscout.androidaps.utils.NumberPicker; - -/** - * Created by Rumen Georgiev on 5/31/2018. - */ - -public class DanaRUserOptionsActivity extends Activity { - private static Logger log = LoggerFactory.getLogger(L.PUMP); - - Switch timeFormat; - Switch buttonScroll; - Switch beep; - RadioGroup pumpAlarm; - RadioButton pumpAlarmSound; - RadioButton pumpAlarmVibrate; - RadioButton pumpAlarmBoth; - Switch pumpUnits; - NumberPicker screenTimeout; - NumberPicker backlightTimeout; - NumberPicker shutdown; - NumberPicker lowReservoir; - Button saveToPumpButton; - // This is for Dana pumps only - boolean isRS = MainApp.getSpecificPlugin(DanaRSPlugin.class) != null && MainApp.getSpecificPlugin(DanaRSPlugin.class).isEnabled(PluginType.PUMP); - boolean isDanaR = MainApp.getSpecificPlugin(DanaRPlugin.class) != null && MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginType.PUMP); - boolean isDanaRv2 = MainApp.getSpecificPlugin(DanaRv2Plugin.class) != null && MainApp.getSpecificPlugin(DanaRv2Plugin.class).isEnabled(PluginType.PUMP); - - @Override - protected void onResume() { - super.onResume(); - MainApp.bus().register(this); - } - - @Override - protected void onPause() { - super.onPause(); - MainApp.bus().unregister(this); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.danar_user_options); - - timeFormat = (Switch) findViewById(R.id.danar_timeformat); - buttonScroll = (Switch) findViewById(R.id.danar_buttonscroll); - beep = (Switch) findViewById(R.id.danar_beep); - pumpAlarm = (RadioGroup) findViewById(R.id.danar_pumpalarm); - pumpAlarmSound = (RadioButton) findViewById(R.id.danar_pumpalarm_sound); - pumpAlarmVibrate = (RadioButton) findViewById(R.id.danar_pumpalarm_vibrate); - pumpAlarmBoth = (RadioButton) findViewById(R.id.danar_pumpalarm_both); - screenTimeout = (NumberPicker) findViewById(R.id.danar_screentimeout); - backlightTimeout = (NumberPicker) findViewById(R.id.danar_backlight); - pumpUnits = (Switch) findViewById(R.id.danar_units); - shutdown = (NumberPicker) findViewById(R.id.danar_shutdown); - lowReservoir = (NumberPicker) findViewById(R.id.danar_lowreservoir); - saveToPumpButton = (Button) findViewById(R.id.save_user_options); - - saveToPumpButton.setOnClickListener(v -> onSaveClick()); - - DanaRPump pump = DanaRPump.getInstance(); - //used for debugging - if (L.isEnabled(L.PUMP)) - log.debug("UserOptionsLoaded:" + (System.currentTimeMillis() - pump.lastConnection) / 1000 + " s ago" - + "\ntimeDisplayType:" + pump.timeDisplayType - + "\nbuttonScroll:" + pump.buttonScrollOnOff - + "\ntimeDisplayType:" + pump.timeDisplayType - + "\nlcdOnTimeSec:" + pump.lcdOnTimeSec - + "\nbacklight:" + pump.backlightOnTimeSec - + "\npumpUnits:" + pump.units - + "\nlowReservoir:" + pump.lowReservoirRate); - - screenTimeout.setParams((double) pump.lcdOnTimeSec, 5d, 240d, 5d, new DecimalFormat("1"), false); - backlightTimeout.setParams((double) pump.backlightOnTimeSec, 1d, 60d, 1d, new DecimalFormat("1"), false); - shutdown.setParams((double) pump.shutdownHour, 0d, 24d, 1d, new DecimalFormat("1"), true); - lowReservoir.setParams((double) pump.lowReservoirRate, 10d, 60d, 10d, new DecimalFormat("10"), false); - switch (pump.beepAndAlarm) { - case 0x01: - pumpAlarmSound.setChecked(true); - break; - case 0x02: - pumpAlarmVibrate.setChecked(true); - break; - case 0x11: - pumpAlarmBoth.setChecked(true); - break; - case 0x101: - pumpAlarmSound.setChecked(true); - beep.setChecked(true); - break; - case 0x110: - pumpAlarmVibrate.setChecked(true); - beep.setChecked(true); - break; - case 0x111: - pumpAlarmBoth.setChecked(true); - beep.setChecked(true); - break; - } - if (pump.lastSettingsRead == 0) - log.error("No settings loaded from pump!"); - else - setData(); - } - - public void setData() { - DanaRPump pump = DanaRPump.getInstance(); - // in DanaRS timeDisplay values are reversed - timeFormat.setChecked((!isRS && pump.timeDisplayType != 0) || (isRS && pump.timeDisplayType == 0)); - buttonScroll.setChecked(pump.buttonScrollOnOff != 0); - beep.setChecked(pump.beepAndAlarm > 4); - screenTimeout.setValue((double) pump.lcdOnTimeSec); - backlightTimeout.setValue((double) pump.backlightOnTimeSec); - pumpUnits.setChecked(pump.getUnits() != null && pump.getUnits().equals(Constants.MMOL)); - shutdown.setValue((double) pump.shutdownHour); - lowReservoir.setValue((double) pump.lowReservoirRate); - } - - @Subscribe - public void onEventInitializationChanged(EventInitializationChanged ignored) { - runOnUiThread(this::setData); - } - - public void onSaveClick() { - if (!isRS && !isDanaR && !isDanaRv2) { - //exit if pump is not DanaRS, Dana!, or DanaR with upgraded firmware - return; - } - DanaRPump pump = DanaRPump.getInstance(); - if (timeFormat.isChecked()) - pump.timeDisplayType = 1; - else - pump.timeDisplayType = 0; - // displayTime on RS is reversed - if (isRS) { - if (timeFormat.isChecked()) - pump.timeDisplayType = 0; - else - pump.timeDisplayType = 1; - } - if (buttonScroll.isChecked()) - pump.buttonScrollOnOff = 1; - else - pump.buttonScrollOnOff = 0; - - pump.beepAndAlarm = 1; // default - if (pumpAlarmSound.isChecked()) pump.beepAndAlarm = 1; - else if (pumpAlarmVibrate.isChecked()) pump.beepAndAlarm = 2; - else if (pumpAlarmBoth.isChecked()) pump.beepAndAlarm = 3; - if (beep.isChecked()) pump.beepAndAlarm += 4; - - - // step is 5 seconds - int screenTimeoutValue = !screenTimeout.getText().isEmpty() ? (Integer.parseInt(screenTimeout.getText().toString()) / 5) * 5 : 5; - if (screenTimeoutValue > 4 && screenTimeoutValue < 241) { - pump.lcdOnTimeSec = screenTimeoutValue; - } else { - pump.lcdOnTimeSec = 5; - } - int backlightTimeoutValue = !backlightTimeout.getText().isEmpty() ? Integer.parseInt(backlightTimeout.getText().toString()) : 1; - if (backlightTimeoutValue > 0 && backlightTimeoutValue < 61) { - pump.backlightOnTimeSec = backlightTimeoutValue; - } - if (pumpUnits.isChecked()) { - pump.units = 1; - } else { - pump.units = 0; - } - int shutDownValue = !shutdown.getText().isEmpty() ? Integer.parseInt(shutdown.getText().toString()) : 0; - if (shutDownValue > -1 && shutDownValue < 25) { - pump.shutdownHour = shutDownValue; - } else { - pump.shutdownHour = 0; - } - int lowReservoirValue = !lowReservoir.getText().isEmpty() ? (Integer.parseInt(lowReservoir.getText().toString()) * 10) / 10 : 10; - if (lowReservoirValue > 9 && lowReservoirValue < 51) { - pump.lowReservoirRate = lowReservoirValue; - } else - pump.lowReservoirRate = 10; - - ConfigBuilderPlugin.getPlugin().getCommandQueue().setUserOptions(null); - finish(); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.kt new file mode 100644 index 00000000000..a3a6c3c0839 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/activities/DanaRUserOptionsActivity.kt @@ -0,0 +1,159 @@ +package info.nightscout.androidaps.plugins.pump.danaR.activities + +import android.content.Intent +import android.os.Bundle +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.events.EventInitializationChanged +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus.toObservable +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin +import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.FabricPrivacy +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.danar_user_options_activity.* +import org.slf4j.LoggerFactory +import java.text.DecimalFormat +import kotlin.math.max +import kotlin.math.min + +class DanaRUserOptionsActivity : NoSplashAppCompatActivity() { + + private val log = LoggerFactory.getLogger(L.PUMP) + + private val disposable = CompositeDisposable() + + // This is for Dana pumps only + private var isRS = DanaRSPlugin.getPlugin().isEnabled(PluginType.PUMP) + private var isDanaR = DanaRPlugin.getPlugin().isEnabled(PluginType.PUMP) + private var isDanaRv2 = DanaRv2Plugin.getPlugin().isEnabled(PluginType.PUMP) + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(toObservable(EventInitializationChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ setData() }) { FabricPrivacy.logException(it) } + ) + } + + @Synchronized + override fun onPause() { + disposable.clear() + super.onPause() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.danar_user_options_activity) + + save_user_options.setOnClickListener { onSaveClick() } + val pump = DanaRPump.getInstance() + + if (L.isEnabled(L.PUMP)) + log.debug("UserOptionsLoaded:" + (System.currentTimeMillis() - pump.lastConnection) / 1000 + " s ago" + + "\ntimeDisplayType:" + pump.timeDisplayType + + "\nbuttonScroll:" + pump.buttonScrollOnOff + + "\ntimeDisplayType:" + pump.timeDisplayType + + "\nlcdOnTimeSec:" + pump.lcdOnTimeSec + + "\nbackLight:" + pump.backlightOnTimeSec + + "\npumpUnits:" + pump.units + + "\nlowReservoir:" + pump.lowReservoirRate) + + danar_screentimeout.setParams(pump.lcdOnTimeSec.toDouble(), 5.0, 240.0, 5.0, DecimalFormat("1"), false, save_user_options) + danar_backlight.setParams(pump.backlightOnTimeSec.toDouble(), 1.0, 60.0, 1.0, DecimalFormat("1"), false, save_user_options) + danar_shutdown.setParams(pump.shutdownHour.toDouble(), 0.0, 24.0, 1.0, DecimalFormat("1"), true, save_user_options) + danar_lowreservoir.setParams(pump.lowReservoirRate.toDouble(), 10.0, 60.0, 10.0, DecimalFormat("10"), false, save_user_options) + when (pump.beepAndAlarm) { + 0x01 -> danar_pumpalarm_sound.isChecked = true + 0x02 -> danar_pumpalarm_vibrate.isChecked = true + 0x11 -> danar_pumpalarm_both.isChecked = true + + 0x101 -> { + danar_pumpalarm_sound.isChecked = true + danar_beep.isChecked = true + } + + 0x110 -> { + danar_pumpalarm_vibrate.isChecked = true + danar_beep.isChecked = true + } + + 0x111 -> { + danar_pumpalarm_both.isChecked = true + danar_beep.isChecked = true + } + } + if (pump.lastSettingsRead == 0L) + log.error("No settings loaded from pump!") else setData() + } + + fun setData() { + val pump = DanaRPump.getInstance() + // in DanaRS timeDisplay values are reversed + danar_timeformat.isChecked = !isRS && pump.timeDisplayType != 0 || isRS && pump.timeDisplayType == 0 + danar_buttonscroll.isChecked = pump.buttonScrollOnOff != 0 + danar_beep.isChecked = pump.beepAndAlarm > 4 + danar_screentimeout.value = pump.lcdOnTimeSec.toDouble() + danar_backlight.value = pump.backlightOnTimeSec.toDouble() + danar_units.isChecked = pump.getUnits() == Constants.MMOL + danar_shutdown.value = pump.shutdownHour.toDouble() + danar_lowreservoir.value = pump.lowReservoirRate.toDouble() + } + + private fun onSaveClick() { + //exit if pump is not DanaRS, DanaR, or DanaR with upgraded firmware + if (!isRS && !isDanaR && !isDanaRv2) return + + val pump = DanaRPump.getInstance() + + if (isRS) // displayTime on RS is reversed + pump.timeDisplayType = if (danar_timeformat.isChecked) 0 else 1 + else + pump.timeDisplayType = if (danar_timeformat.isChecked) 1 else 0 + + pump.buttonScrollOnOff = if (danar_buttonscroll.isChecked) 1 else 0 + pump.beepAndAlarm = when { + danar_pumpalarm_sound.isChecked -> 1 + danar_pumpalarm_vibrate.isChecked -> 2 + danar_pumpalarm_both.isChecked -> 3 + else -> 1 + } + if (danar_beep.isChecked) pump.beepAndAlarm += 4 + + // step is 5 seconds, 5 to 240 + pump.lcdOnTimeSec = min(max(danar_screentimeout.value.toInt() / 5 * 5, 5), 240) + // 1 to 60 + pump.backlightOnTimeSec = min(max(danar_backlight.value.toInt(), 1), 60) + + pump.units = if (danar_units.isChecked) 1 else 0 + + pump.shutdownHour = min(danar_shutdown.value.toInt(),24) + + // 10 to 50 + pump.lowReservoirRate = min(max(danar_lowreservoir.value.toInt() * 10 / 10, 10), 50) + + ConfigBuilderPlugin.getPlugin().commandQueue.setUserOptions(object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + i.putExtra("soundid", R.raw.boluserror) + i.putExtra("status", result.comment) + i.putExtra("title", MainApp.gs(R.string.pumperror)) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + MainApp.instance().startActivity(i) + } + } + }) + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageBase.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageBase.java index 57244ecc20e..bf2ca4b83bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageBase.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageBase.java @@ -105,6 +105,9 @@ public void handleMessage(byte[] bytes) { } } + public void handleMessageNotReceived() { + } + public int getCommand() { int command = byteFromRawBuff(buffer, 5) | (byteFromRawBuff(buffer, 4) << 8); return command; @@ -189,4 +192,8 @@ public static String toHexString(byte[] buff) { return sb.toString(); } + + public boolean isReceived() { + return received; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTable.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTable.java deleted file mode 100644 index d7ec39f6b76..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTable.java +++ /dev/null @@ -1,82 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.comm; - -import java.util.HashMap; - -/** - * Created by mike on 28.05.2016. - */ -public class MessageHashTable { - public static HashMap messages = null; - - static { - if (messages == null) { - messages = new HashMap(); - put(new MsgBolusStop()); // 0x0101 CMD_MEALINS_STOP - put(new MsgBolusStart()); // 0x0102 CMD_MEALINS_START_DATA - put(new MsgBolusStartWithSpeed()); // 0x0104 CMD_MEALINS_START_DATA_SPEED - put(new MsgBolusProgress()); // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS - put(new MsgStatusProfile()); // 0x0204 CMD_PUMP_CALCULATION_SETTING - put(new MsgStatusTempBasal()); // 0x0205 CMD_PUMP_EXERCISE_MODE - put(new MsgStatusBolusExtended()); // 0x0207 CMD_PUMP_EXPANS_INS_I - put(new MsgStatusBasic()); // 0x020A CMD_PUMP_INITVIEW_I - put(new MsgStatus()); // 0x020B CMD_PUMP_STATUS - put(new MsgInitConnStatusTime()); // 0x0301 CMD_PUMPINIT_TIME_INFO - put(new MsgInitConnStatusBolus()); // 0x0302 CMD_PUMPINIT_BOLUS_INFO - put(new MsgInitConnStatusBasic()); // 0x0303 CMD_PUMPINIT_INIT_INFO - put(new MsgInitConnStatusOption()); // 0x0304 CMD_PUMPINIT_OPTION - put(new MsgSetTempBasalStart()); // 0x0401 CMD_PUMPSET_EXERCISE_S - put(new MsgSetCarbsEntry()); // 0x0402 CMD_PUMPSET_HIS_S - put(new MsgSetTempBasalStop()); // 0x0403 CMD_PUMPSET_EXERCISE_STOP - put(new MsgSetExtendedBolusStop()); // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP - put(new MsgSetExtendedBolusStart()); // 0x0407 CMD_PUMPSET_EXPANS_INS_S - put(new MsgError()); // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS - put(new MsgPCCommStart()); // 0x3001 CMD_CONNECT - put(new MsgPCCommStop()); // 0x3002 CMD_DISCONNECT - put(new MsgHistoryBolus()); // 0x3101 CMD_HISTORY_MEAL_INS - put(new MsgHistoryDailyInsulin()); // 0x3102 CMD_HISTORY_DAY_INS - put(new MsgHistoryGlucose()); // 0x3104 CMD_HISTORY_GLUCOSE - put(new MsgHistoryAlarm()); // 0x3105 CMD_HISTORY_ALARM - put(new MsgHistoryError()); // 0x3106 CMD_HISTORY_ERROR - put(new MsgHistoryCarbo()); // 0x3107 CMD_HISTORY_CARBOHY - put(new MsgHistoryRefill()); // 0x3108 CMD_HISTORY_REFILL - put(new MsgHistorySuspend()); // 0x3109 CMD_HISTORY_SUSPEND - put(new MsgHistoryBasalHour()); // 0x310A CMD_HISTORY_BASAL_HOUR - put(new MsgHistoryDone()); // 0x31F1 CMD_HISTORY_DONT_USED - put(new MsgSettingBasal()); // 0x3202 CMD_SETTING_V_BASAL_INS_I - put(new MsgSettingMeal()); // 0x3203 CMD_SETTING_V_MEAL_SETTING_I - put(new MsgSettingProfileRatios()); // 0x3204 CMD_SETTING_V_CCC_I - put(new MsgSettingMaxValues()); // 0x3205 CMD_SETTING_V_MAX_VALUE_I - put(new MsgSettingBasalProfileAll()); // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL - put(new MsgSettingShippingInfo()); // 0x3207 CMD_SETTING_V_SHIPPING_I - put(new MsgSettingGlucose()); // 0x3209 CMD_SETTING_V_GLUCOSEandEASY - put(new MsgSettingPumpTime()); // 0x320A CMD_SETTING_V_TIME_I - put(new MsgSettingUserOptions()); // 0x320B CMD_SETTING_V_USER_OPTIONS - put(new MsgSettingActiveProfile()); // 0x320C CMD_SETTING_V_PROFILE_NUMBER - put(new MsgSettingProfileRatiosAll()); // 0x320D CMD_SETTING_V_CIR_CF_VALUE - put(new MsgSetSingleBasalProfile()); // 0x3302 CMD_SETTING_BASAL_INS_S - put(new MsgSetBasalProfile()); // 0x3306 CMD_SETTING_BASAL_PROFILE_S - put(new MsgSetUserOptions()); // 0x330B CMD_SETTING_USER_OPTIONS_S - put(new MsgSetActivateBasalProfile()); // 0x330C CMD_SETTING_PROFILE_NUMBER_S - put(new MsgHistoryAllDone()); // 0x41F1 CMD_HISTORY_ALL_DONE - put(new MsgHistoryAll()); // 0x41F2 CMD_HISTORY_ALL - put(new MsgHistoryNewDone()); // 0x42F1 CMD_HISTORY_NEW_DONE - put(new MsgHistoryNew()); // 0x42F2 CMD_HISTORY_NEW - put(new MsgCheckValue()); // 0xF0F1 CMD_PUMP_CHECK_VALUE - } - } - - public static void put(MessageBase message) { - int command = message.getCommand(); - //String name = MessageOriginalNames.getName(command); - messages.put(command, message); - //log.debug(String.format("%04x ", command) + " " + name); - } - - public static MessageBase findMessage(Integer command) { - if (messages.containsKey(command)) { - return messages.get(command); - } else { - return new MessageBase(); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableBase.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableBase.kt new file mode 100644 index 00000000000..9c524e02a6f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableBase.kt @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.pump.danaR.comm + +interface MessageHashTableBase { + fun put(message: MessageBase) + fun findMessage(command: Int): MessageBase +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableR.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableR.kt new file mode 100644 index 00000000000..6d3129f5ba2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MessageHashTableR.kt @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.plugins.pump.danaR.comm + +import java.util.* + +object MessageHashTableR : MessageHashTableBase { + var messages: HashMap = HashMap() + + init { + put(MsgBolusStop()) // 0x0101 CMD_MEALINS_STOP + put(MsgBolusStart()) // 0x0102 CMD_MEALINS_START_DATA + put(MsgBolusStartWithSpeed()) // 0x0104 CMD_MEALINS_START_DATA_SPEED + put(MsgBolusProgress()) // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS + put(MsgStatusProfile()) // 0x0204 CMD_PUMP_CALCULATION_SETTING + put(MsgStatusTempBasal()) // 0x0205 CMD_PUMP_EXERCISE_MODE + put(MsgStatusBolusExtended()) // 0x0207 CMD_PUMP_EXPANS_INS_I + put(MsgStatusBasic()) // 0x020A CMD_PUMP_INITVIEW_I + put(MsgStatus()) // 0x020B CMD_PUMP_STATUS + put(MsgInitConnStatusTime()) // 0x0301 CMD_PUMPINIT_TIME_INFO + put(MsgInitConnStatusBolus()) // 0x0302 CMD_PUMPINIT_BOLUS_INFO + put(MsgInitConnStatusBasic()) // 0x0303 CMD_PUMPINIT_INIT_INFO + put(MsgInitConnStatusOption()) // 0x0304 CMD_PUMPINIT_OPTION + put(MsgSetTempBasalStart()) // 0x0401 CMD_PUMPSET_EXERCISE_S + put(MsgSetCarbsEntry()) // 0x0402 CMD_PUMPSET_HIS_S + put(MsgSetTempBasalStop()) // 0x0403 CMD_PUMPSET_EXERCISE_STOP + put(MsgSetExtendedBolusStop()) // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP + put(MsgSetExtendedBolusStart()) // 0x0407 CMD_PUMPSET_EXPANS_INS_S + put(MsgError()) // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS + put(MsgPCCommStart()) // 0x3001 CMD_CONNECT + put(MsgPCCommStop()) // 0x3002 CMD_DISCONNECT + put(MsgHistoryBolus()) // 0x3101 CMD_HISTORY_MEAL_INS + put(MsgHistoryDailyInsulin()) // 0x3102 CMD_HISTORY_DAY_INS + put(MsgHistoryGlucose()) // 0x3104 CMD_HISTORY_GLUCOSE + put(MsgHistoryAlarm()) // 0x3105 CMD_HISTORY_ALARM + put(MsgHistoryError()) // 0x3106 CMD_HISTORY_ERROR + put(MsgHistoryCarbo()) // 0x3107 CMD_HISTORY_CARBOHY + put(MsgHistoryRefill()) // 0x3108 CMD_HISTORY_REFILL + put(MsgHistorySuspend()) // 0x3109 CMD_HISTORY_SUSPEND + put(MsgHistoryBasalHour()) // 0x310A CMD_HISTORY_BASAL_HOUR + put(MsgHistoryDone()) // 0x31F1 CMD_HISTORY_DONT_USED + put(MsgSettingBasal()) // 0x3202 CMD_SETTING_V_BASAL_INS_I + put(MsgSettingMeal()) // 0x3203 CMD_SETTING_V_MEAL_SETTING_I + put(MsgSettingProfileRatios()) // 0x3204 CMD_SETTING_V_CCC_I + put(MsgSettingMaxValues()) // 0x3205 CMD_SETTING_V_MAX_VALUE_I + put(MsgSettingBasalProfileAll()) // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL + put(MsgSettingShippingInfo()) // 0x3207 CMD_SETTING_V_SHIPPING_I + put(MsgSettingGlucose()) // 0x3209 CMD_SETTING_V_GLUCOSEandEASY + put(MsgSettingPumpTime()) // 0x320A CMD_SETTING_V_TIME_I + put(MsgSettingUserOptions()) // 0x320B CMD_SETTING_V_USER_OPTIONS + put(MsgSettingActiveProfile()) // 0x320C CMD_SETTING_V_PROFILE_NUMBER + put(MsgSettingProfileRatiosAll()) // 0x320D CMD_SETTING_V_CIR_CF_VALUE + put(MsgSetSingleBasalProfile()) // 0x3302 CMD_SETTING_BASAL_INS_S + put(MsgSetBasalProfile()) // 0x3306 CMD_SETTING_BASAL_PROFILE_S + put(MsgSetUserOptions()) // 0x330B CMD_SETTING_USER_OPTIONS_S + put(MsgSetActivateBasalProfile()) // 0x330C CMD_SETTING_PROFILE_NUMBER_S + put(MsgHistoryAllDone()) // 0x41F1 CMD_HISTORY_ALL_DONE + put(MsgHistoryAll()) // 0x41F2 CMD_HISTORY_ALL + put(MsgHistoryNewDone()) // 0x42F1 CMD_HISTORY_NEW_DONE + put(MsgHistoryNew()) // 0x42F2 CMD_HISTORY_NEW + put(MsgCheckValue()) // 0xF0F1 CMD_PUMP_CHECK_VALUE + } + + override fun put(message: MessageBase) { + messages[message.command] = message + } + + override fun findMessage(command: Int): MessageBase { + return messages[command] ?: MessageBase() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusProgress.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusProgress.java index 6fb19932374..362b72c9294 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusProgress.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusProgress.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.treatments.Treatment; @@ -37,15 +38,15 @@ public void handleMessage(byte[] bytes) { lastReceive = System.currentTimeMillis(); Double done = (amount * 100 - progress) / 100d; t.insulin = done; - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.status = String.format(MainApp.gs(R.string.bolusdelivering), done); - bolusingEvent.t = t; - bolusingEvent.percent = Math.min((int) (done / amount * 100), 100); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.bolusdelivering), done)); + bolusingEvent.setT(t); + bolusingEvent.setPercent(Math.min((int) (done / amount * 100), 100)); if (L.isEnabled(L.PUMPCOMM)) { log.debug("Bolus remaining: " + progress + " delivered: " + done); } - MainApp.bus().post(bolusingEvent); + RxBus.INSTANCE.send(bolusingEvent); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusStop.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusStop.java index 39531b8b10d..2ea50297fb7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusStop.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgBolusStop.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.treatments.Treatment; @@ -35,15 +36,15 @@ public MsgBolusStop(Double amount, Treatment t) { public void handleMessage(byte[] bytes) { if (L.isEnabled(L.PUMPCOMM)) log.debug("Messsage received"); - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; stopped = true; if (!forced) { t.insulin = amount; - bolusingEvent.status = MainApp.gs(R.string.overview_bolusprogress_delivered); - bolusingEvent.percent = 100; + bolusingEvent.setStatus(MainApp.gs(R.string.overview_bolusprogress_delivered)); + bolusingEvent.setPercent(100); } else { - bolusingEvent.status = MainApp.gs(R.string.overview_bolusprogress_stoped); + bolusingEvent.setStatus(MainApp.gs(R.string.overview_bolusprogress_stoped)); } - MainApp.bus().post(bolusingEvent); + RxBus.INSTANCE.send(bolusingEvent); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgCheckValue.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgCheckValue.java index d40f8f0cd6e..317fe9db814 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgCheckValue.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgCheckValue.java @@ -32,7 +32,7 @@ public void handleMessage(byte[] bytes) { pump.protocol = intFromBuff(bytes, 1, 1); pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.EXPORT_MODEL) { - MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); + DanaRPlugin.getPlugin().disconnect("Wrong Model"); log.debug("Wrong model selected"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgError.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgError.java index f6978947786..a6fdaa8d0d2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgError.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgError.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; @@ -44,10 +45,10 @@ public void handleMessage(byte[] bytes) { } if (errorCode < 8) { // bolus delivering stopped - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; MsgBolusStop.stopped = true; - bolusingEvent.status = errorString; - MainApp.bus().post(bolusingEvent); + bolusingEvent.setStatus(errorString); + RxBus.INSTANCE.send(bolusingEvent); failed=true; } if (L.isEnabled(L.PUMPCOMM)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgHistoryAll.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgHistoryAll.java index 7de2489c917..955d7fc0909 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgHistoryAll.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgHistoryAll.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.DanaRHistoryRecord; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRSyncStatus; import info.nightscout.androidaps.utils.DateUtil; @@ -33,8 +34,6 @@ public void handleMessage(byte[] bytes) { byte paramByte8 = (byte) intFromBuff(bytes, 7, 1); double value = (double) intFromBuff(bytes, 8, 2); - EventDanaRSyncStatus ev = new EventDanaRSyncStatus(); - DanaRHistoryRecord danaRHistoryRecord = new DanaRHistoryRecord(); danaRHistoryRecord.recordCode = recordCode; @@ -145,11 +144,6 @@ public void handleMessage(byte[] bytes) { } MainApp.getDbHelper().createOrUpdate(danaRHistoryRecord); - - ev.message = DateUtil.dateAndTimeString(danaRHistoryRecord.recordDate); - ev.message += " " + messageType; - MainApp.bus().post(ev); - - return; + RxBus.INSTANCE.send(new EventDanaRSyncStatus(DateUtil.dateAndTimeString(danaRHistoryRecord.recordDate) + " " + messageType)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusBolus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusBolus.java index 27d5aa5d2d0..79fac775a5a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusBolus.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -45,9 +46,9 @@ public void handleMessage(byte[] bytes) { if (!pump.isExtendedBolusEnabled) { Notification notification = new Notification(Notification.EXTENDED_BOLUS_DISABLED, MainApp.gs(R.string.danar_enableextendedbolus), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusOption.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusOption.java index 220a4d5fc07..f615b4d5591 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusOption.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusOption.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; @@ -45,13 +46,13 @@ public void handleMessage(byte[] bytes) { if (!DanaRPump.getInstance().isPasswordOK()) { Notification notification = new Notification(Notification.WRONG_PUMP_PASSWORD, MainApp.gs(R.string.wrongpumppassword), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.WRONG_PUMP_PASSWORD)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.WRONG_PUMP_PASSWORD)); } // This is last message of initial sequence - if (ConfigBuilderPlugin.getPlugin().getActivePump() != null ) + if (ConfigBuilderPlugin.getPlugin().getActivePump() != null) ConfigBuilderPlugin.getPlugin().getActivePump().finishHandshaking(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusTime.java index 72e538355a4..974e63fe803 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgInitConnStatusTime.java @@ -5,9 +5,10 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventRefreshGui; +import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -29,24 +30,24 @@ public MsgInitConnStatusTime() { public void handleMessage(byte[] bytes) { if (bytes.length - 10 > 7) { Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.gs(R.string.pumpdrivercorrected), Notification.NORMAL); - MainApp.bus().post(new EventNewNotification(notification)); - MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + DanaRPlugin.getPlugin().disconnect("Wrong Model"); log.error("Wrong model selected. Switching to Korean DanaR"); - 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); + DanaRKoreanPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, true); + DanaRKoreanPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, true); + DanaRPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, false); + DanaRPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, false); DanaRPump.reset(); // mark not initialized //If profile coming from pump, switch it as well - if (MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginType.PROFILE)) { - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); + if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)) { + (DanaRPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, false); + (DanaRKoreanPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, true); } ConfigBuilderPlugin.getPlugin().storeSettings("ChangingDanaDriver"); - MainApp.bus().post(new EventRefreshGui()); + RxBus.INSTANCE.send(new EventRebuildTabs()); ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("PumpDriverChange", null); // force new connection failed = false; return; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetBasalProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetBasalProfile.java index 7ea6719e30c..89cd1c95f44 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetBasalProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetBasalProfile.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -37,12 +38,12 @@ public void handleMessage(byte[] bytes) { if (L.isEnabled(L.PUMPCOMM)) log.debug("Set basal profile result: " + result + " FAILED!!!"); Notification reportFail = new Notification(Notification.PROFILE_SET_FAILED, MainApp.gs(R.string.profile_set_failed), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); } else { if (L.isEnabled(L.PUMPCOMM)) log.debug("Set basal profile result: " + result); Notification reportOK = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(reportOK)); + RxBus.INSTANCE.send(new EventNewNotification(reportOK)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetSingleBasalProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetSingleBasalProfile.java index 29c70c9379b..8888f17b1c3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetSingleBasalProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSetSingleBasalProfile.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; @@ -34,12 +35,12 @@ public void handleMessage(byte[] bytes) { if (L.isEnabled(L.PUMPCOMM)) log.debug("Set basal profile result: " + result + " FAILED!!!"); Notification reportFail = new Notification(Notification.PROFILE_SET_FAILED, MainApp.gs(R.string.profile_set_failed), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); } else { if (L.isEnabled(L.PUMPCOMM)) log.debug("Set basal profile result: " + result); Notification reportOK = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(reportOK)); + RxBus.INSTANCE.send(new EventNewNotification(reportOK)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingMeal.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingMeal.java index b8be76560b1..6756d9d5048 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingMeal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingMeal.java @@ -7,6 +7,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -51,16 +52,16 @@ public void handleMessage(byte[] bytes) { if (pump.basalStep != 0.01d) { Notification notification = new Notification(Notification.WRONGBASALSTEP, MainApp.gs(R.string.danar_setbasalstep001), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.WRONGBASALSTEP)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.WRONGBASALSTEP)); } if (pump.isConfigUD) { Notification notification = new Notification(Notification.UD_MODE_ENABLED, MainApp.gs(R.string.danar_switchtouhmode), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.UD_MODE_ENABLED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.UD_MODE_ENABLED)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingPumpTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingPumpTime.java index d22352e69f6..23a6ba6f757 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingPumpTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgSettingPumpTime.java @@ -34,4 +34,10 @@ public void handleMessage(byte[] bytes) { DanaRPump.getInstance().pumpTime = time; } + + @Override + public void handleMessageNotReceived() { + DanaRPump.getInstance().pumpTime = 0; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java index 9b27561d5ad..aa272b546d8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusBolusExtended.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java index 76fa0e9f438..00b3bf5f69b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/comm/MsgStatusTempBasal.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaR.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.java deleted file mode 100644 index 9efdee10573..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.events; - -import info.nightscout.androidaps.events.Event; - -/** - * Created by mike on 08.07.2016. - */ -public class EventDanaRNewStatus extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.kt new file mode 100644 index 00000000000..67b12d954cf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRNewStatus.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.danaR.events + +import info.nightscout.androidaps.events.Event + +class EventDanaRNewStatus : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.java deleted file mode 100644 index 5fc0bd740f5..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaR.events; - -import info.nightscout.androidaps.events.Event; - -/** - * Created by mike on 20.07.2016. - */ -public class EventDanaRSyncStatus extends Event { - public String message; - - public EventDanaRSyncStatus() { - } - - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.kt new file mode 100644 index 00000000000..59ad7e2d83b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/events/EventDanaRSyncStatus.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.danaR.events + +import info.nightscout.androidaps.events.Event + +class EventDanaRSyncStatus(var message: String) : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java index 9e3c1ff3cae..839debe9be1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/AbstractDanaRExecutionService.java @@ -23,6 +23,7 @@ import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStop; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgHistoryAlarm; @@ -105,7 +106,7 @@ public void onReceive(Context context, Intent intent) { if (mSerialIOThread != null) { mSerialIOThread.disconnect("BT disconnection broadcast"); } - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); } } } @@ -135,7 +136,7 @@ public boolean isHandshakeInProgress() { public void finishHandshaking() { mHandshakeInProgress = false; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED, 0)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED, 0)); } public void disconnect(String from) { @@ -243,7 +244,7 @@ protected void waitForWholeMinute() { long timeToWholeMinute = (60000 - time % 60000); if (timeToWholeMinute > 59800 || timeToWholeMinute < 3000) break; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.waitingfortimesynchronization, (int) (timeToWholeMinute / 1000)))); + RxBus.INSTANCE.send(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/pump/danaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java index 22d98368a64..848abd9b52e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaR/services/DanaRExecutionService.java @@ -5,8 +5,6 @@ import android.os.Binder; import android.os.SystemClock; -import com.squareup.otto.Subscribe; - import java.io.IOException; import java.util.Date; @@ -22,16 +20,18 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; import info.nightscout.androidaps.plugins.pump.danaR.SerialIOThread; import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageHashTableR; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusProgress; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStartWithSpeed; @@ -65,15 +65,50 @@ import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; public class DanaRExecutionService extends AbstractDanaRExecutionService { + private CompositeDisposable disposable = new CompositeDisposable(); public DanaRExecutionService() { mBinder = new LocalBinder(); - registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); + } + + @Override + public void onCreate() { + super.onCreate(); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (mSerialIOThread != null) + mSerialIOThread.disconnect("EventPreferenceChange"); + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.PUMP)) + log.debug("EventAppExit received"); + + if (mSerialIOThread != null) + mSerialIOThread.disconnect("Application exit"); + MainApp.instance().getApplicationContext().unregisterReceiver(receiver); + stopSelf(); + }, FabricPrivacy::logException) + ); + } + + @Override + public void onDestroy() { + disposable.clear(); + super.onDestroy(); } public class LocalBinder extends Binder { @@ -82,21 +117,6 @@ public DanaRExecutionService getServiceInstance() { } } - private void registerBus() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - } - public void connect() { if (mConnectionInProgress) return; @@ -123,9 +143,9 @@ public void connect() { if (mSerialIOThread != null) { mSerialIOThread.disconnect("Recreate SerialIOThread"); } - mSerialIOThread = new SerialIOThread(mRfcommSocket); + mSerialIOThread = new SerialIOThread(mRfcommSocket, MessageHashTableR.INSTANCE); mHandshakeInProgress = true; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.HANDSHAKING, 0)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.HANDSHAKING, 0)); } mConnectionInProgress = false; @@ -135,7 +155,7 @@ public void connect() { public void getPumpStatus() { DanaRPump danaRPump = DanaRPump.getInstance(); try { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); MsgStatus statusMsg = new MsgStatus(); MsgStatusBasic statusBasicMsg = new MsgStatusBasic(); MsgStatusTempBasal tempStatusMsg = new MsgStatusTempBasal(); @@ -151,11 +171,11 @@ public void getPumpStatus() { mSerialIOThread.sendMessage(statusMsg); mSerialIOThread.sendMessage(statusBasicMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); mSerialIOThread.sendMessage(tempStatusMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); mSerialIOThread.sendMessage(exStatusMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); long now = System.currentTimeMillis(); danaRPump.lastConnection = now; @@ -163,15 +183,15 @@ public void getPumpStatus() { Profile profile = ProfileFunctions.getInstance().getProfile(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile != null && Math.abs(danaRPump.currentBasal - profile.getBasal()) >= pump.getPumpDescription().basalStep) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingBasal()); if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { - MainApp.bus().post(new EventProfileNeedsUpdate()); + RxBus.INSTANCE.send(new EventProfileNeedsUpdate()); } } if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !pump.isInitialized()) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); mSerialIOThread.sendMessage(new MsgSettingMeal()); @@ -183,8 +203,17 @@ public void getPumpStatus() { mSerialIOThread.sendMessage(new MsgSettingProfileRatios()); mSerialIOThread.sendMessage(new MsgSettingProfileRatiosAll()); mSerialIOThread.sendMessage(new MsgSettingUserOptions()); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); + if (danaRPump.pumpTime == 0) { + // initial handshake was not successfull + // deinitialize pump + danaRPump.lastConnection = 0; + danaRPump.lastSettingsRead = 0; + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); + return; + } long timeDiff = (danaRPump.pumpTime - System.currentTimeMillis()) / 1000L; if (L.isEnabled(L.PUMP)) log.debug("Pump time difference: " + timeDiff + " seconds"); @@ -198,15 +227,15 @@ public void getPumpStatus() { danaRPump.lastSettingsRead = now; } - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { if (L.isEnabled(L.PUMP)) log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); if (System.currentTimeMillis() > lastApproachingDailyLimit + 30 * 60 * 1000) { Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.gs(R.string.approachingdailylimit), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); NSUpload.uploadError(MainApp.gs(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); lastApproachingDailyLimit = System.currentTimeMillis(); } @@ -220,41 +249,41 @@ public boolean tempBasal(int percent, int durationInHours) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; if (danaRPump.isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); SystemClock.sleep(500); } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean tempBasalStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolus(double insulin, int durationInHalfHours) { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStart(insulin, (byte) (durationInHalfHours & 0xFF))); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolusStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStop()); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -302,9 +331,9 @@ public boolean bolus(double amount, int carbs, long carbtime, final Treatment t) } SystemClock.sleep(300); - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.t = t; - bolusingEvent.percent = 99; + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setT(t); + bolusingEvent.setPercent(99); mBolusingTreatment = null; @@ -328,8 +357,8 @@ public boolean bolus(double amount, int carbs, long carbtime, final Treatment t) while (System.currentTimeMillis() < expectedEnd) { long waitTime = expectedEnd - System.currentTimeMillis(); - bolusingEvent.status = String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000); - MainApp.bus().post(bolusingEvent); + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000)); + RxBus.INSTANCE.send(bolusingEvent); SystemClock.sleep(1000); } @@ -354,7 +383,7 @@ public void run() { try { o.wait(); } catch (InterruptedException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } } else { @@ -384,7 +413,7 @@ public boolean tempBasalShortDuration(int percent, int durationInMinutes) { public boolean updateBasalsInPump(final Profile profile) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); double[] basal = DanaRPump.getInstance().buildDanaRProfileRecord(profile); MsgSetBasalProfile msgSet = new MsgSetBasalProfile((byte) 0, basal); mSerialIOThread.sendMessage(msgSet); @@ -392,23 +421,10 @@ public boolean updateBasalsInPump(final Profile profile) { mSerialIOThread.sendMessage(msgActivate); danaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } - @Subscribe - public void onStatusEvent(EventAppExit event) { - if (L.isEnabled(L.PUMP)) - log.debug("EventAppExit received"); - - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - - MainApp.instance().getApplicationContext().unregisterReceiver(receiver); - - stopSelf(); - } - public PumpEnactResult setUserOptions() { if (!isConnected()) return new PumpEnactResult().success(false); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java index 3b4aa0ee4c5..2645e25ade0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/DanaRKoreanPlugin.java @@ -5,10 +5,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; - -import com.squareup.otto.Subscribe; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -22,7 +18,7 @@ import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.danaR.AbstractDanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; @@ -30,13 +26,17 @@ import info.nightscout.androidaps.plugins.pump.danaRKorean.services.DanaRKoreanExecutionService; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.SP; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 05.08.2016. */ public class DanaRKoreanPlugin extends AbstractDanaRPlugin { + private CompositeDisposable disposable = new CompositeDisposable(); private static DanaRKoreanPlugin plugin = null; @@ -53,36 +53,32 @@ public DanaRKoreanPlugin() { pumpDescription.setPumpDescription(PumpType.DanaRKorean); } - @Override - public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity context) { - boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); - if (allowHardwarePump || context == null) { - pluginSwitcher.invoke(); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(R.string.allow_hardware_pump_text) - .setPositiveButton(R.string.yes, (dialog, id) -> { - pluginSwitcher.invoke(); - SP.putBoolean("allow_hardware_pump", true); - if (L.isEnabled(L.PUMP)) - log.debug("First time HW pump allowed!"); - }) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - pluginSwitcher.cancel(); - if (L.isEnabled(L.PUMP)) - log.debug("User does not allow switching to HW pump!"); - }); - builder.create().show(); - } - } - - @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); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (isEnabled(PluginType.PUMP)) { + boolean previousValue = useExtendedBoluses; + useExtendedBoluses = SP.getBoolean(R.string.key_danar_useextended, false); + + if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { + sExecutionService.extendedBolusStop(); + } + } + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + MainApp.instance().getApplicationContext().unbindService(mConnection); + }, FabricPrivacy::logException) + ); super.onStart(); } @@ -91,7 +87,8 @@ protected void onStop() { Context context = MainApp.instance().getApplicationContext(); context.unbindService(mConnection); - MainApp.bus().unregister(this); + disposable.clear(); + super.onStop(); } private ServiceConnection mConnection = new ServiceConnection() { @@ -110,24 +107,6 @@ public void onServiceConnected(ComponentName name, IBinder service) { } }; - @SuppressWarnings("UnusedParameters") - @Subscribe - public void onStatusEvent(final EventAppExit e) { - MainApp.instance().getApplicationContext().unbindService(mConnection); - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange s) { - if (isEnabled(PluginType.PUMP)) { - boolean previousValue = useExtendedBoluses; - useExtendedBoluses = SP.getBoolean(R.string.key_danar_useextended, false); - - if (useExtendedBoluses != previousValue && TreatmentsPlugin.getPlugin().isInHistoryExtendedBoluslInProgress()) { - sExecutionService.extendedBolusStop(); - } - } - } - // Plugin base interface @Override public String getName() { @@ -266,7 +245,7 @@ public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer duratio // Correct basal already set ? if (L.isEnabled(L.PUMP)) log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); - if (activeTemp.percentRate == percentRate) { + if (activeTemp.percentRate == percentRate && activeTemp.getPlannedRemainingMinutes() > 4) { if (enforceNew) { cancelTempBasal(true); } else { @@ -364,6 +343,11 @@ public PumpEnactResult cancelTempBasal(boolean force) { return result; } + @Override + public PumpType model() { + return PumpType.DanaRKorean; + } + private PumpEnactResult cancelRealTempBasal() { PumpEnactResult result = new PumpEnactResult(); TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/SerialIOThread.java deleted file mode 100644 index 5c47d7c7d1d..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/SerialIOThread.java +++ /dev/null @@ -1,210 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRKorean; - -import android.bluetooth.BluetoothSocket; -import android.os.SystemClock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; -import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; -import info.nightscout.androidaps.plugins.pump.danaR.services.AbstractSerialIOThread; -import info.nightscout.androidaps.plugins.pump.danaRKorean.comm.MessageHashTable_k; -import info.nightscout.androidaps.utils.CRC; - -/** - * Created by mike on 17.07.2016. - */ -public class SerialIOThread extends AbstractSerialIOThread { - private static Logger log = LoggerFactory.getLogger(L.PUMPBTCOMM); - - private InputStream mInputStream = null; - private OutputStream mOutputStream = null; - private BluetoothSocket mRfCommSocket; - - private boolean mKeepRunning = true; - private byte[] mReadBuff = new byte[0]; - - private MessageBase processedMessage; - - public SerialIOThread(BluetoothSocket rfcommSocket) { - super(); - - mRfCommSocket = rfcommSocket; - try { - mOutputStream = mRfCommSocket.getOutputStream(); - mInputStream = mRfCommSocket.getInputStream(); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - this.start(); - } - - @Override - public final void run() { - try { - while (mKeepRunning) { - int availableBytes = mInputStream.available(); - // Ask for 1024 byte (or more if available) - byte[] newData = new byte[Math.max(1024, availableBytes)]; - int gotBytes = mInputStream.read(newData); - // When we are here there is some new data available - appendToBuffer(newData, gotBytes); - - // process all messages we already got - while (mReadBuff.length > 3) { // 3rd byte is packet size. continue only if we an determine packet size - byte[] extractedBuff = cutMessageFromBuffer(); - if (extractedBuff == null) - break; // message is not complete in buffer (wrong packet calls disconnection) - - int command = (extractedBuff[5] & 0xFF) | ((extractedBuff[4] << 8) & 0xFF00); - - MessageBase message; - if (processedMessage != null && processedMessage.getCommand() == command) { - message = processedMessage; - } else { - // get it from hash table - message = MessageHashTable_k.findMessage(command); - } - - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug("<<<<< " + message.getMessageName() + " " + message.toHexString(extractedBuff)); - - // process the message content - message.received = true; - message.handleMessage(extractedBuff); - synchronized (message) { - message.notify(); - } - } - } - } catch (Exception e) { - if (e.getMessage().indexOf("bt socket closed") < 0) - log.error("Thread exception: ", e); - mKeepRunning = false; - } - disconnect("EndOfLoop"); - } - - void appendToBuffer(byte[] newData, int gotBytes) { - // add newData to mReadBuff - byte[] newReadBuff = new byte[mReadBuff.length + gotBytes]; - System.arraycopy(mReadBuff, 0, newReadBuff, 0, mReadBuff.length); - System.arraycopy(newData, 0, newReadBuff, mReadBuff.length, gotBytes); - mReadBuff = newReadBuff; - } - - byte[] cutMessageFromBuffer() { - if (mReadBuff[0] == (byte) 0x7E && mReadBuff[1] == (byte) 0x7E) { - int length = (mReadBuff[2] & 0xFF) + 7; - // Check if we have enough data - if (mReadBuff.length < length) { - return null; - } - if (mReadBuff[length - 2] != (byte) 0x2E || mReadBuff[length - 1] != (byte) 0x2E) { - log.error("wrong packet lenght=" + length + " data " + MessageBase.toHexString(mReadBuff)); - disconnect("wrong packet"); - return null; - } - - short crc = CRC.getCrc16(mReadBuff, 3, length - 7); - byte crcByte0 = (byte) (crc >> 8 & 0xFF); - byte crcByte1 = (byte) (crc & 0xFF); - - byte crcByte0received = mReadBuff[length - 4]; - byte crcByte1received = mReadBuff[length - 3]; - - if (crcByte0 != crcByte0received || crcByte1 != crcByte1received) { - log.error("CRC Error" + String.format("%02x ", crcByte0) + String.format("%02x ", crcByte1) + String.format("%02x ", crcByte0received) + String.format("%02x ", crcByte1received)); - disconnect("crc error"); - return null; - } - // Packet is verified here. extract data - byte[] extractedBuff = new byte[length]; - System.arraycopy(mReadBuff, 0, extractedBuff, 0, length); - // remove extracted data from read buffer - byte[] unprocessedData = new byte[mReadBuff.length - length]; - System.arraycopy(mReadBuff, length, unprocessedData, 0, unprocessedData.length); - mReadBuff = unprocessedData; - return extractedBuff; - } else { - log.error("Wrong beginning of packet len=" + mReadBuff.length + " " + MessageBase.toHexString(mReadBuff)); - disconnect("Wrong beginning of packet"); - return null; - } - } - - @Override - public synchronized void sendMessage(MessageBase message) { - if (!mRfCommSocket.isConnected()) { - log.error("Socket not connected on sendMessage"); - return; - } - processedMessage = message; - - byte[] messageBytes = message.getRawMessageBytes(); - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(">>>>> " + message.getMessageName() + " " + message.toHexString(messageBytes)); - - try { - mOutputStream.write(messageBytes); - } catch (Exception e) { - log.error("sendMessage write exception: ", e); - } - - synchronized (message) { - try { - message.wait(5000); - } catch (InterruptedException e) { - log.error("sendMessage InterruptedException", e); - } - } - - SystemClock.sleep(200); - if (!message.received) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.warn("Reply not received " + message.getMessageName()); - if (message.getCommand() == 0xF0F1) { - DanaRPump.getInstance().isNewPump = false; - log.error("Old firmware detected"); - } - } - } - - @Override - public void disconnect(String reason) { - mKeepRunning = false; - try { - mInputStream.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - mOutputStream.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - mRfCommSocket.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - System.runFinalization(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug("Disconnected: " + reason); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTableRkorean.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTableRkorean.kt new file mode 100644 index 00000000000..ffdf9d14c53 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTableRkorean.kt @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.danaRKorean.comm + +import info.nightscout.androidaps.plugins.pump.danaR.comm.* +import java.util.* + +object MessageHashTableRkorean : MessageHashTableBase { + var messages: HashMap = HashMap() + + init { + put(MsgBolusStop()) // 0x0101 CMD_MEALINS_STOP + put(MsgBolusStart()) // 0x0102 CMD_MEALINS_START_DATA + put(MsgBolusProgress()) // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS + put(MsgStatusProfile()) // 0x0204 CMD_PUMP_CALCULATION_SETTING + put(MsgStatusTempBasal()) // 0x0205 CMD_PUMP_EXERCISE_MODE + put(MsgStatusBolusExtended()) // 0x0207 CMD_PUMP_EXPANS_INS_I + put(MsgStatusBasic_k()) // 0x020A CMD_PUMP_INITVIEW_I + put(MsgStatus_k()) // 0x020B CMD_PUMP_STATUS + put(MsgInitConnStatusTime_k()) // 0x0301 CMD_PUMPINIT_TIME_INFO + put(MsgInitConnStatusBolus_k()) // 0x0302 CMD_PUMPINIT_BOLUS_INFO + put(MsgInitConnStatusBasic_k()) // 0x0303 CMD_PUMPINIT_INIT_INFO + put(MsgSetTempBasalStart()) // 0x0401 CMD_PUMPSET_EXERCISE_S + put(MsgSetCarbsEntry()) // 0x0402 CMD_PUMPSET_HIS_S + put(MsgSetTempBasalStop()) // 0x0403 CMD_PUMPSET_EXERCISE_STOP + put(MsgSetExtendedBolusStop()) // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP + put(MsgSetExtendedBolusStart()) // 0x0407 CMD_PUMPSET_EXPANS_INS_S + put(MsgError()) // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS + put(MsgPCCommStart()) // 0x3001 CMD_CONNECT + put(MsgPCCommStop()) // 0x3002 CMD_DISCONNECT + put(MsgHistoryBolus()) // 0x3101 CMD_HISTORY_MEAL_INS + put(MsgHistoryDailyInsulin()) // 0x3102 CMD_HISTORY_DAY_INS + put(MsgHistoryGlucose()) // 0x3104 CMD_HISTORY_GLUCOSE + put(MsgHistoryAlarm()) // 0x3105 CMD_HISTORY_ALARM + put(MsgHistoryCarbo()) // 0x3107 CMD_HISTORY_CARBOHY + put(MsgSettingBasal_k()) // 0x3202 CMD_SETTING_V_BASAL_INS_I + put(MsgSettingMeal()) // 0x3203 CMD_SETTING_V_MEAL_SETTING_I + put(MsgSettingProfileRatios()) // 0x3204 CMD_SETTING_V_CCC_I + put(MsgSettingMaxValues()) // 0x3205 CMD_SETTING_V_MAX_VALUE_I + put(MsgSettingBasalProfileAll_k()) // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL + put(MsgSettingShippingInfo()) // 0x3207 CMD_SETTING_V_SHIPPING_I + put(MsgSettingGlucose()) // 0x3209 CMD_SETTING_V_GLUCOSEandEASY + put(MsgSettingPumpTime()) // 0x320A CMD_SETTING_V_TIME_I + put(MsgSetSingleBasalProfile()) // 0x3302 CMD_SETTING_BASAL_INS_S + put(MsgHistoryAll()) // 0x41F2 CMD_HISTORY_ALL + put(MsgHistoryNewDone()) // 0x42F1 CMD_HISTORY_NEW_DONE + put(MsgHistoryNew()) // 0x42F2 CMD_HISTORY_NEW + put(MsgCheckValue_k()) // 0xF0F1 CMD_PUMP_CHECK_VALUE + } + + override fun put(message: MessageBase) { + messages[message.command] = message + } + + override fun findMessage(command: Int): MessageBase { + return messages[command] ?: MessageBase() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTable_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTable_k.java deleted file mode 100644 index 30ac7107247..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MessageHashTable_k.java +++ /dev/null @@ -1,76 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRKorean.comm; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; - -import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; -import info.nightscout.androidaps.plugins.pump.danaR.comm.*; - -/** - * Created by mike on 28.05.2016. - */ -public class MessageHashTable_k { - private static Logger log = LoggerFactory.getLogger(MessageHashTable_k.class); - - public static HashMap messages = null; - - static { - if (messages == null) { - messages = new HashMap(); - put(new MsgBolusStop()); // 0x0101 CMD_MEALINS_STOP - put(new MsgBolusStart()); // 0x0102 CMD_MEALINS_START_DATA - put(new MsgBolusProgress()); // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS - put(new MsgStatusProfile()); // 0x0204 CMD_PUMP_CALCULATION_SETTING - put(new MsgStatusTempBasal()); // 0x0205 CMD_PUMP_EXERCISE_MODE - put(new MsgStatusBolusExtended()); // 0x0207 CMD_PUMP_EXPANS_INS_I - put(new MsgStatusBasic_k()); // 0x020A CMD_PUMP_INITVIEW_I - put(new MsgStatus_k()); // 0x020B CMD_PUMP_STATUS - put(new MsgInitConnStatusTime_k()); // 0x0301 CMD_PUMPINIT_TIME_INFO - put(new MsgInitConnStatusBolus_k()); // 0x0302 CMD_PUMPINIT_BOLUS_INFO - put(new MsgInitConnStatusBasic_k()); // 0x0303 CMD_PUMPINIT_INIT_INFO - put(new MsgSetTempBasalStart()); // 0x0401 CMD_PUMPSET_EXERCISE_S - put(new MsgSetCarbsEntry()); // 0x0402 CMD_PUMPSET_HIS_S - put(new MsgSetTempBasalStop()); // 0x0403 CMD_PUMPSET_EXERCISE_STOP - put(new MsgSetExtendedBolusStop()); // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP - put(new MsgSetExtendedBolusStart()); // 0x0407 CMD_PUMPSET_EXPANS_INS_S - put(new MsgError()); // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS - put(new MsgPCCommStart()); // 0x3001 CMD_CONNECT - put(new MsgPCCommStop()); // 0x3002 CMD_DISCONNECT - put(new MsgHistoryBolus()); // 0x3101 CMD_HISTORY_MEAL_INS - put(new MsgHistoryDailyInsulin()); // 0x3102 CMD_HISTORY_DAY_INS - put(new MsgHistoryGlucose()); // 0x3104 CMD_HISTORY_GLUCOSE - put(new MsgHistoryAlarm()); // 0x3105 CMD_HISTORY_ALARM - put(new MsgHistoryCarbo()); // 0x3107 CMD_HISTORY_CARBOHY - put(new MsgSettingBasal_k()); // 0x3202 CMD_SETTING_V_BASAL_INS_I - put(new MsgSettingMeal()); // 0x3203 CMD_SETTING_V_MEAL_SETTING_I - put(new MsgSettingProfileRatios()); // 0x3204 CMD_SETTING_V_CCC_I - put(new MsgSettingMaxValues()); // 0x3205 CMD_SETTING_V_MAX_VALUE_I - put(new MsgSettingBasalProfileAll_k()); // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL - put(new MsgSettingShippingInfo()); // 0x3207 CMD_SETTING_V_SHIPPING_I - put(new MsgSettingGlucose()); // 0x3209 CMD_SETTING_V_GLUCOSEandEASY - put(new MsgSettingPumpTime()); // 0x320A CMD_SETTING_V_TIME_I - put(new MsgSetSingleBasalProfile()); // 0x3302 CMD_SETTING_BASAL_INS_S - put(new MsgHistoryAll()); // 0x41F2 CMD_HISTORY_ALL - put(new MsgHistoryNewDone()); // 0x42F1 CMD_HISTORY_NEW_DONE - put(new MsgHistoryNew()); // 0x42F2 CMD_HISTORY_NEW - put(new MsgCheckValue_k()); // 0xF0F1 CMD_PUMP_CHECK_VALUE - } - } - - public static void put(MessageBase message) { - int command = message.getCommand(); - //String name = MessageOriginalNames.getName(command); - messages.put(command, message); - //log.debug(String.format("%04x ", command) + " " + name); - } - - public static MessageBase findMessage(Integer command) { - if (messages.containsKey(command)) { - return messages.get(command); - } else { - return new MessageBase(); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBasic_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBasic_k.java index 5c9ac9707b3..8f62ccd58f3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBasic_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBasic_k.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -42,16 +43,16 @@ public void handleMessage(byte[] bytes) { if (pump.isEasyModeEnabled) { Notification notification = new Notification(Notification.EASYMODE_ENABLED, MainApp.gs(R.string.danar_disableeasymode), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.EASYMODE_ENABLED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.EASYMODE_ENABLED)); } if (!DanaRPump.getInstance().isPasswordOK()) { Notification notification = new Notification(Notification.WRONG_PUMP_PASSWORD, MainApp.gs(R.string.wrongpumppassword), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.WRONG_PUMP_PASSWORD)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.WRONG_PUMP_PASSWORD)); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBolus_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBolus_k.java index b77f8ee61c2..0df308369f1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBolus_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusBolus_k.java @@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; @@ -48,9 +49,9 @@ public void handleMessage(byte[] bytes) { if (!pump.isExtendedBolusEnabled) { Notification notification = new Notification(Notification.EXTENDED_BOLUS_DISABLED, MainApp.gs(R.string.danar_enableextendedbolus), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); } // This is last message of initial sequence diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusTime_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusTime_k.java index 103c3a22342..8a642a89f81 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusTime_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/comm/MsgInitConnStatusTime_k.java @@ -5,9 +5,10 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventRefreshGui; +import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -31,24 +32,24 @@ public void handleMessage(byte[] bytes) { if (bytes.length - 10 < 10) { Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.gs(R.string.pumpdrivercorrected), Notification.NORMAL); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.error("Wrong model selected. Switching to export DanaR"); - 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); + DanaRKoreanPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, false); + DanaRKoreanPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, false); + DanaRPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, true); + DanaRPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, true); DanaRPump.reset(); // mark not initialized //If profile coming from pump, switch it as well - if (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginType.PROFILE)) { - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); + if (DanaRKoreanPlugin.getPlugin().isEnabled(PluginType.PROFILE)) { + (DanaRKoreanPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, false); + (DanaRPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, true); } ConfigBuilderPlugin.getPlugin().storeSettings("ChangingKoreanDanaDriver"); - MainApp.bus().post(new EventRefreshGui()); + RxBus.INSTANCE.send(new EventRebuildTabs()); ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java index 749b977c2c4..0032a7e5e08 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRKorean/services/DanaRKoreanExecutionService.java @@ -5,8 +5,6 @@ import android.os.Binder; import android.os.SystemClock; -import com.squareup.otto.Subscribe; - import java.io.IOException; import java.util.Date; @@ -22,13 +20,15 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; +import info.nightscout.androidaps.plugins.pump.danaR.SerialIOThread; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusProgress; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStop; @@ -50,56 +50,63 @@ import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgStatusTempBasal; import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRNewStatus; import info.nightscout.androidaps.plugins.pump.danaR.services.AbstractDanaRExecutionService; -import info.nightscout.androidaps.plugins.pump.danaRKorean.SerialIOThread; +import info.nightscout.androidaps.plugins.pump.danaRKorean.comm.MessageHashTableRkorean; import info.nightscout.androidaps.plugins.pump.danaRKorean.comm.MsgCheckValue_k; import info.nightscout.androidaps.plugins.pump.danaRKorean.comm.MsgSettingBasal_k; import info.nightscout.androidaps.plugins.pump.danaRKorean.comm.MsgStatusBasic_k; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { + private CompositeDisposable disposable = new CompositeDisposable(); public DanaRKoreanExecutionService() { mBinder = new LocalBinder(); - registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); } - public class LocalBinder extends Binder { - public DanaRKoreanExecutionService getServiceInstance() { - return DanaRKoreanExecutionService.this; - } - } - - private void registerBus() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); + @Override + public void onCreate() { + super.onCreate(); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (mSerialIOThread != null) + mSerialIOThread.disconnect("EventPreferenceChange"); + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.PUMP)) + log.debug("EventAppExit received"); + + if (mSerialIOThread != null) + mSerialIOThread.disconnect("Application exit"); + MainApp.instance().getApplicationContext().unregisterReceiver(receiver); + stopSelf(); + }, FabricPrivacy::logException) + ); } - @Subscribe - public void onStatusEvent(EventAppExit event) { - if (L.isEnabled(L.PUMP)) - log.debug("EventAppExit received"); - - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - - MainApp.instance().getApplicationContext().unregisterReceiver(receiver); - - stopSelf(); + @Override + public void onDestroy() { + disposable.clear(); + super.onDestroy(); } - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); + public class LocalBinder extends Binder { + public DanaRKoreanExecutionService getServiceInstance() { + return DanaRKoreanExecutionService.this; + } } public void connect() { @@ -128,9 +135,9 @@ public void connect() { if (mSerialIOThread != null) { mSerialIOThread.disconnect("Recreate SerialIOThread"); } - mSerialIOThread = new SerialIOThread(mRfcommSocket); + mSerialIOThread = new SerialIOThread(mRfcommSocket, MessageHashTableRkorean.INSTANCE); mHandshakeInProgress = true; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.HANDSHAKING, 0)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.HANDSHAKING, 0)); } mConnectionInProgress = false; @@ -140,7 +147,7 @@ public void connect() { public void getPumpStatus() { DanaRPump danaRPump = DanaRPump.getInstance(); try { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); //MsgStatus_k statusMsg = new MsgStatus_k(); MsgStatusBasic_k statusBasicMsg = new MsgStatusBasic_k(); MsgStatusTempBasal tempStatusMsg = new MsgStatusTempBasal(); @@ -156,11 +163,11 @@ public void getPumpStatus() { //mSerialIOThread.sendMessage(statusMsg); mSerialIOThread.sendMessage(statusBasicMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); mSerialIOThread.sendMessage(tempStatusMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); mSerialIOThread.sendMessage(exStatusMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); long now = System.currentTimeMillis(); danaRPump.lastConnection = now; @@ -168,15 +175,15 @@ public void getPumpStatus() { Profile profile = ProfileFunctions.getInstance().getProfile(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile != null && Math.abs(danaRPump.currentBasal - profile.getBasal()) >= pump.getPumpDescription().basalStep) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingBasal()); if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { - MainApp.bus().post(new EventProfileNeedsUpdate()); + RxBus.INSTANCE.send(new EventProfileNeedsUpdate()); } } if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !pump.isInitialized()) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingMeal()); mSerialIOThread.sendMessage(new MsgSettingBasal_k()); @@ -184,8 +191,17 @@ public void getPumpStatus() { mSerialIOThread.sendMessage(new MsgSettingMaxValues()); mSerialIOThread.sendMessage(new MsgSettingGlucose()); mSerialIOThread.sendMessage(new MsgSettingProfileRatios()); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); + if (danaRPump.pumpTime == 0) { + // initial handshake was not successfull + // deinitialize pump + danaRPump.lastConnection = 0; + danaRPump.lastSettingsRead = 0; + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); + return; + } long timeDiff = (danaRPump.pumpTime - System.currentTimeMillis()) / 1000L; if (L.isEnabled(L.PUMP)) log.debug("Pump time difference: " + timeDiff + " seconds"); @@ -201,15 +217,15 @@ public void getPumpStatus() { danaRPump.lastSettingsRead = now; } - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { if (L.isEnabled(L.PUMP)) log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); if (System.currentTimeMillis() > lastApproachingDailyLimit + 30 * 60 * 1000) { Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.gs(R.string.approachingdailylimit), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); NSUpload.uploadError(MainApp.gs(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); lastApproachingDailyLimit = System.currentTimeMillis(); } @@ -223,41 +239,41 @@ public boolean tempBasal(int percent, int durationInHours) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; if (danaRPump.isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); SystemClock.sleep(500); } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean tempBasalStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); mSerialIOThread.sendMessage(new MsgStatusTempBasal()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolus(double insulin, int durationInHalfHours) { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStart(insulin, (byte) (durationInHalfHours & 0xFF))); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolusStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStop()); mSerialIOThread.sendMessage(new MsgStatusBolusExtended()); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -325,13 +341,13 @@ public boolean tempBasalShortDuration(int percent, int durationInMinutes) { public boolean updateBasalsInPump(final Profile profile) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); double[] basal = DanaRPump.getInstance().buildDanaRProfileRecord(profile); MsgSetSingleBasalProfile msgSet = new MsgSetSingleBasalProfile(basal); mSerialIOThread.sendMessage(msgSet); danaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java index ddbb8547ae0..12b228893cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/DanaRSPlugin.java @@ -5,12 +5,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; +import android.preference.Preference; -import com.squareup.otto.Subscribe; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -34,12 +34,11 @@ 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.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; -import info.nightscout.androidaps.plugins.configBuilder.DetailedBolusInfoStorage; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; @@ -47,6 +46,7 @@ import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.pump.danaR.DanaRFragment; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; @@ -58,16 +58,22 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import info.nightscout.androidaps.utils.TimeChangeType; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 03.09.2017. */ -public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { +public class DanaRSPlugin extends PluginBase implements PumpInterface, DanaRInterface, ConstraintsInterface { private Logger log = LoggerFactory.getLogger(L.PUMP); + private CompositeDisposable disposable = new CompositeDisposable(); + private static DanaRSPlugin plugin = null; public static DanaRSPlugin getPlugin() { @@ -106,14 +112,35 @@ public void onStateChange(PluginType type, State oldState, State newState) { } } + @Override + public void updatePreferenceSummary(@NotNull Preference pref) { + super.updatePreferenceSummary(pref); + + if (pref.getKey().equals(MainApp.gs(R.string.key_danars_name))) + pref.setSummary(SP.getString(R.string.key_danars_name, "")); + } + @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 + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + MainApp.instance().getApplicationContext().unbindService(mConnection); + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventDanaRSDeviceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + loadAddress(); + }, FabricPrivacy::logException) + ); + loadAddress(); // load device name super.onStart(); } @@ -122,30 +149,13 @@ protected void onStop() { Context context = MainApp.instance().getApplicationContext(); context.unbindService(mConnection); - MainApp.bus().unregister(this); + disposable.clear(); + super.onStop(); } @Override - public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity context) { - boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); - if (allowHardwarePump || context == null) { - pluginSwitcher.invoke(); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(R.string.allow_hardware_pump_text) - .setPositiveButton(R.string.yes, (dialog, id) -> { - pluginSwitcher.invoke(); - SP.putBoolean("allow_hardware_pump", true); - if (L.isEnabled(L.PUMP)) - log.debug("First time HW pump allowed!"); - }) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - pluginSwitcher.cancel(); - if (L.isEnabled(L.PUMP)) - log.debug("User does not allow switching to HW pump!"); - }); - builder.create().show(); - } + public void switchAllowed(boolean newState, FragmentActivity activity, PluginType type) { + confirmPumpPluginActivation(newState, activity, type); } private ServiceConnection mConnection = new ServiceConnection() { @@ -164,14 +174,7 @@ public void onServiceConnected(ComponentName name, IBinder service) { } }; - @SuppressWarnings("UnusedParameters") - @Subscribe - public void onStatusEvent(final EventAppExit e) { - MainApp.instance().getApplicationContext().unbindService(mConnection); - } - - @Subscribe - public void onStatusEvent(final EventDanaRSDeviceChange e) { + private void loadAddress() { mDeviceAddress = SP.getString(R.string.key_danars_address, ""); mDeviceName = SP.getString(R.string.key_danars_name, ""); } @@ -272,26 +275,6 @@ public Constraint applyExtendedBolusConstraints(Constraint insul return applyBolusConstraints(insulin); } - // Profile interface - - @Nullable - @Override - public ProfileStore getProfile() { - if (DanaRPump.getInstance().lastSettingsRead == 0) - return null; // no info now - return DanaRPump.getInstance().createConvertedProfile(); - } - - @Override - public String getUnits() { - return DanaRPump.getInstance().getUnits(); - } - - @Override - public String getProfileName() { - return DanaRPump.getInstance().createConvertedProfileName(); - } - // Pump interface @Override @@ -322,22 +305,22 @@ public PumpEnactResult setNewBasalProfile(Profile profile) { if (!isInitialized()) { 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)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = MainApp.gs(R.string.pumpNotInitializedProfileNotSet); return result; } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); } if (!danaRSService.updateBasalsInPump(profile)) { Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = MainApp.gs(R.string.failedupdatebasalprofile); return result; } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.success = true; result.enacted = true; result.comment = "OK"; @@ -356,8 +339,7 @@ public boolean isThisProfileSet(Profile profile) { 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.getBasalTimeFromMidnight((Integer) (h * basalIncrement)); - if (profileValue == null) return true; + Double profileValue = profile.getBasalTimeFromMidnight(h * basalIncrement); if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { if (L.isEnabled(L.PUMP)) log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); @@ -378,10 +360,14 @@ public double getBaseBasalRate() { } @Override - public double getReservoirLevel() { return DanaRPump.getInstance().reservoirRemainingUnits; } + public double getReservoirLevel() { + return DanaRPump.getInstance().reservoirRemainingUnits; + } @Override - public int getBatteryLevel() { return DanaRPump.getInstance().batteryRemaining; } + public int getBatteryLevel() { + return DanaRPump.getInstance().batteryRemaining; + } @Override public synchronized PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { @@ -411,7 +397,7 @@ public synchronized PumpEnactResult deliverTreatment(DetailedBolusInfo detailedB if (carbTime == 0) carbTime--; // better set 1 min back to prevents clash with insulin detailedBolusInfo.carbTime = 0; - DetailedBolusInfoStorage.add(detailedBolusInfo); // will be picked up on reading history + DetailedBolusInfoStorage.INSTANCE.add(detailedBolusInfo); // will be picked up on reading history Treatment t = new Treatment(); t.isSMB = detailedBolusInfo.isSMB; @@ -512,7 +498,7 @@ public synchronized PumpEnactResult setTempBasalAbsolute(Double absoluteRate, In if (L.isEnabled(L.PUMP)) log.debug("setTempBasalAbsolute: currently running: " + activeTemp.toString()); // Correct basal already set ? - if (activeTemp.percentRate == percentRate) { + if (activeTemp.percentRate == percentRate && activeTemp.getPlannedRemainingMinutes() > 4) { if (!enforceNew) { result.success = true; result.percent = percentRate; @@ -530,7 +516,7 @@ public synchronized PumpEnactResult setTempBasalAbsolute(Double absoluteRate, In if (L.isEnabled(L.PUMP)) log.debug("setTempBasalAbsolute: Setting temp basal " + percentRate + "% for " + durationInMinutes + " mins (doLowTemp || doHighTemp)"); if (percentRate == 0 && durationInMinutes > 30) { - result = setTempBasalPercent(percentRate, durationInMinutes, profile, false); + result = setTempBasalPercent(percentRate, durationInMinutes, profile, enforceNew); } else { // use special APS temp basal call ... 100+/15min .... 100-/30min result = setHighTempBasalPercent(percentRate); @@ -566,8 +552,8 @@ public synchronized PumpEnactResult setTempBasalPercent(Integer percent, Integer if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; long now = System.currentTimeMillis(); - TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); - if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(now); + if (activeTemp != null && activeTemp.percentRate == percent && activeTemp.getPlannedRemainingMinutes() > 4 && !enforceNew) { result.enacted = false; result.success = true; result.isTempCancel = false; @@ -769,7 +755,17 @@ public JSONObject getJSONStatus(Profile profile, String profileName) { } @Override - public String deviceID() { + public ManufacturerType manufacturer() { + return ManufacturerType.Sooil; + } + + @Override + public PumpType model() { + return PumpType.DanaRS; + } + + @Override + public String serialNumber() { return DanaRPump.getInstance().serialNumber; } @@ -801,7 +797,6 @@ public String shortStatus(boolean veryShort) { if (!veryShort) { ret += "TDD: " + DecimalFormatter.to0Decimal(pump.dailyTotalUnits) + " / " + pump.maxDailyTotalUnits + " U\n"; } - ret += "IOB: " + pump.iob + "U\n"; ret += "Reserv: " + DecimalFormatter.to0Decimal(pump.reservoirRemainingUnits) + "U\n"; ret += "Batt: " + pump.batteryRemaining + "\n"; return ret; @@ -832,4 +827,9 @@ public boolean canHandleDST() { return false; } + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java index 502c18cbc90..87c904212c0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/BLEScanActivity.java @@ -5,9 +5,9 @@ import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanResult; +import android.content.pm.ActivityInfo; import android.os.Bundle; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; @@ -18,29 +18,27 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSDeviceChange; import info.nightscout.androidaps.utils.SP; -public class BLEScanActivity extends AppCompatActivity { - private ListView listView = null; +public class BLEScanActivity extends NoSplashAppCompatActivity { private ListAdapter mListAdapter = null; private ArrayList mDevices = new ArrayList<>(); - ; - private BluetoothAdapter mBluetoothAdapter = null; private BluetoothLeScanner mBluetoothLeScanner = null; - @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.danars_blescanner_activity); + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mListAdapter = new ListAdapter(); - listView = (ListView) findViewById(R.id.danars_blescanner_listview); + ListView listView = findViewById(R.id.danars_blescanner_listview); listView.setEmptyView(findViewById(R.id.danars_blescanner_nodevice)); listView.setAdapter(mListAdapter); @@ -51,8 +49,9 @@ protected void onCreate(Bundle savedInstanceState) { protected void onResume() { super.onResume(); - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter != null) { + if (!mBluetoothAdapter.isEnabled()) mBluetoothAdapter.enable(); mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); if (mBluetoothLeScanner == null) { @@ -90,11 +89,7 @@ private void addBleDevice(BluetoothDevice device) { } mDevices.add(item); - new Handler().post(new Runnable() { - public void run() { - mListAdapter.notifyDataSetChanged(); - } - }); + new Handler().post(() -> mListAdapter.notifyDataSetChanged()); } private ScanCallback mBleScanCallback = new ScanCallback() { @@ -135,19 +130,19 @@ public View getView(int i, View convertView, ViewGroup parent) { } BluetoothDeviceItem item = getItem(i); - holder.setData(i, item); + holder.setData(item); return v; } private class ViewHolder implements View.OnClickListener { private BluetoothDeviceItem item = null; - private TextView mName = null; - private TextView mAddress = null; + private TextView mName; + private TextView mAddress; - public ViewHolder(View v) { - mName = (TextView) v.findViewById(R.id.ble_name); - mAddress = (TextView) v.findViewById(R.id.ble_address); + ViewHolder(View v) { + mName = v.findViewById(R.id.ble_name); + mAddress = v.findViewById(R.id.ble_address); v.setOnClickListener(ViewHolder.this); } @@ -157,11 +152,11 @@ public void onClick(View v) { SP.putString(R.string.key_danars_address, item.device.getAddress()); SP.putString(R.string.key_danars_name, mName.getText().toString()); item.device.createBond(); - MainApp.bus().post(new EventDanaRSDeviceChange()); + RxBus.INSTANCE.send(new EventDanaRSDeviceChange()); finish(); } - public void setData(int pos, BluetoothDeviceItem data) { + public void setData(BluetoothDeviceItem data) { if (data != null) { try { String tTitle = data.device.getName(); @@ -175,7 +170,7 @@ public void setData(int pos, BluetoothDeviceItem data) { mAddress.setText(data.device.getAddress()); item = data; - } catch (Exception e) { + } catch (Exception ignored) { } } } @@ -186,14 +181,14 @@ public void setData(int pos, BluetoothDeviceItem data) { private class BluetoothDeviceItem { private BluetoothDevice device; - public BluetoothDeviceItem(BluetoothDevice device) { + BluetoothDeviceItem(BluetoothDevice device) { super(); this.device = device; } @Override public boolean equals(Object o) { - if (device == null || o == null || !(o instanceof BluetoothDeviceItem)) { + if (device == null || !(o instanceof BluetoothDeviceItem)) { return false; } BluetoothDeviceItem checkItem = (BluetoothDeviceItem) o; @@ -203,7 +198,7 @@ public boolean equals(Object o) { return stringEquals(device.getAddress(), checkItem.device.getAddress()); } - public boolean stringEquals(String arg1, String arg2) { + boolean stringEquals(String arg1, String arg2) { try { return arg1.equals(arg2); } catch (Exception e) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java deleted file mode 100644 index a98d5419630..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRS.activities; - -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; - -public class PairingHelperActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - PairingProgressDialog bolusProgressDialog = new PairingProgressDialog(); - bolusProgressDialog.setHelperActivity(this); - bolusProgressDialog.show(this.getSupportFragmentManager(), "PairingProgress"); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.kt new file mode 100644 index 00000000000..ba73e1b368f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingHelperActivity.kt @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.danaRS.activities + +import android.content.pm.ActivityInfo +import android.os.Bundle +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.plugins.pump.danaRS.dialogs.PairingProgressDialog + +class PairingHelperActivity : NoSplashAppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + PairingProgressDialog() + .setHelperActivity(this) + .show(supportFragmentManager, "PairingProgress") + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java deleted file mode 100644 index 1d9943a5f59..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/activities/PairingProgressDialog.java +++ /dev/null @@ -1,138 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRS.activities; - - -import android.app.Activity; -import android.os.Bundle; -import android.os.Handler; -import android.os.HandlerThread; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ProgressBar; -import android.widget.TextView; - -import com.squareup.otto.Subscribe; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSPairingSuccess; - - -public class PairingProgressDialog extends DialogFragment implements View.OnClickListener { - - TextView statusView; - ProgressBar progressBar; - Button button; - PairingHelperActivity helperActivity; - - static int secondsPassed = 0; - public static boolean pairingEnded = false; - public static boolean running = true; - - private static Handler sHandler; - private static HandlerThread sHandlerThread; - - public PairingProgressDialog() { - super(); - // Required empty public constructor - if (sHandlerThread == null) { - sHandlerThread = new HandlerThread(PairingProgressDialog.class.getSimpleName()); - sHandlerThread.start(); - sHandler = new Handler(sHandlerThread.getLooper()); - } - secondsPassed = 0; - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.danars_pairingprogressdialog, container, false); - getDialog().setTitle(MainApp.gs(R.string.pairing)); - statusView = (TextView) view.findViewById(R.id.danars_pairingprogress_status); - progressBar = (ProgressBar) view.findViewById(R.id.danars_pairingprogress_progressbar); - button = (Button) view.findViewById(R.id.ok); - - progressBar.setMax(100); - progressBar.setProgress(0); - statusView.setText(MainApp.gs(R.string.waitingforpairing)); - button.setVisibility(View.GONE); - button.setOnClickListener(this); - setCancelable(false); - - sHandler.post(() -> { - for (int i = 0; i < 20; i++) { - if (pairingEnded) { - Activity activity = getActivity(); - if (activity != null) { - activity.runOnUiThread(() -> { - progressBar.setProgress(100); - statusView.setText(R.string.pairingok); - try { - Thread.sleep(1000); - } catch (InterruptedException ignored) { - } - dismiss(); - }); - } else - dismiss(); - return; - } - progressBar.setProgress(i * 5); - try { - Thread.sleep(1000); - } catch (InterruptedException ignored) { - } - } - Activity activity = getActivity(); - if (activity != null) { - activity.runOnUiThread(() -> { - progressBar.setProgress(100); - statusView.setText(R.string.pairingtimedout); - button.setVisibility(View.VISIBLE); - }); - } - }); - return view; - } - - @Override - public void onResume() { - super.onResume(); - MainApp.bus().register(this); - running = true; - if (pairingEnded) dismiss(); - } - - @Override - public void dismiss() { - super.dismissAllowingStateLoss(); - if (helperActivity != null) { - helperActivity.finish(); - } - } - - @Override - public void onPause() { - super.onPause(); - MainApp.bus().unregister(this); - running = false; - } - - @Subscribe - public void onStatusEvent(final EventDanaRSPairingSuccess ev) { - pairingEnded = true; - } - - public void setHelperActivity(PairingHelperActivity activity) { - this.helperActivity = activity; - } - - @Override - public void onClick(View v) { - running = false; - dismiss(); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java index 28a081aaa83..55a2e0c51e1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet.java @@ -4,11 +4,15 @@ import android.os.Build; import com.cozmo.danar.util.BleCommandUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.util.Date; public class DanaRS_Packet { + private static final Logger log = LoggerFactory.getLogger(DanaRS_Packet.class); + protected static final int TYPE_START = 0; protected static final int OPCODE_START = 1; protected static final int DATA_START = 2; @@ -47,8 +51,6 @@ public byte[] getRequestParams() { return null; } - ; - // STATIC FUNCTIONS public static int getCommand(byte[] data) { @@ -60,6 +62,9 @@ public static int getCommand(byte[] data) { public void handleMessage(byte[] data) { } + public void handleMessageNotReceived() { + } + public String getFriendlyName() { return "UNKNOWN_PACKET"; } @@ -72,7 +77,7 @@ protected static byte[] getBytes(byte[] data, int srcStart, int srcLength) { return ret; } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } return null; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_APS_History_Events.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_APS_History_Events.java index f6364290c21..fde34c58e05 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_APS_History_Events.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_APS_History_Events.java @@ -17,7 +17,8 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.DetailedBolusInfoStorage; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; @@ -129,7 +130,7 @@ public void handleMessage(byte[] data) { status = "EXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.BOLUS: - DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(datetime); + DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.INSTANCE.findDetailedBolusInfo(datetime, param1 / 100d); if (detailedBolusInfo == null) { detailedBolusInfo = new DetailedBolusInfo(); } @@ -144,7 +145,7 @@ public void handleMessage(byte[] data) { status = "BOLUS " + DateUtil.timeString(datetime); break; case DanaRPump.DUALBOLUS: - detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(datetime); + detailedBolusInfo = DetailedBolusInfoStorage.INSTANCE.findDetailedBolusInfo(datetime, param1 / 100d); if (detailedBolusInfo == null) { detailedBolusInfo = new DetailedBolusInfo(); } @@ -223,7 +224,7 @@ public void handleMessage(byte[] data) { if (datetime > lastEventTimeLoaded) lastEventTimeLoaded = datetime; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.processinghistory) + ": " + status)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.processinghistory) + ": " + status)); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Basal_Rate.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Basal_Rate.java index eb89cd5d103..830aca2d028 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Basal_Rate.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Basal_Rate.java @@ -11,6 +11,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -57,9 +58,9 @@ public void handleMessage(byte[] data) { if (pump.basalStep != 0.01d) { failed = true; Notification notification = new Notification(Notification.WRONGBASALSTEP, MainApp.gs(R.string.danar_setbasalstep001), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else { - MainApp.bus().post(new EventDismissNotification(Notification.WRONGBASALSTEP)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.WRONGBASALSTEP)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java index 5724c5ff80f..117baf23ffc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Basal_Get_Temporary_Basal_State.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRS.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import com.cozmo.danar.util.BleCommandUtil; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Get_Bolus_Option.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Get_Bolus_Option.java index 73c7a8d8f08..c656de11cf6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Get_Bolus_Option.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Get_Bolus_Option.java @@ -8,6 +8,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -105,10 +106,10 @@ public void handleMessage(byte[] data) { if (!pump.isExtendedBolusEnabled) { Notification notification = new Notification(Notification.EXTENDED_BOLUS_DISABLED, MainApp.gs(R.string.danar_enableextendedbolus), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); failed = true; } else { - MainApp.bus().post(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.EXTENDED_BOLUS_DISABLED)); } if (L.isEnabled(L.PUMPCOMM)) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java index 5e3fe3fbcd6..7cb0a11f9be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Bolus_Set_Step_Bolus_Stop.java @@ -9,6 +9,7 @@ import com.cozmo.danar.util.BleCommandUtil; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; @@ -47,16 +48,16 @@ public void handleMessage(byte[] data) { } } - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; stopped = true; if (!forced) { t.insulin = amount; - bolusingEvent.status = MainApp.gs(R.string.overview_bolusprogress_delivered); - bolusingEvent.percent = 100; + bolusingEvent.setStatus(MainApp.gs(R.string.overview_bolusprogress_delivered)); + bolusingEvent.setPercent(100); } else { - bolusingEvent.status = MainApp.gs(R.string.overview_bolusprogress_stoped); + bolusingEvent.setStatus(MainApp.gs(R.string.overview_bolusprogress_stoped)); } - MainApp.bus().post(bolusingEvent); + RxBus.INSTANCE.send(bolusingEvent); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Get_Pump_Check.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Get_Pump_Check.java index 7c0a0ec2ae8..f9bda3dfe50 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Get_Pump_Check.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_General_Get_Pump_Check.java @@ -8,6 +8,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; @@ -24,7 +25,7 @@ public DanaRS_Packet_General_Get_Pump_Check() { @Override public void handleMessage(byte[] data) { - if (data.length <5){ + if (data.length < 5) { failed = true; return; } @@ -49,7 +50,7 @@ public void handleMessage(byte[] data) { } if (pump.productCode < 2) { - MainApp.bus().post(new EventNewNotification(new Notification(Notification.UNSUPPORTED_FIRMWARE, MainApp.gs(R.string.unsupportedfirmware), Notification.URGENT))); + RxBus.INSTANCE.send(new EventNewNotification(new Notification(Notification.UNSUPPORTED_FIRMWARE, MainApp.gs(R.string.unsupportedfirmware), Notification.URGENT))); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_History_.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_History_.java index ceaf49908b6..07893346d75 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_History_.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_History_.java @@ -10,6 +10,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.db.DanaRHistoryRecord; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.pump.danaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRSyncStatus; import info.nightscout.androidaps.utils.DateUtil; @@ -109,7 +110,6 @@ public void handleMessage(byte[] data) { log.debug("History packet: " + recordCode + " Date: " + datetimewihtsec.toLocaleString() + " Code: " + historyCode + " Value: " + value); - EventDanaRSyncStatus ev = new EventDanaRSyncStatus(); DanaRHistoryRecord danaRHistoryRecord = new DanaRHistoryRecord(); danaRHistoryRecord.setBytes(data); @@ -224,9 +224,7 @@ public void handleMessage(byte[] data) { MainApp.getDbHelper().createOrUpdate(danaRHistoryRecord); - ev.message = DateUtil.dateAndTimeString(danaRHistoryRecord.recordDate); - ev.message += " " + messageType; - MainApp.bus().post(ev); + RxBus.INSTANCE.send(new EventDanaRSyncStatus(DateUtil.dateAndTimeString(danaRHistoryRecord.recordDate) + " " + messageType)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java index 3c916dc399b..00b6483fbb9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Complete.java @@ -8,6 +8,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.treatments.Treatment; @@ -39,12 +40,12 @@ public void handleMessage(byte[] data) { if (t != null) { t.insulin = deliveredInsulin; - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.status = String.format(MainApp.gs(R.string.bolusdelivering), deliveredInsulin); - bolusingEvent.t = t; - bolusingEvent.percent = Math.min((int) (deliveredInsulin / amount * 100), 100); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.bolusdelivering), deliveredInsulin)); + bolusingEvent.setT(t); + bolusingEvent.setPercent(Math.min((int) (deliveredInsulin / amount * 100), 100)); done = true; - MainApp.bus().post(bolusingEvent); + RxBus.INSTANCE.send(bolusingEvent); } if (L.isEnabled(L.PUMPCOMM)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java index 290e9743eff..1d650cb7792 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Notify_Delivery_Rate_Display.java @@ -8,6 +8,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.treatments.Treatment; @@ -40,12 +41,12 @@ public void handleMessage(byte[] data) { if (t != null) { lastReceive = System.currentTimeMillis(); t.insulin = deliveredInsulin; - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.status = String.format(MainApp.gs(R.string.bolusdelivering), deliveredInsulin); - bolusingEvent.t = t; - bolusingEvent.percent = Math.min((int) (deliveredInsulin / amount * 100), 100); - failed = bolusingEvent.percent < 100? true: false; - MainApp.bus().post(bolusingEvent); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.bolusdelivering), deliveredInsulin)); + bolusingEvent.setT(t); + bolusingEvent.setPercent(Math.min((int) (deliveredInsulin / amount * 100), 100)); + failed = bolusingEvent.getPercent() < 100? true: false; + RxBus.INSTANCE.send(bolusingEvent); } if (L.isEnabled(L.PUMPCOMM)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Option_Get_Pump_Time.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Option_Get_Pump_Time.java index d12ed52c791..500c1707b6e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Option_Get_Pump_Time.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/comm/DanaRS_Packet_Option_Get_Pump_Time.java @@ -58,6 +58,11 @@ public void handleMessage(byte[] data) { } } + @Override + public void handleMessageNotReceived() { + DanaRPump.getInstance().pumpTime = 0; + } + @Override public String getFriendlyName() { return "OPTION__GET_PUMP_TIME"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/dialogs/PairingProgressDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/dialogs/PairingProgressDialog.java new file mode 100644 index 00000000000..bad9e981124 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/dialogs/PairingProgressDialog.java @@ -0,0 +1,139 @@ +package info.nightscout.androidaps.plugins.pump.danaRS.dialogs; + + +import android.app.Activity; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.fragment.app.DialogFragment; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.danaRS.activities.PairingHelperActivity; +import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSPairingSuccess; +import info.nightscout.androidaps.utils.FabricPrivacy; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + + +public class PairingProgressDialog extends DialogFragment { + private CompositeDisposable disposable = new CompositeDisposable(); + + private TextView statusView; + private ProgressBar progressBar; + private Button button; + private PairingHelperActivity helperActivity; + + private static boolean pairingEnded = false; + + private static Handler sHandler; + private static HandlerThread sHandlerThread; + + public PairingProgressDialog() { + super(); + // Required empty public constructor + if (sHandlerThread == null) { + sHandlerThread = new HandlerThread(PairingProgressDialog.class.getSimpleName()); + sHandlerThread.start(); + sHandler = new Handler(sHandlerThread.getLooper()); + } + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.danars_pairingprogressdialog, container, false); + + getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + setCancelable(false); + getDialog().setCanceledOnTouchOutside(false); + + statusView = view.findViewById(R.id.danars_pairingprogress_status); + progressBar = view.findViewById(R.id.danars_pairingprogress_progressbar); + button = view.findViewById(R.id.ok); + + progressBar.setMax(100); + progressBar.setProgress(0); + statusView.setText(MainApp.gs(R.string.waitingforpairing)); + button.setVisibility(View.GONE); + button.setOnClickListener(v -> dismiss()); + + sHandler.post(() -> { + for (int i = 0; i < 20; i++) { + if (pairingEnded) { + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> { + progressBar.setProgress(100); + statusView.setText(R.string.pairingok); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + dismiss(); + }); + } else + dismiss(); + return; + } + progressBar.setProgress(i * 5); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> { + progressBar.setProgress(100); + statusView.setText(R.string.pairingtimedout); + button.setVisibility(View.VISIBLE); + }); + } + }); + return view; + } + + @Override + public void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventDanaRSPairingSuccess.class) + .observeOn(Schedulers.io()) + .subscribe(event -> pairingEnded = true, FabricPrivacy::logException) + ); + if (pairingEnded) dismiss(); + getDialog().getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + @Override + public void dismiss() { + super.dismissAllowingStateLoss(); + if (helperActivity != null) { + helperActivity.finish(); + } + } + + @Override + public void onPause() { + super.onPause(); + disposable.clear(); + } + + public PairingProgressDialog setHelperActivity(PairingHelperActivity activity) { + this.helperActivity = activity; + return this; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.java deleted file mode 100644 index 224f939ad70..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.java +++ /dev/null @@ -1,10 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRS.events; - -import info.nightscout.androidaps.events.Event; - -/** - * Created by mike on 05.09.2017. - */ - -public class EventDanaRSDeviceChange extends Event { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.kt new file mode 100644 index 00000000000..8b87d59ec1b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSDeviceChange.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.danaRS.events + +import info.nightscout.androidaps.events.Event + +class EventDanaRSDeviceChange : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPacket.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPacket.java deleted file mode 100644 index e0c401a27f8..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPacket.java +++ /dev/null @@ -1,16 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRS.events; - -import info.nightscout.androidaps.events.Event; -import info.nightscout.androidaps.plugins.pump.danaRS.comm.DanaRS_Packet; - -/** - * Created by mike on 01.09.2017. - */ - -public class EventDanaRSPacket extends Event{ - public EventDanaRSPacket(DanaRS_Packet data) { - this.data = data; - } - - public DanaRS_Packet data; -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.java deleted file mode 100644 index e332e9e0b28..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.java +++ /dev/null @@ -1,10 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRS.events; - -import info.nightscout.androidaps.events.Event; - -/** - * Created by mike on 01.09.2017. - */ - -public class EventDanaRSPairingSuccess extends Event{ -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.kt new file mode 100644 index 00000000000..6e0379a4d39 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/events/EventDanaRSPairingSuccess.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.danaRS.events + +import info.nightscout.androidaps.events.Event + +class EventDanaRSPairingSuccess : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java index 69ca9f19010..253b861811c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/BLEComm.java @@ -26,6 +26,8 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; @@ -33,9 +35,7 @@ import info.nightscout.androidaps.plugins.pump.danaRS.activities.PairingHelperActivity; import info.nightscout.androidaps.plugins.pump.danaRS.comm.DanaRSMessageHashTable; import info.nightscout.androidaps.plugins.pump.danaRS.comm.DanaRS_Packet; -import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSPacket; import info.nightscout.androidaps.plugins.pump.danaRS.events.EventDanaRSPairingSuccess; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.utils.SP; /** @@ -338,7 +338,7 @@ private synchronized void onConnectionStateChangeSynchronized(BluetoothGatt gatt close(); isConnected = false; isConnecting = false; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); if (L.isEnabled(L.PUMPBTCOMM)) log.debug("Device was disconnected " + gatt.getDevice().getName());//Device was disconnected } @@ -451,24 +451,24 @@ private void readDataParsing() { if (L.isEnabled(L.PUMPBTCOMM)) log.debug("<<<<< " + "ENCRYPTION__PUMP_CHECK (PUMP)" + " " + DanaRS_Packet.toHexString(inputBuffer)); mSendQueue.clear(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED, MainApp.gs(R.string.pumperror))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, MainApp.gs(R.string.pumperror))); NSUpload.uploadError(MainApp.gs(R.string.pumperror)); Notification n = new Notification(Notification.PUMPERROR, MainApp.gs(R.string.pumperror), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(n)); + RxBus.INSTANCE.send(new EventNewNotification(n)); } else if (inputBuffer.length == 6 && inputBuffer[2] == 'B' && inputBuffer[3] == 'U' && inputBuffer[4] == 'S' && inputBuffer[5] == 'Y') { if (L.isEnabled(L.PUMPBTCOMM)) log.debug("<<<<< " + "ENCRYPTION__PUMP_CHECK (BUSY)" + " " + DanaRS_Packet.toHexString(inputBuffer)); mSendQueue.clear(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED, MainApp.gs(R.string.pumpbusy))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, MainApp.gs(R.string.pumpbusy))); } else { // ERROR in response, wrong serial number if (L.isEnabled(L.PUMPBTCOMM)) log.debug("<<<<< " + "ENCRYPTION__PUMP_CHECK (ERROR)" + " " + DanaRS_Packet.toHexString(inputBuffer)); mSendQueue.clear(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED, MainApp.gs(R.string.connectionerror))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED, MainApp.gs(R.string.connectionerror))); SP.remove(MainApp.gs(R.string.key_danars_pairingkey) + DanaRSPlugin.mDeviceName); Notification n = new Notification(Notification.WRONGSERIALNUMBER, MainApp.gs(R.string.wrongpassword), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(n)); + RxBus.INSTANCE.send(new EventNewNotification(n)); } break; // 2nd packet, pairing key @@ -495,7 +495,7 @@ private void readDataParsing() { if (L.isEnabled(L.PUMPBTCOMM)) log.debug("<<<<< " + "ENCRYPTION__PASSKEY_RETURN " + DanaRS_Packet.toHexString(inputBuffer)); // Paring is successfull, sending time info - MainApp.bus().post(new EventDanaRSPairingSuccess()); + RxBus.INSTANCE.send(new EventDanaRSPairingSuccess()); SendTimeInfo(); byte[] pairingKey = {inputBuffer[2], inputBuffer[3]}; // store pairing key to preferences @@ -514,7 +514,7 @@ private void readDataParsing() { if (L.isEnabled(L.PUMPBTCOMM)) log.debug("Pump user password: " + Integer.toHexString(pass)); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)); isConnected = true; isConnecting = false; if (L.isEnabled(L.PUMPBTCOMM)) @@ -545,14 +545,13 @@ private void readDataParsing() { // notify to sendMessage message.notify(); } - MainApp.bus().post(new EventDanaRSPacket(message)); } else { log.error("Unknown message received " + DanaRS_Packet.toHexString(inputBuffer)); } break; } } catch (Exception e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } startSignatureFound = false; packetIsValid = false; @@ -636,13 +635,14 @@ public void sendMessage(DanaRS_Packet message) { message.wait(5000); } catch (InterruptedException e) { log.error("sendMessage InterruptedException", e); - e.printStackTrace(); + log.error("Unhandled exception", e); } } //SystemClock.sleep(200); if (!message.isReceived()) { log.warn("Reply not received " + message.getFriendlyName()); + message.handleMessageNotReceived(); } } @@ -663,9 +663,9 @@ private void SendPairingRequest() { private void SendPumpCheck() { // 1st message sent to pump after connect String devicename = getConnectDeviceName(); - if(devicename == null || devicename.equals("")){ + if (devicename == null || devicename.equals("")) { Notification n = new Notification(Notification.DEVICENOTPAIRED, MainApp.gs(R.string.pairfirst), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(n)); + RxBus.INSTANCE.send(new EventNewNotification(n)); return; } byte[] bytes = BleCommandUtil.getInstance().getEncryptedPacket(BleCommandUtil.DANAR_PACKET__OPCODE_ENCRYPTION__PUMP_CHECK, null, devicename); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/DanaRSService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/DanaRSService.java index 06a3a651ca9..4e56133222d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/DanaRSService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRS/services/DanaRSService.java @@ -6,8 +6,6 @@ import android.os.IBinder; import android.os.SystemClock; -import com.squareup.otto.Subscribe; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,10 +22,12 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -79,12 +79,15 @@ import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; public class DanaRSService extends Service { private Logger log = LoggerFactory.getLogger(L.PUMPCOMM); + private CompositeDisposable disposable = new CompositeDisposable(); private BLEComm bleComm = BLEComm.getInstance(this); @@ -96,12 +99,25 @@ public class DanaRSService extends Service { private long lastApproachingDailyLimit = 0; public DanaRSService() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); + } + + @Override + public void onCreate() { + super.onCreate(); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.PUMP)) log.debug("EventAppExit received"); + stopSelf(); + }, FabricPrivacy::logException) + ); + } + + @Override + public void onDestroy() { + disposable.clear(); + super.onDestroy(); } public boolean isConnected() { @@ -131,14 +147,14 @@ public void sendMessage(DanaRS_Packet message) { public void getPumpStatus() { DanaRPump danaRPump = DanaRPump.getInstance(); try { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); bleComm.sendMessage(new DanaRS_Packet_General_Initial_Screen_Information()); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Extended_Bolus_State()); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Step_Bolus_Information()); // last bolus, bolusStep, maxBolus - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); danaRPump.lastConnection = System.currentTimeMillis(); @@ -146,17 +162,39 @@ public void getPumpStatus() { Profile profile = ProfileFunctions.getInstance().getProfile(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile != null && Math.abs(danaRPump.currentBasal - profile.getBasal()) >= pump.getPumpDescription().basalStep) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Basal_Rate()); // basal profile, basalStep, maxBasal if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { - MainApp.bus().post(new EventProfileNeedsUpdate()); + RxBus.INSTANCE.send(new EventProfileNeedsUpdate()); } } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); bleComm.sendMessage(new DanaRS_Packet_Option_Get_Pump_Time()); long timeDiff = (danaRPump.pumpTime - System.currentTimeMillis()) / 1000L; + if (danaRPump.pumpTime == 0) { + // initial handshake was not successfull + // deinitialize pump + danaRPump.lastConnection = 0; + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); + return; + } + long now = System.currentTimeMillis(); + if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !pump.isInitialized()) { + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + bleComm.sendMessage(new DanaRS_Packet_General_Get_Shipping_Information()); // serial no + bleComm.sendMessage(new DanaRS_Packet_General_Get_Pump_Check()); // firmware + bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Profile_Number()); + bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Bolus_Option()); // isExtendedEnabled + bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Basal_Rate()); // basal profile, basalStep, maxBasal + bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Calculation_Information()); // target + bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_CIR_CF_Array()); + bleComm.sendMessage(new DanaRS_Packet_Option_Get_User_Option()); // Getting user options + danaRPump.lastSettingsRead = now; + } + if (L.isEnabled(L.PUMPCOMM)) log.debug("Pump time difference: " + timeDiff + " seconds"); if (Math.abs(timeDiff) > 3) { @@ -173,13 +211,17 @@ public void getPumpStatus() { //deinitialize pump danaRPump.lastConnection = 0; - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); return; } else { - 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()))); + if (danaRPump.protocol >= 6) { + bleComm.sendMessage(new DanaRS_Packet_Option_Set_Pump_Time(new Date())); + } else { + 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 - System.currentTimeMillis()) / 1000L; if (L.isEnabled(L.PUMPCOMM)) @@ -187,31 +229,17 @@ public void getPumpStatus() { } } - long now = System.currentTimeMillis(); - if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !pump.isInitialized()) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); - bleComm.sendMessage(new DanaRS_Packet_General_Get_Shipping_Information()); // serial no - bleComm.sendMessage(new DanaRS_Packet_General_Get_Pump_Check()); // firmware - bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Profile_Number()); - bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Bolus_Option()); // isExtendedEnabled - bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Basal_Rate()); // basal profile, basalStep, maxBasal - bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Calculation_Information()); // target - bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_CIR_CF_Array()); - bleComm.sendMessage(new DanaRS_Packet_Option_Get_User_Option()); // Getting user options - danaRPump.lastSettingsRead = now; - } - loadEvents(); - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { if (L.isEnabled(L.PUMPCOMM)) log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); if (System.currentTimeMillis() > lastApproachingDailyLimit + 30 * 60 * 1000) { Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.gs(R.string.approachingdailylimit), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); NSUpload.uploadError(MainApp.gs(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); lastApproachingDailyLimit = System.currentTimeMillis(); } @@ -225,7 +253,7 @@ public void getPumpStatus() { public PumpEnactResult loadEvents() { - if (!MainApp.getSpecificPlugin(DanaRSPlugin.class).isInitialized()) { + if (!DanaRSPlugin.getPlugin().isInitialized()) { PumpEnactResult result = new PumpEnactResult().success(false); result.comment = "pump not initialized"; return result; @@ -241,7 +269,7 @@ public PumpEnactResult loadEvents() { } else { msg = new DanaRS_Packet_APS_History_Events(lastHistoryFetched); if (L.isEnabled(L.PUMPCOMM)) - log.debug("Loading event history from: " +DateUtil.dateAndTimeFullString(lastHistoryFetched)); + log.debug("Loading event history from: " + DateUtil.dateAndTimeFullString(lastHistoryFetched)); } bleComm.sendMessage(msg); while (!msg.done && bleComm.isConnected()) { @@ -269,7 +297,7 @@ public boolean bolus(final double insulin, int carbs, long carbtime, Treatment t if (!isConnected()) return false; if (BolusProgressDialog.stopPressed) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.startingbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.startingbolus))); bolusingTreatment = t; final int preferencesSpeed = SP.getInt(R.string.key_danars_bolusspeed, 0); DanaRS_Packet_Bolus_Set_Step_Bolus_Start start = new DanaRS_Packet_Bolus_Set_Step_Bolus_Start(insulin, preferencesSpeed); @@ -305,9 +333,9 @@ public boolean bolus(final double insulin, int carbs, long carbtime, Treatment t } } - final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.t = t; - bolusingEvent.percent = 99; + final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setT(t); + bolusingEvent.setPercent(99); bolusingTreatment = null; int speed = 12; @@ -326,8 +354,8 @@ public boolean bolus(final double insulin, int carbs, long carbtime, Treatment t long expectedEnd = bolusStart + bolusDurationInMSec + 2000; while (System.currentTimeMillis() < expectedEnd) { long waitTime = expectedEnd - System.currentTimeMillis(); - bolusingEvent.status = String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000); - MainApp.bus().post(bolusingEvent); + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000)); + RxBus.INSTANCE.send(bolusingEvent); SystemClock.sleep(1000); } // do not call loadEvents() directly, reconnection may be needed @@ -335,10 +363,10 @@ public boolean bolus(final double insulin, int carbs, long carbtime, Treatment t @Override public void run() { // reread bolus status - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Step_Bolus_Information()); // last bolus - bolusingEvent.percent = 100; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.disconnecting))); + bolusingEvent.setPercent(100); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.disconnecting))); } }); return !start.failed; @@ -363,30 +391,30 @@ public void bolusStop() { public boolean tempBasal(Integer percent, int durationInHours) { if (!isConnected()) return false; if (DanaRPump.getInstance().isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(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))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); bleComm.sendMessage(new DanaRS_Packet_Basal_Set_Temporary_Basal(percent, durationInHours)); SystemClock.sleep(200); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean highTempBasal(Integer percent) { if (DanaRPump.getInstance().isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(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))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); bleComm.sendMessage(new DanaRS_Packet_APS_Basal_Set_Temporary_Basal(percent)); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -397,52 +425,52 @@ public boolean tempBasalShortDuration(Integer percent, int durationInMinutes) { } if (DanaRPump.getInstance().isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(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))); + RxBus.INSTANCE.send(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)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean tempBasalStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); bleComm.sendMessage(new DanaRS_Packet_Basal_Set_Cancel_Temporary_Basal()); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolus(Double insulin, int durationInHalfHours) { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); bleComm.sendMessage(new DanaRS_Packet_Bolus_Set_Extended_Bolus(insulin, durationInHalfHours)); SystemClock.sleep(200); bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Extended_Bolus_State()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolusStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); bleComm.sendMessage(new DanaRS_Packet_Bolus_Set_Extended_Bolus_Cancel()); bleComm.sendMessage(new DanaRS_Packet_Bolus_Get_Extended_Bolus_State()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean updateBasalsInPump(Profile profile) { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); double[] basal = DanaRPump.getInstance().buildDanaRProfileRecord(profile); DanaRS_Packet_Basal_Set_Profile_Basal_Rate msgSet = new DanaRS_Packet_Basal_Set_Profile_Basal_Rate(0, basal); bleComm.sendMessage(msgSet); @@ -450,7 +478,7 @@ public boolean updateBasalsInPump(Profile profile) { bleComm.sendMessage(msgActivate); DanaRPump.getInstance().lastSettingsRead = 0; // force read full settings getPumpStatus(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -520,21 +548,13 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } - @Subscribe - public void onStatusEvent(EventAppExit event) { - if (L.isEnabled(L.PUMP)) - log.debug("EventAppExit received"); - - stopSelf(); - } - 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)))); + RxBus.INSTANCE.send(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/pump/danaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java index 9e3ca499d0d..3437d37656e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/DanaRv2Plugin.java @@ -5,10 +5,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.v4.app.FragmentActivity; -import android.support.v7.app.AlertDialog; - -import com.squareup.otto.Subscribe; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -19,11 +15,9 @@ import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.logging.L; - -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; -import info.nightscout.androidaps.plugins.configBuilder.DetailedBolusInfoStorage; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; - import info.nightscout.androidaps.plugins.pump.danaR.AbstractDanaRPlugin; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStartWithSpeed; @@ -31,14 +25,18 @@ import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.Round; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 05.08.2016. */ public class DanaRv2Plugin extends AbstractDanaRPlugin { + private CompositeDisposable disposable = new CompositeDisposable(); private static DanaRv2Plugin plugin = null; @@ -61,7 +59,13 @@ protected void onStart() { Intent intent = new Intent(context, DanaRv2ExecutionService.class); context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + MainApp.instance().getApplicationContext().unbindService(mConnection); + }, FabricPrivacy::logException) + ); super.onStart(); } @@ -70,7 +74,8 @@ protected void onStop() { Context context = MainApp.instance().getApplicationContext(); context.unbindService(mConnection); - MainApp.bus().unregister(this); + disposable.clear(); + super.onStop(); } private ServiceConnection mConnection = new ServiceConnection() { @@ -89,12 +94,6 @@ public void onServiceConnected(ComponentName name, IBinder service) { } }; - @SuppressWarnings("UnusedParameters") - @Subscribe - public void onStatusEvent(final EventAppExit e) { - MainApp.instance().getApplicationContext().unbindService(mConnection); - } - // Plugin base interface @Override public String getName() { @@ -126,29 +125,6 @@ public void finishHandshaking() { sExecutionService.finishHandshaking(); } - @Override - public void switchAllowed(ConfigBuilderFragment.PluginViewHolder.PluginSwitcher pluginSwitcher, FragmentActivity context) { - boolean allowHardwarePump = SP.getBoolean("allow_hardware_pump", false); - if (allowHardwarePump || context == null) { - pluginSwitcher.invoke(); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setMessage(R.string.allow_hardware_pump_text) - .setPositiveButton(R.string.yes, (dialog, id) -> { - pluginSwitcher.invoke(); - SP.putBoolean("allow_hardware_pump", true); - if (L.isEnabled(L.PUMP)) - log.debug("First time HW pump allowed!"); - }) - .setNegativeButton(R.string.cancel, (dialog, id) -> { - pluginSwitcher.cancel(); - if (L.isEnabled(L.PUMP)) - log.debug("User does not allow switching to HW pump!"); - }); - builder.create().show(); - } - } - // Pump interface @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { @@ -178,7 +154,7 @@ public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { if (carbTime == 0) carbTime--; // better set 1 man back to prevent clash with insulin detailedBolusInfo.carbTime = 0; - DetailedBolusInfoStorage.add(detailedBolusInfo); // will be picked up on reading history + DetailedBolusInfoStorage.INSTANCE.add(detailedBolusInfo); // will be picked up on reading history Treatment t = new Treatment(); t.isSMB = detailedBolusInfo.isSMB; @@ -261,7 +237,7 @@ public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer duratio TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); if (activeTemp != null) { // Correct basal already set ? - if (activeTemp.percentRate == percentRate) { + if (activeTemp.percentRate == percentRate && activeTemp.getPlannedRemainingMinutes() > 4) { if (!enforceNew) { result.success = true; result.percent = percentRate; @@ -279,7 +255,7 @@ public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer duratio if (L.isEnabled(L.PUMP)) log.debug("setTempBasalAbsolute: Setting temp basal " + percentRate + "% for " + durationInMinutes + " mins (doLowTemp || doHighTemp)"); if (percentRate == 0 && durationInMinutes > 30) { - result = setTempBasalPercent(percentRate, durationInMinutes, profile, false); + result = setTempBasalPercent(percentRate, durationInMinutes, profile, enforceNew); } else { // use special APS temp basal call ... 100+/15min .... 100-/30min result = setHighTempBasalPercent(percentRate); @@ -315,8 +291,8 @@ public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMi if (percent > getPumpDescription().maxTempPercent) percent = getPumpDescription().maxTempPercent; long now = System.currentTimeMillis(); - TemporaryBasal runningTB = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); - if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { + TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getRealTempBasalFromHistory(now); + if (activeTemp != null && activeTemp.percentRate == percent && activeTemp.getPlannedRemainingMinutes() > 4 && !enforceNew) { result.enacted = false; result.success = true; result.isTempCancel = false; @@ -402,6 +378,11 @@ public PumpEnactResult cancelTempBasal(boolean force) { } } + @Override + public PumpType model() { + return PumpType.DanaRv2; + } + @Override public PumpEnactResult loadEvents() { return sExecutionService.loadEvents(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/SerialIOThread.java deleted file mode 100644 index ba1fdc5cb23..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/SerialIOThread.java +++ /dev/null @@ -1,209 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRv2; - -import android.bluetooth.BluetoothSocket; -import android.os.SystemClock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; -import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; -import info.nightscout.androidaps.plugins.pump.danaR.services.AbstractSerialIOThread; -import info.nightscout.androidaps.plugins.pump.danaRv2.comm.MessageHashTable_v2; -import info.nightscout.androidaps.utils.CRC; - -/** - * Created by mike on 17.07.2016. - */ -public class SerialIOThread extends AbstractSerialIOThread { - private static Logger log = LoggerFactory.getLogger(L.PUMPBTCOMM); - - private InputStream mInputStream = null; - private OutputStream mOutputStream = null; - private BluetoothSocket mRfCommSocket; - - private boolean mKeepRunning = true; - private byte[] mReadBuff = new byte[0]; - - private MessageBase processedMessage; - - public SerialIOThread(BluetoothSocket rfcommSocket) { - super(); - - mRfCommSocket = rfcommSocket; - try { - mOutputStream = mRfCommSocket.getOutputStream(); - mInputStream = mRfCommSocket.getInputStream(); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - this.start(); - } - - @Override - public final void run() { - try { - while (mKeepRunning) { - int availableBytes = mInputStream.available(); - // Ask for 1024 byte (or more if available) - byte[] newData = new byte[Math.max(1024, availableBytes)]; - int gotBytes = mInputStream.read(newData); - // When we are here there is some new data available - appendToBuffer(newData, gotBytes); - - // process all messages we already got - while (mReadBuff.length > 3) { // 3rd byte is packet size. continue only if we an determine packet size - byte[] extractedBuff = cutMessageFromBuffer(); - if (extractedBuff == null) - break; // message is not complete in buffer (wrong packet calls disconnection) - - int command = (extractedBuff[5] & 0xFF) | ((extractedBuff[4] << 8) & 0xFF00); - - MessageBase message; - if (processedMessage != null && processedMessage.getCommand() == command) { - message = processedMessage; - } else { - // get it from hash table - message = MessageHashTable_v2.findMessage(command); - } - - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug("<<<<< " + message.getMessageName() + " " + message.toHexString(extractedBuff)); - - // process the message content - message.received = true; - message.handleMessage(extractedBuff); - synchronized (message) { - message.notify(); - } - } - } - } catch (Exception e) { - if (e.getMessage().indexOf("bt socket closed") < 0) - log.error("Thread exception: ", e); - mKeepRunning = false; - } - disconnect("EndOfLoop"); - } - - void appendToBuffer(byte[] newData, int gotBytes) { - // add newData to mReadBuff - byte[] newReadBuff = new byte[mReadBuff.length + gotBytes]; - System.arraycopy(mReadBuff, 0, newReadBuff, 0, mReadBuff.length); - System.arraycopy(newData, 0, newReadBuff, mReadBuff.length, gotBytes); - mReadBuff = newReadBuff; - } - - byte[] cutMessageFromBuffer() { - if (mReadBuff[0] == (byte) 0x7E && mReadBuff[1] == (byte) 0x7E) { - int length = (mReadBuff[2] & 0xFF) + 7; - // Check if we have enough data - if (mReadBuff.length < length) { - return null; - } - if (mReadBuff[length - 2] != (byte) 0x2E || mReadBuff[length - 1] != (byte) 0x2E) { - log.error("wrong packet lenght=" + length + " data " + MessageBase.toHexString(mReadBuff)); - disconnect("wrong packet"); - return null; - } - - short crc = CRC.getCrc16(mReadBuff, 3, length - 7); - byte crcByte0 = (byte) (crc >> 8 & 0xFF); - byte crcByte1 = (byte) (crc & 0xFF); - - byte crcByte0received = mReadBuff[length - 4]; - byte crcByte1received = mReadBuff[length - 3]; - - if (crcByte0 != crcByte0received || crcByte1 != crcByte1received) { - log.error("CRC Error" + String.format("%02x ", crcByte0) + String.format("%02x ", crcByte1) + String.format("%02x ", crcByte0received) + String.format("%02x ", crcByte1received)); - disconnect("crc error"); - return null; - } - // Packet is verified here. extract data - byte[] extractedBuff = new byte[length]; - System.arraycopy(mReadBuff, 0, extractedBuff, 0, length); - // remove extracted data from read buffer - byte[] unprocessedData = new byte[mReadBuff.length - length]; - System.arraycopy(mReadBuff, length, unprocessedData, 0, unprocessedData.length); - mReadBuff = unprocessedData; - return extractedBuff; - } else { - log.error("Wrong beginning of packet len=" + mReadBuff.length + " " + MessageBase.toHexString(mReadBuff)); - disconnect("Wrong beginning of packet"); - return null; - } - } - - @Override - public synchronized void sendMessage(MessageBase message) { - if (!mRfCommSocket.isConnected()) { - log.error("Socket not connected on sendMessage"); - return; - } - processedMessage = message; - - byte[] messageBytes = message.getRawMessageBytes(); - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(">>>>> " + message.getMessageName() + " " + message.toHexString(messageBytes)); - - try { - mOutputStream.write(messageBytes); - } catch (Exception e) { - log.error("sendMessage write exception: ", e); - } - - synchronized (message) { - try { - message.wait(5000); - } catch (InterruptedException e) { - log.error("sendMessage InterruptedException", e); - } - } - - SystemClock.sleep(200); - if (!message.received) { - log.error("Reply not received " + message.getMessageName()); - if (message.getCommand() == 0xF0F1) { - DanaRPump.getInstance().isNewPump = false; - log.error("Old firmware detected"); - } - } - } - - @Override - public void disconnect(String reason) { - mKeepRunning = false; - try { - mInputStream.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - mOutputStream.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - mRfCommSocket.close(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - try { - System.runFinalization(); - } catch (Exception e) { - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug(e.getMessage()); - } - if (L.isEnabled(L.PUMPBTCOMM)) - log.debug("Disconnected: " + reason); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTableRv2.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTableRv2.kt new file mode 100644 index 00000000000..f22a3cf3fa5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTableRv2.kt @@ -0,0 +1,77 @@ +package info.nightscout.androidaps.plugins.pump.danaRv2.comm + +import info.nightscout.androidaps.plugins.pump.danaR.comm.* +import java.util.* + + +object MessageHashTableRv2 : MessageHashTableBase { + var messages: HashMap = HashMap() + + init { + put(MsgBolusStop()) // 0x0101 CMD_MEALINS_STOP + put(MsgBolusStart()) // 0x0102 CMD_MEALINS_START_DATA + put(MsgBolusStartWithSpeed()) // 0x0104 CMD_MEALINS_START_DATA_SPEED + put(MsgBolusProgress()) // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS + put(MsgStatusProfile()) // 0x0204 CMD_PUMP_CALCULATION_SETTING + + put(MsgStatusTempBasal_v2()) // 0x0205 CMD_PUMP_EXERCISE_MODE + put(MsgStatusBolusExtended_v2()) // 0x0207 CMD_PUMP_EXPANS_INS_I + + put(MsgStatusBasic()) // 0x020A CMD_PUMP_INITVIEW_I + put(MsgStatus()) // 0x020B CMD_PUMP_STATUS + put(MsgInitConnStatusTime()) // 0x0301 CMD_PUMPINIT_TIME_INFO + put(MsgInitConnStatusBolus()) // 0x0302 CMD_PUMPINIT_BOLUS_INFO + put(MsgInitConnStatusBasic()) // 0x0303 CMD_PUMPINIT_INIT_INFO + put(MsgInitConnStatusOption()) // 0x0304 CMD_PUMPINIT_OPTION + put(MsgSetTempBasalStart()) // 0x0401 CMD_PUMPSET_EXERCISE_S + put(MsgSetCarbsEntry()) // 0x0402 CMD_PUMPSET_HIS_S + put(MsgSetTempBasalStop()) // 0x0403 CMD_PUMPSET_EXERCISE_STOP + put(MsgSetExtendedBolusStop()) // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP + put(MsgSetExtendedBolusStart()) // 0x0407 CMD_PUMPSET_EXPANS_INS_S + put(MsgError()) // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS + put(MsgPCCommStart()) // 0x3001 CMD_CONNECT + put(MsgPCCommStop()) // 0x3002 CMD_DISCONNECT + put(MsgHistoryBolus()) // 0x3101 CMD_HISTORY_MEAL_INS + put(MsgHistoryDailyInsulin()) // 0x3102 CMD_HISTORY_DAY_INS + put(MsgHistoryGlucose()) // 0x3104 CMD_HISTORY_GLUCOSE + put(MsgHistoryAlarm()) // 0x3105 CMD_HISTORY_ALARM + put(MsgHistoryError()) // 0x3106 CMD_HISTORY_ERROR + put(MsgHistoryCarbo()) // 0x3107 CMD_HISTORY_CARBOHY + put(MsgHistoryRefill()) // 0x3108 CMD_HISTORY_REFILL + put(MsgHistorySuspend()) // 0x3109 CMD_HISTORY_SUSPEND + put(MsgHistoryBasalHour()) // 0x310A CMD_HISTORY_BASAL_HOUR + put(MsgHistoryDone()) // 0x31F1 CMD_HISTORY_DONT_USED + put(MsgSettingBasal()) // 0x3202 CMD_SETTING_V_BASAL_INS_I + put(MsgSettingMeal()) // 0x3203 CMD_SETTING_V_MEAL_SETTING_I + put(MsgSettingProfileRatios()) // 0x3204 CMD_SETTING_V_CCC_I + put(MsgSettingMaxValues()) // 0x3205 CMD_SETTING_V_MAX_VALUE_I + put(MsgSettingBasalProfileAll()) // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL + put(MsgSettingShippingInfo()) // 0x3207 CMD_SETTING_V_SHIPPING_I + put(MsgSettingGlucose()) // 0x3209 CMD_SETTING_V_GLUCOSEandEASY + put(MsgSettingPumpTime()) // 0x320A CMD_SETTING_V_TIME_I + put(MsgSettingUserOptions()) // 0x320B CMD_SETTING_V_USER_OPTIONS + put(MsgSettingActiveProfile()) // 0x320C CMD_SETTING_V_PROFILE_NUMBER + put(MsgSettingProfileRatiosAll()) // 0x320D CMD_SETTING_V_CIR_CF_VALUE + put(MsgSetSingleBasalProfile()) // 0x3302 CMD_SETTING_BASAL_INS_S + put(MsgSetBasalProfile()) // 0x3306 CMD_SETTING_BASAL_PROFILE_S + put(MsgSetUserOptions()) // 0x330B CMD_SETTING_USER_OPTIONS_S + put(MsgSetActivateBasalProfile()) // 0x330C CMD_SETTING_PROFILE_NUMBER_S + put(MsgHistoryAllDone()) // 0x41F1 CMD_HISTORY_ALL_DONE + put(MsgHistoryAll()) // 0x41F2 CMD_HISTORY_ALL + put(MsgHistoryNewDone()) // 0x42F1 CMD_HISTORY_NEW_DONE + put(MsgHistoryNew()) // 0x42F2 CMD_HISTORY_NEW + put(MsgCheckValue_v2()) // 0xF0F1 CMD_PUMP_CHECK_VALUE + put(MsgStatusAPS_v2()) // 0xE001 CMD_PUMPSTATUS_APS + put(MsgSetAPSTempBasalStart_v2()) // 0xE002 CMD_PUMPSET_APSTEMP + put(MsgHistoryEvents_v2()) // 0xE003 CMD_GET_HISTORY + put(MsgSetHistoryEntry_v2()) // 0xE004 CMD_SET_HISTORY_ENTRY + } + + override fun put(message: MessageBase) { + messages[message.command] = message + } + + override fun findMessage(command: Int): MessageBase { + return messages[command] ?: MessageBase() + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2.java deleted file mode 100644 index 66376290af8..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MessageHashTable_v2.java +++ /dev/null @@ -1,92 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.danaRv2.comm; - -import java.util.HashMap; - -import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; -import info.nightscout.androidaps.plugins.pump.danaR.comm.*; - - -/** - * Created by mike on 28.05.2016. - */ -public class MessageHashTable_v2 { - public static HashMap messages = null; - - static { - if (messages == null) { - messages = new HashMap(); - put(new MsgBolusStop()); // 0x0101 CMD_MEALINS_STOP - put(new MsgBolusStart()); // 0x0102 CMD_MEALINS_START_DATA - put(new MsgBolusStartWithSpeed()); // 0x0104 CMD_MEALINS_START_DATA_SPEED - put(new MsgBolusProgress()); // 0x0202 CMD_PUMP_THIS_REMAINDER_MEAL_INS - put(new MsgStatusProfile()); // 0x0204 CMD_PUMP_CALCULATION_SETTING - - put(new MsgStatusTempBasal_v2()); // 0x0205 CMD_PUMP_EXERCISE_MODE - put(new MsgStatusBolusExtended_v2()); // 0x0207 CMD_PUMP_EXPANS_INS_I - - put(new MsgStatusBasic()); // 0x020A CMD_PUMP_INITVIEW_I - put(new MsgStatus()); // 0x020B CMD_PUMP_STATUS - put(new MsgInitConnStatusTime()); // 0x0301 CMD_PUMPINIT_TIME_INFO - put(new MsgInitConnStatusBolus()); // 0x0302 CMD_PUMPINIT_BOLUS_INFO - put(new MsgInitConnStatusBasic()); // 0x0303 CMD_PUMPINIT_INIT_INFO - put(new MsgInitConnStatusOption()); // 0x0304 CMD_PUMPINIT_OPTION - put(new MsgSetTempBasalStart()); // 0x0401 CMD_PUMPSET_EXERCISE_S - put(new MsgSetCarbsEntry()); // 0x0402 CMD_PUMPSET_HIS_S - put(new MsgSetTempBasalStop()); // 0x0403 CMD_PUMPSET_EXERCISE_STOP - put(new MsgSetExtendedBolusStop()); // 0x0406 CMD_PUMPSET_EXPANS_INS_STOP - put(new MsgSetExtendedBolusStart()); // 0x0407 CMD_PUMPSET_EXPANS_INS_S - put(new MsgError()); // 0x0601 CMD_PUMPOWAY_SYSTEM_STATUS - put(new MsgPCCommStart()); // 0x3001 CMD_CONNECT - put(new MsgPCCommStop()); // 0x3002 CMD_DISCONNECT - put(new MsgHistoryBolus()); // 0x3101 CMD_HISTORY_MEAL_INS - put(new MsgHistoryDailyInsulin()); // 0x3102 CMD_HISTORY_DAY_INS - put(new MsgHistoryGlucose()); // 0x3104 CMD_HISTORY_GLUCOSE - put(new MsgHistoryAlarm()); // 0x3105 CMD_HISTORY_ALARM - put(new MsgHistoryError()); // 0x3106 CMD_HISTORY_ERROR - put(new MsgHistoryCarbo()); // 0x3107 CMD_HISTORY_CARBOHY - put(new MsgHistoryRefill()); // 0x3108 CMD_HISTORY_REFILL - put(new MsgHistorySuspend()); // 0x3109 CMD_HISTORY_SUSPEND - put(new MsgHistoryBasalHour()); // 0x310A CMD_HISTORY_BASAL_HOUR - put(new MsgHistoryDone()); // 0x31F1 CMD_HISTORY_DONT_USED - put(new MsgSettingBasal()); // 0x3202 CMD_SETTING_V_BASAL_INS_I - put(new MsgSettingMeal()); // 0x3203 CMD_SETTING_V_MEAL_SETTING_I - put(new MsgSettingProfileRatios()); // 0x3204 CMD_SETTING_V_CCC_I - put(new MsgSettingMaxValues()); // 0x3205 CMD_SETTING_V_MAX_VALUE_I - put(new MsgSettingBasalProfileAll()); // 0x3206 CMD_SETTING_V_BASAL_PROFILE_ALL - put(new MsgSettingShippingInfo()); // 0x3207 CMD_SETTING_V_SHIPPING_I - put(new MsgSettingGlucose()); // 0x3209 CMD_SETTING_V_GLUCOSEandEASY - put(new MsgSettingPumpTime()); // 0x320A CMD_SETTING_V_TIME_I - put(new MsgSettingUserOptions()); // 0x320B CMD_SETTING_V_USER_OPTIONS - put(new MsgSettingActiveProfile()); // 0x320C CMD_SETTING_V_PROFILE_NUMBER - put(new MsgSettingProfileRatiosAll()); // 0x320D CMD_SETTING_V_CIR_CF_VALUE - put(new MsgSetSingleBasalProfile()); // 0x3302 CMD_SETTING_BASAL_INS_S - put(new MsgSetBasalProfile()); // 0x3306 CMD_SETTING_BASAL_PROFILE_S - put(new MsgSetUserOptions()); // 0x330B CMD_SETTING_USER_OPTIONS_S - put(new MsgSetActivateBasalProfile()); // 0x330C CMD_SETTING_PROFILE_NUMBER_S - put(new MsgHistoryAllDone()); // 0x41F1 CMD_HISTORY_ALL_DONE - put(new MsgHistoryAll()); // 0x41F2 CMD_HISTORY_ALL - put(new MsgHistoryNewDone()); // 0x42F1 CMD_HISTORY_NEW_DONE - put(new MsgHistoryNew()); // 0x42F2 CMD_HISTORY_NEW - put(new MsgCheckValue_v2()); // 0xF0F1 CMD_PUMP_CHECK_VALUE - put(new MsgStatusAPS_v2()); // 0xE001 CMD_PUMPSTATUS_APS - put(new MsgSetAPSTempBasalStart_v2()); // 0xE002 CMD_PUMPSET_APSTEMP - put(new MsgHistoryEvents_v2()); // 0xE003 CMD_GET_HISTORY - put(new MsgSetHistoryEntry_v2()); // 0xE004 CMD_SET_HISTORY_ENTRY - } - } - - public static void put(MessageBase message) { - int command = message.getCommand(); - //String name = MessageOriginalNames.getName(command); - messages.put(command, message); - //log.debug(String.format("%04x ", command) + " " + name); - } - - public static MessageBase findMessage(Integer command) { - if (messages.containsKey(command)) { - return messages.get(command); - } else { - return new MessageBase(); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2.java index 5af7705106f..3070c52b641 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgCheckValue_v2.java @@ -5,9 +5,10 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventRefreshGui; +import info.nightscout.androidaps.events.EventRebuildTabs; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -43,24 +44,24 @@ public void handleMessage(byte[] bytes) { if (pump.model != DanaRPump.EXPORT_MODEL) { pump.lastConnection = 0; Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.gs(R.string.pumpdrivercorrected), Notification.NORMAL); - MainApp.bus().post(new EventNewNotification(notification)); - MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + DanaRPlugin.getPlugin().disconnect("Wrong Model"); log.error("Wrong model selected. Switching to Korean DanaR"); - 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); + DanaRKoreanPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, true); + DanaRKoreanPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, true); + DanaRPlugin.getPlugin().setPluginEnabled(PluginType.PUMP, false); + DanaRPlugin.getPlugin().setFragmentVisible(PluginType.PUMP, false); DanaRPump.reset(); // mark not initialized //If profile coming from pump, switch it as well - if (MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginType.PROFILE)) { - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); + if (DanaRPlugin.getPlugin().isEnabled(PluginType.PROFILE)) { + (DanaRPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, false); + (DanaRKoreanPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, true); } ConfigBuilderPlugin.getPlugin().storeSettings("ChangingDanaRv2Driver"); - MainApp.bus().post(new EventRefreshGui()); + RxBus.INSTANCE.send(new EventRebuildTabs()); ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; } @@ -68,22 +69,22 @@ public void handleMessage(byte[] bytes) { if (pump.protocol != 2) { pump.lastConnection = 0; Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.gs(R.string.pumpdrivercorrected), Notification.NORMAL); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); log.error("Wrong model selected. Switching to non APS DanaR"); - (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); + (DanaRv2Plugin.getPlugin()).setPluginEnabled(PluginType.PUMP, false); + (DanaRv2Plugin.getPlugin()).setFragmentVisible(PluginType.PUMP, false); + (DanaRPlugin.getPlugin()).setPluginEnabled(PluginType.PUMP, true); + (DanaRPlugin.getPlugin()).setFragmentVisible(PluginType.PUMP, true); //If profile coming from pump, switch it as well - if (MainApp.getSpecificPlugin(DanaRv2Plugin.class).isEnabled(PluginType.PROFILE)) { - (MainApp.getSpecificPlugin(DanaRv2Plugin.class)).setPluginEnabled(PluginType.PROFILE, false); - (MainApp.getSpecificPlugin(DanaRPlugin.class)).setPluginEnabled(PluginType.PROFILE, true); + if (DanaRv2Plugin.getPlugin().isEnabled(PluginType.PROFILE)) { + (DanaRv2Plugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, false); + (DanaRPlugin.getPlugin()).setPluginEnabled(PluginType.PROFILE, true); } ConfigBuilderPlugin.getPlugin().storeSettings("ChangingDanaRv2Driver"); - MainApp.bus().post(new EventRefreshGui()); + RxBus.INSTANCE.send(new EventRebuildTabs()); ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("PumpDriverChange", null); // force new connection return; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2.java index b3c7d00612b..3ef43cad86c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgHistoryEvents_v2.java @@ -13,7 +13,8 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.DetailedBolusInfoStorage; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; @@ -110,7 +111,7 @@ public void handleMessage(byte[] bytes) { status = "EXTENDEDSTOP " + DateUtil.timeString(datetime); break; case DanaRPump.BOLUS: - DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(datetime); + DetailedBolusInfo detailedBolusInfo = DetailedBolusInfoStorage.INSTANCE.findDetailedBolusInfo(datetime, param1 / 100d); if (detailedBolusInfo == null) { detailedBolusInfo = new DetailedBolusInfo(); } @@ -125,7 +126,7 @@ public void handleMessage(byte[] bytes) { status = "BOLUS " + DateUtil.timeString(datetime); break; case DanaRPump.DUALBOLUS: - detailedBolusInfo = DetailedBolusInfoStorage.findDetailedBolusInfo(datetime); + detailedBolusInfo = DetailedBolusInfoStorage.INSTANCE.findDetailedBolusInfo(datetime, param1 / 100d); if (detailedBolusInfo == null) { detailedBolusInfo = new DetailedBolusInfo(); } @@ -199,6 +200,6 @@ public void handleMessage(byte[] bytes) { if (datetime > lastEventTimeLoaded) lastEventTimeLoaded = datetime; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.processinghistory) + ": " + status)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.processinghistory) + ": " + status)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java index 578be6d4b63..a829b3afcb1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusBolusExtended_v2.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRv2.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java index a92c534d92b..19a0a2b6cec 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/comm/MsgStatusTempBasal_v2.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.pump.danaRv2.comm; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java index 2d2d2526ddd..ef254580f96 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/danaRv2/services/DanaRv2ExecutionService.java @@ -6,8 +6,6 @@ import android.os.Binder; import android.os.SystemClock; -import com.squareup.otto.Subscribe; - import java.io.IOException; import java.util.Date; @@ -23,15 +21,17 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.danaR.DanaRPump; +import info.nightscout.androidaps.plugins.pump.danaR.SerialIOThread; import info.nightscout.androidaps.plugins.pump.danaR.comm.MessageBase; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusProgress; import info.nightscout.androidaps.plugins.pump.danaR.comm.MsgBolusStart; @@ -61,7 +61,7 @@ import info.nightscout.androidaps.plugins.pump.danaR.events.EventDanaRNewStatus; import info.nightscout.androidaps.plugins.pump.danaR.services.AbstractDanaRExecutionService; import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; -import info.nightscout.androidaps.plugins.pump.danaRv2.SerialIOThread; +import info.nightscout.androidaps.plugins.pump.danaRv2.comm.MessageHashTableRv2; import info.nightscout.androidaps.plugins.pump.danaRv2.comm.MsgCheckValue_v2; import info.nightscout.androidaps.plugins.pump.danaRv2.comm.MsgHistoryEvents_v2; import info.nightscout.androidaps.plugins.pump.danaRv2.comm.MsgSetAPSTempBasalStart_v2; @@ -72,17 +72,20 @@ import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.queue.commands.Command; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { + private CompositeDisposable disposable = new CompositeDisposable(); private long lastHistoryFetched = 0; public DanaRv2ExecutionService() { mBinder = new LocalBinder(); - registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); } @@ -92,32 +95,36 @@ public DanaRv2ExecutionService getServiceInstance() { } } - private void registerBus() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); - } - - @Subscribe - public void onStatusEvent(EventAppExit event) { - if (L.isEnabled(L.PUMP)) - log.debug("EventAppExit received"); - - if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - - MainApp.instance().getApplicationContext().unregisterReceiver(receiver); - - stopSelf(); + @Override + public void onCreate() { + super.onCreate(); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (mSerialIOThread != null) + mSerialIOThread.disconnect("EventPreferenceChange"); + }, FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.PUMP)) + log.debug("EventAppExit received"); + + if (mSerialIOThread != null) + mSerialIOThread.disconnect("Application exit"); + MainApp.instance().getApplicationContext().unregisterReceiver(receiver); + stopSelf(); + }, FabricPrivacy::logException) + ); } - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); + @Override + public void onDestroy() { + disposable.clear(); + super.onDestroy(); } public void connect() { @@ -146,9 +153,9 @@ public void connect() { if (mSerialIOThread != null) { mSerialIOThread.disconnect("Recreate SerialIOThread"); } - mSerialIOThread = new SerialIOThread(mRfcommSocket); + mSerialIOThread = new SerialIOThread(mRfcommSocket, MessageHashTableRv2.INSTANCE); mHandshakeInProgress = true; - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.HANDSHAKING, 0)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.HANDSHAKING, 0)); } mConnectionInProgress = false; @@ -158,7 +165,7 @@ public void connect() { public void getPumpStatus() { DanaRPump danaRPump = DanaRPump.getInstance(); try { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpstatus))); MsgStatus statusMsg = new MsgStatus(); MsgStatusBasic statusBasicMsg = new MsgStatusBasic(); MsgStatusTempBasal_v2 tempStatusMsg = new MsgStatusTempBasal_v2(); @@ -172,12 +179,12 @@ public void getPumpStatus() { } } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); mSerialIOThread.sendMessage(statusMsg); mSerialIOThread.sendMessage(statusBasicMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingtempbasalstatus))); mSerialIOThread.sendMessage(tempStatusMsg); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingextendedbolusstatus))); mSerialIOThread.sendMessage(exStatusMsg); danaRPump.lastConnection = System.currentTimeMillis(); @@ -185,15 +192,24 @@ public void getPumpStatus() { Profile profile = ProfileFunctions.getInstance().getProfile(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); if (profile != null && Math.abs(danaRPump.currentBasal - profile.getBasal()) >= pump.getPumpDescription().basalStep) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingBasal()); if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { - MainApp.bus().post(new EventProfileNeedsUpdate()); + RxBus.INSTANCE.send(new EventProfileNeedsUpdate()); } } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); + if (danaRPump.pumpTime == 0) { + // initial handshake was not successfull + // deinitialize pump + danaRPump.lastConnection = 0; + danaRPump.lastSettingsRead = 0; + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); + return; + } long timeDiff = (danaRPump.pumpTime - System.currentTimeMillis()) / 1000L; if (L.isEnabled(L.PUMP)) log.debug("Pump time difference: " + timeDiff + " seconds"); @@ -211,8 +227,8 @@ public void getPumpStatus() { //deinitialize pump danaRPump.lastConnection = 0; - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); return; } else { waitForWholeMinute(); // Dana can set only whole minute @@ -227,7 +243,7 @@ public void getPumpStatus() { long now = System.currentTimeMillis(); if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !pump.isInitialized()) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); mSerialIOThread.sendMessage(new MsgSettingMeal()); @@ -244,15 +260,15 @@ public void getPumpStatus() { loadEvents(); - MainApp.bus().post(new EventDanaRNewStatus()); - MainApp.bus().post(new EventInitializationChanged()); + RxBus.INSTANCE.send(new EventDanaRNewStatus()); + RxBus.INSTANCE.send(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { if (L.isEnabled(L.PUMP)) log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); if (System.currentTimeMillis() > lastApproachingDailyLimit + 30 * 60 * 1000) { Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.gs(R.string.approachingdailylimit), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(reportFail)); + RxBus.INSTANCE.send(new EventNewNotification(reportFail)); NSUpload.uploadError(MainApp.gs(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); lastApproachingDailyLimit = System.currentTimeMillis(); } @@ -266,15 +282,15 @@ public boolean tempBasal(int percent, int durationInHours) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; if (danaRPump.isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); SystemClock.sleep(500); } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); mSerialIOThread.sendMessage(new MsgStatusTempBasal_v2()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -282,15 +298,15 @@ public boolean highTempBasal(int percent) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; if (danaRPump.isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); SystemClock.sleep(500); } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetAPSTempBasalStart_v2(percent)); mSerialIOThread.sendMessage(new MsgStatusTempBasal_v2()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -303,45 +319,45 @@ public boolean tempBasalShortDuration(int percent, int durationInMinutes) { if (!isConnected()) return false; if (danaRPump.isTempBasalInProgress) { - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); SystemClock.sleep(500); } - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingtempbasal))); + RxBus.INSTANCE.send(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)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean tempBasalStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); mSerialIOThread.sendMessage(new MsgStatusTempBasal_v2()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolus(double insulin, int durationInHalfHours) { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.settingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStart(insulin, (byte) (durationInHalfHours & 0xFF))); mSerialIOThread.sendMessage(new MsgStatusBolusExtended_v2()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } public boolean extendedBolusStop() { if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.stoppingextendedbolus))); mSerialIOThread.sendMessage(new MsgSetExtendedBolusStop()); mSerialIOThread.sendMessage(new MsgStatusBolusExtended_v2()); loadEvents(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } @@ -349,7 +365,7 @@ public boolean bolus(final double amount, int carbs, long carbtime, final Treatm if (!isConnected()) return false; if (BolusProgressDialog.stopPressed) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.startingbolus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.startingbolus))); mBolusingTreatment = t; final int preferencesSpeed = SP.getInt(R.string.key_danars_bolusspeed, 0); MessageBase start; @@ -387,9 +403,9 @@ public boolean bolus(final double amount, int carbs, long carbtime, final Treatm } } - final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.t = t; - bolusingEvent.percent = 99; + final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setT(t); + bolusingEvent.setPercent(99); mBolusingTreatment = null; int speed = 12; @@ -408,8 +424,8 @@ public boolean bolus(final double amount, int carbs, long carbtime, final Treatm long expectedEnd = bolusStart + bolusDurationInMSec + 2000; while (System.currentTimeMillis() < expectedEnd) { long waitTime = expectedEnd - System.currentTimeMillis(); - bolusingEvent.status = String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000); - MainApp.bus().post(bolusingEvent); + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.waitingforestimatedbolusend), waitTime / 1000)); + RxBus.INSTANCE.send(bolusingEvent); SystemClock.sleep(1000); } // do not call loadEvents() directly, reconnection may be needed @@ -417,10 +433,10 @@ public boolean bolus(final double amount, int carbs, long carbtime, final Treatm @Override public void run() { // load last bolus status - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.gettingbolusstatus))); mSerialIOThread.sendMessage(new MsgStatus()); - bolusingEvent.percent = 100; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.disconnecting))); + bolusingEvent.setPercent(100); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.disconnecting))); } }); return !start.failed; @@ -455,7 +471,7 @@ public boolean carbsEntry(int amount, long time) { public PumpEnactResult loadEvents() { DanaRPump danaRPump = DanaRPump.getInstance(); - if (!MainApp.getSpecificPlugin(DanaRv2Plugin.class).isInitialized()) { + if (!DanaRv2Plugin.getPlugin().isInitialized()) { PumpEnactResult result = new PumpEnactResult().success(false); result.comment = "pump not initialized"; return result; @@ -485,7 +501,7 @@ public PumpEnactResult loadEvents() { public boolean updateBasalsInPump(final Profile profile) { DanaRPump danaRPump = DanaRPump.getInstance(); if (!isConnected()) return false; - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.updatingbasalrates))); double[] basal = DanaRPump.getInstance().buildDanaRProfileRecord(profile); MsgSetBasalProfile msgSet = new MsgSetBasalProfile((byte) 0, basal); mSerialIOThread.sendMessage(msgSet); @@ -493,7 +509,7 @@ public boolean updateBasalsInPump(final Profile profile) { mSerialIOThread.sendMessage(msgActivate); danaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); return true; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/EventLocalInsightUpdateGUI.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/EventLocalInsightUpdateGUI.java deleted file mode 100644 index 89b001891c8..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/EventLocalInsightUpdateGUI.java +++ /dev/null @@ -1,6 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.insight; - -import info.nightscout.androidaps.events.EventUpdateGui; - -public class EventLocalInsightUpdateGUI extends EventUpdateGui { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java index d851389aaf5..26555898c07 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/InsightAlertService.java @@ -15,7 +15,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Vibrator; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java index 26f5180f47f..3464a1803ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightFragment.java @@ -3,8 +3,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -12,14 +10,16 @@ import android.widget.LinearLayout; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import java.util.ArrayList; import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.TBROverNotificationBlock; import info.nightscout.androidaps.plugins.pump.insight.descriptors.ActiveBasalRate; @@ -28,11 +28,16 @@ import info.nightscout.androidaps.plugins.pump.insight.descriptors.CartridgeStatus; import info.nightscout.androidaps.plugins.pump.insight.descriptors.InsightState; import info.nightscout.androidaps.plugins.pump.insight.descriptors.TotalDailyDose; +import info.nightscout.androidaps.plugins.pump.insight.events.EventLocalInsightUpdateGUI; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class LocalInsightFragment extends SubscriberFragment implements View.OnClickListener { +public class LocalInsightFragment extends Fragment implements View.OnClickListener { + private CompositeDisposable disposable = new CompositeDisposable(); private static final boolean ENABLE_OPERATING_MODE_BUTTON = false; @@ -61,6 +66,23 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return view; } + @Override + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventLocalInsightUpdateGUI.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGUI(), FabricPrivacy::logException) + ); + updateGUI(); + } + + @Override + public synchronized void onPause() { + super.onPause(); + disposable.clear(); + } + @Override public synchronized void onDestroyView() { super.onDestroyView(); @@ -121,12 +143,6 @@ public void run() { } } - @Subscribe - public void onUpdateGUIEvent(EventLocalInsightUpdateGUI event) { - updateGUI(); - } - - @Override protected void updateGUI() { if (!viewsCreated) return; statusItemContainer.removeAllViews(); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java index c5b505a3d3f..bae3e6da3cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java @@ -8,10 +8,8 @@ import android.os.IBinder; import android.os.Looper; -import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; -import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; -import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.*; -import info.nightscout.androidaps.plugins.pump.insight.descriptors.*; +import androidx.fragment.app.FragmentActivity; + import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -45,8 +43,12 @@ import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; @@ -73,6 +75,14 @@ import info.nightscout.androidaps.plugins.pump.insight.app_layer.history.history_events.StartOfTBREvent; import info.nightscout.androidaps.plugins.pump.insight.app_layer.history.history_events.TotalDailyDoseEvent; import info.nightscout.androidaps.plugins.pump.insight.app_layer.history.history_events.TubeFilledEvent; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.ActiveBRProfileBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.BRProfile1Block; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.BRProfileBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.FactoryMinBasalAmountBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.FactoryMinBolusAmountBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.MaxBasalAmountBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.MaxBolusAmountBlock; +import info.nightscout.androidaps.plugins.pump.insight.app_layer.parameter_blocks.TBROverNotificationBlock; import info.nightscout.androidaps.plugins.pump.insight.app_layer.remote_control.CancelBolusMessage; import info.nightscout.androidaps.plugins.pump.insight.app_layer.remote_control.CancelTBRMessage; import info.nightscout.androidaps.plugins.pump.insight.app_layer.remote_control.ChangeTBRMessage; @@ -96,6 +106,20 @@ import info.nightscout.androidaps.plugins.pump.insight.database.InsightBolusID; import info.nightscout.androidaps.plugins.pump.insight.database.InsightHistoryOffset; import info.nightscout.androidaps.plugins.pump.insight.database.InsightPumpID; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.ActiveBasalRate; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.ActiveBolus; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.ActiveTBR; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.AlertType; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.BasalProfile; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.BasalProfileBlock; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.BatteryStatus; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.BolusType; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.CartridgeStatus; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.InsightState; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.OperatingMode; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.PumpTime; +import info.nightscout.androidaps.plugins.pump.insight.descriptors.TotalDailyDose; +import info.nightscout.androidaps.plugins.pump.insight.events.EventLocalInsightUpdateGUI; import info.nightscout.androidaps.plugins.pump.insight.exceptions.InsightException; import info.nightscout.androidaps.plugins.pump.insight.exceptions.app_layer_errors.AppLayerErrorException; import info.nightscout.androidaps.plugins.pump.insight.exceptions.app_layer_errors.NoActiveTBRToCanceLException; @@ -105,6 +129,7 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.TimeChangeType; public class LocalInsightPlugin extends PluginBase implements PumpInterface, ConstraintsInterface, InsightConnectionService.StateCallback { @@ -125,8 +150,9 @@ public void onServiceConnected(ComponentName name, IBinder binder) { } else if (binder instanceof InsightAlertService.LocalBinder) { alertService = ((InsightAlertService.LocalBinder) binder).getService(); } - if (connectionService != null && alertService != null) - MainApp.bus().post(new EventInitializationChanged()); + if (connectionService != null && alertService != null) { + RxBus.INSTANCE.send(new EventInitializationChanged()); + } } @Override @@ -168,7 +194,8 @@ public LocalInsightPlugin() { .mainType(PluginType.PUMP) .description(R.string.description_pump_insight_local) .fragmentClass(LocalInsightFragment.class.getName()) - .preferencesId(R.xml.pref_insight_local)); + .preferencesId(MainApp.instance().getPackageName().equals("info.nightscout.androidaps") + ? R.xml.pref_insight_local_full : R.xml.pref_insight_local_pumpcontrol)); pumpDescription = new PumpDescription(); pumpDescription.setPumpDescription(PumpType.AccuChekInsightBluetooth); @@ -227,6 +254,11 @@ protected void onStop() { MainApp.instance().unbindService(serviceConnection); } + @Override + public void switchAllowed(boolean newState, FragmentActivity activity, PluginType type) { + confirmPumpPluginActivation(newState, activity, type); + } + @Override public boolean isInitialized() { return connectionService != null && alertService != null && connectionService.isPaired(); @@ -327,7 +359,7 @@ private void updatePumpTimeIfNeeded() throws Exception { setDateTimeMessage.setPumpTime(pumpTime); connectionService.requestMessage(setDateTimeMessage).await(); Notification notification = new Notification(Notification.INSIGHT_DATE_TIME_UPDATED, MainApp.gs(R.string.pump_time_updated), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } } @@ -395,8 +427,8 @@ private void fetchStatus() throws Exception { } lastUpdated = System.currentTimeMillis(); new Handler(Looper.getMainLooper()).post(() -> { - MainApp.bus().post(new EventLocalInsightUpdateGUI()); - MainApp.bus().post(new EventRefreshOverview("LocalInsightPlugin::fetchStatus")); + RxBus.INSTANCE.send(new EventLocalInsightUpdateGUI()); + RxBus.INSTANCE.send(new EventRefreshOverview("LocalInsightPlugin::fetchStatus")); }); } @@ -404,7 +436,7 @@ private void fetchLimitations() throws Exception { maximumBolusAmount = ParameterBlockUtil.readParameterBlock(connectionService, Service.CONFIGURATION, MaxBolusAmountBlock.class).getAmountLimitation(); maximumBasalAmount = ParameterBlockUtil.readParameterBlock(connectionService, Service.CONFIGURATION, MaxBasalAmountBlock.class).getAmountLimitation(); minimumBolusAmount = ParameterBlockUtil.readParameterBlock(connectionService, Service.CONFIGURATION, FactoryMinBolusAmountBlock.class).getAmountLimitation(); - minimumBasalAmount = ParameterBlockUtil.readParameterBlock(connectionService, Service.CONFIGURATION, FactoryMinBolusAmountBlock.class).getAmountLimitation(); + minimumBasalAmount = ParameterBlockUtil.readParameterBlock(connectionService, Service.CONFIGURATION, FactoryMinBasalAmountBlock.class).getAmountLimitation(); this.pumpDescription.basalMaximumRate = maximumBasalAmount; this.pumpDescription.basalMinimumRate = minimumBasalAmount; limitsFetched = true; @@ -413,11 +445,11 @@ private void fetchLimitations() throws Exception { @Override public PumpEnactResult setNewBasalProfile(Profile profile) { PumpEnactResult result = new PumpEnactResult(); - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + RxBus.INSTANCE.send(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; + Profile.ProfileValue basalValue = profile.getBasalValues()[i]; + Profile.ProfileValue nextValue = null; if (profile.getBasalValues().length > i + 1) nextValue = profile.getBasalValues()[i + 1]; BasalProfileBlock profileBlock = new BasalProfileBlock(); @@ -433,9 +465,9 @@ public PumpEnactResult setNewBasalProfile(Profile profile) { BRProfileBlock profileBlock = new BRProfile1Block(); profileBlock.setProfileBlocks(profileBlocks); ParameterBlockUtil.writeConfigurationBlock(connectionService, profileBlock); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.success = true; result.enacted = true; result.comment = MainApp.gs(R.string.virtualpump_resultok); @@ -447,17 +479,17 @@ public PumpEnactResult setNewBasalProfile(Profile profile) { } catch (AppLayerErrorException e) { log.info("Exception while setting profile: " + e.getClass().getCanonicalName() + " (" + e.getErrorCode() + ")"); Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = ExceptionTranslator.getString(e); } catch (InsightException e) { log.info("Exception while setting profile: " + e.getClass().getCanonicalName()); Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = ExceptionTranslator.getString(e); } catch (Exception e) { log.error("Exception while setting profile", e); Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); result.comment = ExceptionTranslator.getString(e); } return result; @@ -470,13 +502,13 @@ public boolean isThisProfileSet(Profile profile) { if (activeBasalProfile != BasalProfile.PROFILE_1) return false; for (int i = 0; i < profileBlocks.size(); i++) { BasalProfileBlock profileBlock = profileBlocks.get(i); - Profile.BasalValue basalValue = profile.getBasalValues()[i]; - Profile.BasalValue nextValue = null; + Profile.ProfileValue basalValue = profile.getBasalValues()[i]; + Profile.ProfileValue nextValue = null; if (profile.getBasalValues().length > i + 1) nextValue = profile.getBasalValues()[i + 1]; if (profileBlock.getDuration() * 60 != (nextValue != null ? nextValue.timeAsSeconds : 24 * 60 * 60) - basalValue.timeAsSeconds) return false; - if (Math.abs(profileBlock.getBasalAmount() - basalValue.value) > (basalValue.value > 5 ? 0.05 : 0.005)) + if (Math.abs(profileBlock.getBasalAmount() - basalValue.value) > (basalValue.value > 5 ? 0.051 : 0.0051)) return false; } return true; @@ -526,11 +558,11 @@ public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { result.enacted = true; Treatment t = new Treatment(); t.isSMB = detailedBolusInfo.isSMB; - final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.t = t; - bolusingEvent.status = MainApp.gs(R.string.insight_delivered, 0d, insulin); - bolusingEvent.percent = 0; - MainApp.bus().post(bolusingEvent); + final EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setT(t); + bolusingEvent.setStatus(MainApp.gs(R.string.insight_delivered, 0d, insulin)); + bolusingEvent.setPercent(0); + RxBus.INSTANCE.send(bolusingEvent); int trials = 0; InsightBolusID insightBolusID = new InsightBolusID(); insightBolusID.bolusID = bolusID; @@ -540,6 +572,15 @@ public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { detailedBolusInfo.date = insightBolusID.timestamp; detailedBolusInfo.source = Source.PUMP; detailedBolusInfo.pumpId = insightBolusID.id; + if (detailedBolusInfo.carbs > 0 && detailedBolusInfo.carbTime != 0) { + DetailedBolusInfo carbInfo = new DetailedBolusInfo(); + carbInfo.carbs = detailedBolusInfo.carbs; + carbInfo.date = detailedBolusInfo.date + detailedBolusInfo.carbTime * 60L * 1000L; + carbInfo.source = Source.USER; + TreatmentsPlugin.getPlugin().addToHistoryTreatment(carbInfo, false); + detailedBolusInfo.carbTime = 0; + detailedBolusInfo.carbs = 0; + } TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true); while (true) { synchronized ($bolusLock) { @@ -557,18 +598,18 @@ public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { } if (activeBolus != null) { trials = -1; - int percentBefore = bolusingEvent.percent; - bolusingEvent.percent = (int) (100D / activeBolus.getInitialAmount() * (activeBolus.getInitialAmount() - activeBolus.getRemainingAmount())); - bolusingEvent.status = MainApp.gs(R.string.insight_delivered, activeBolus.getInitialAmount() - activeBolus.getRemainingAmount(), activeBolus.getInitialAmount()); - if (percentBefore != bolusingEvent.percent) - MainApp.bus().post(bolusingEvent); + int percentBefore = bolusingEvent.getPercent(); + bolusingEvent.setPercent((int) (100D / activeBolus.getInitialAmount() * (activeBolus.getInitialAmount() - activeBolus.getRemainingAmount()))); + bolusingEvent.setStatus(MainApp.gs(R.string.insight_delivered, activeBolus.getInitialAmount() - activeBolus.getRemainingAmount(), activeBolus.getInitialAmount())); + if (percentBefore != bolusingEvent.getPercent()) + RxBus.INSTANCE.send(bolusingEvent); } else { synchronized ($bolusLock) { if (bolusCancelled || trials == -1 || trials++ >= 5) { if (!bolusCancelled) { - bolusingEvent.status = MainApp.gs(R.string.insight_delivered, insulin, insulin); - bolusingEvent.percent = 100; - MainApp.bus().post(bolusingEvent); + bolusingEvent.setStatus(MainApp.gs(R.string.insight_delivered, insulin, insulin)); + bolusingEvent.setPercent(100); + RxBus.INSTANCE.send(bolusingEvent); } break; } @@ -844,7 +885,6 @@ private PumpEnactResult cancelExtendedBolusOnly() { if (extendedBolus != null) { extendedBolus.durationInMinutes = (int) ((System.currentTimeMillis() - extendedBolus.date) / 60000); if (extendedBolus.durationInMinutes <= 0) { - ; final String _id = extendedBolus._id; if (NSUpload.isIdValid(_id)) NSUpload.removeCareportalEntryFromNS(_id); @@ -944,8 +984,18 @@ public JSONObject getJSONStatus(Profile profile, String profileName) { } @Override - public String deviceID() { - if (connectionService == null || alertService == null) return null; + public ManufacturerType manufacturer() { + return ManufacturerType.Roche; + } + + @Override + public PumpType model() { + return PumpType.AccuChekInsightBluetooth; + } + + @Override + public String serialNumber() { + if (connectionService == null || alertService == null) return "Unknown"; return connectionService.getPumpSystemIdentification().getSerialNumber(); } @@ -1127,7 +1177,7 @@ private void readHistory() { } catch (Exception e) { log.error("Exception while reading history", e); } - new Handler(Looper.getMainLooper()).post(() -> MainApp.bus().post(new EventRefreshOverview("LocalInsightPlugin::readHistory"))); + new Handler(Looper.getMainLooper()).post(() -> RxBus.INSTANCE.send(new EventRefreshOverview("LocalInsightPlugin::readHistory"))); } private void processHistoryEvents(String serial, List historyEvents) { @@ -1484,9 +1534,15 @@ private void logNote(long date, String note) { data.put("created_at", DateUtil.toISOString(date)); data.put("eventType", CareportalEvent.NOTE); data.put("notes", note); + CareportalEvent careportalEvent = new CareportalEvent(); + careportalEvent.date = date; + careportalEvent.source = Source.USER; + careportalEvent.eventType = CareportalEvent.NOTE; + careportalEvent.json = data.toString(); + MainApp.getDbHelper().createOrUpdate(careportalEvent); NSUpload.uploadCareportalEntryToNS(data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } @@ -1512,9 +1568,15 @@ private void uploadCareportalEvent(long date, String event) { if (!enteredBy.equals("")) data.put("enteredBy", enteredBy); data.put("created_at", DateUtil.toISOString(date)); data.put("eventType", event); + CareportalEvent careportalEvent = new CareportalEvent(); + careportalEvent.date = date; + careportalEvent.source = Source.USER; + careportalEvent.eventType = event; + careportalEvent.json = data.toString(); + MainApp.getDbHelper().createOrUpdate(careportalEvent); NSUpload.uploadCareportalEntryToNS(data); } catch (JSONException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } @@ -1549,7 +1611,7 @@ public Constraint applyExtendedBolusConstraints(Constraint insul public void onStateChanged(InsightState state) { if (state == InsightState.CONNECTED) { statusLoaded = false; - new Handler(Looper.getMainLooper()).post(() -> MainApp.bus().post(new EventDismissNotification(Notification.INSIGHT_TIMEOUT_DURING_HANDSHAKE))); + new Handler(Looper.getMainLooper()).post(() -> RxBus.INSTANCE.send(new EventDismissNotification(Notification.INSIGHT_TIMEOUT_DURING_HANDSHAKE))); } else if (state == InsightState.NOT_PAIRED) { connectionService.withdrawConnectionRequest(this); statusLoaded = false; @@ -1562,9 +1624,9 @@ public void onStateChanged(InsightState state) { activeTBR = null; activeBoluses = null; tbrOverNotificationBlock = null; - new Handler(Looper.getMainLooper()).post(() -> MainApp.bus().post(new EventRefreshOverview("LocalInsightPlugin::onStateChanged"))); + new Handler(Looper.getMainLooper()).post(() -> RxBus.INSTANCE.send(new EventRefreshOverview("LocalInsightPlugin::onStateChanged"))); } - new Handler(Looper.getMainLooper()).post(() -> MainApp.bus().post(new EventLocalInsightUpdateGUI())); + new Handler(Looper.getMainLooper()).post(() -> RxBus.INSTANCE.send(new EventLocalInsightUpdateGUI())); } @Override @@ -1575,11 +1637,17 @@ public void onPumpPaired() { @Override public void onTimeoutDuringHandshake() { Notification notification = new Notification(Notification.INSIGHT_TIMEOUT_DURING_HANDSHAKE, MainApp.gs(R.string.timeout_during_handshake), Notification.URGENT); - new Handler(Looper.getMainLooper()).post(() -> MainApp.bus().post(new EventNewNotification(notification))); + new Handler(Looper.getMainLooper()).post(() -> RxBus.INSTANCE.send(new EventNewNotification(notification))); } @Override public boolean canHandleDST() { return true; } + + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + } + } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java index c40cb99ed66..77be3ab03ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightAlertActivity.java @@ -5,8 +5,6 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; import android.text.Html; import android.view.View; import android.view.WindowManager; @@ -14,14 +12,17 @@ import android.widget.ImageView; import android.widget.TextView; +import androidx.core.content.ContextCompat; + import java.text.DecimalFormat; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.plugins.pump.insight.InsightAlertService; import info.nightscout.androidaps.plugins.pump.insight.descriptors.Alert; import info.nightscout.androidaps.plugins.pump.insight.descriptors.AlertStatus; -public class InsightAlertActivity extends AppCompatActivity { +public class InsightAlertActivity extends NoSplashAppCompatActivity { private Alert alert; private InsightAlertService alertService; @@ -40,7 +41,7 @@ public void onServiceConnected(ComponentName name, IBinder binder) { alertService.setAlertActivity(InsightAlertActivity.this); alert = alertService.getAlert(); if (alert == null) finish(); - update(alert); + else update(alert); } @Override @@ -50,7 +51,7 @@ public void onServiceDisconnected(ComponentName name) { }; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_insight_alert); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java index 7ea52ed2716..71450aaa0c9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingActivity.java @@ -10,11 +10,6 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -22,15 +17,21 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import java.util.ArrayList; import java.util.List; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.plugins.pump.insight.connection_service.InsightConnectionService; import info.nightscout.androidaps.plugins.pump.insight.descriptors.InsightState; import info.nightscout.androidaps.plugins.pump.insight.utils.ExceptionTranslator; -public class InsightPairingActivity extends AppCompatActivity implements InsightConnectionService.StateCallback, View.OnClickListener, InsightConnectionService.ExceptionCallback { +public class InsightPairingActivity extends NoSplashAppCompatActivity implements InsightConnectionService.StateCallback, View.OnClickListener, InsightConnectionService.ExceptionCallback { private boolean scanning; private LinearLayout deviceSearchSection; @@ -66,7 +67,7 @@ public void onServiceDisconnected(ComponentName name) { }; @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_insight_pairing); @@ -160,20 +161,25 @@ public void onStateChanged(InsightState state) { private void startBLScan() { if (!scanning) { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (!bluetoothAdapter.isEnabled()) bluetoothAdapter.enable(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); - intentFilter.addAction(BluetoothDevice.ACTION_FOUND); - registerReceiver(broadcastReceiver, intentFilter); - bluetoothAdapter.startDiscovery(); - scanning = true; + if (bluetoothAdapter != null) { + if (!bluetoothAdapter.isEnabled()) bluetoothAdapter.enable(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); + intentFilter.addAction(BluetoothDevice.ACTION_FOUND); + registerReceiver(broadcastReceiver, intentFilter); + bluetoothAdapter.startDiscovery(); + scanning = true; + } } } private void stopBLScan() { if (scanning) { unregisterReceiver(broadcastReceiver); - BluetoothAdapter.getDefaultAdapter().cancelDiscovery(); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (bluetoothAdapter != null) { + bluetoothAdapter.cancelDiscovery(); + } scanning = false; } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java index 8580b30dca2..e20fb2c94f0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/activities/InsightPairingInformationActivity.java @@ -5,15 +5,16 @@ import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; -import android.support.annotation.Nullable; -import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.TextView; +import androidx.annotation.Nullable; + import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.plugins.pump.insight.connection_service.InsightConnectionService; -public class InsightPairingInformationActivity extends AppCompatActivity { +public class InsightPairingInformationActivity extends NoSplashAppCompatActivity { private InsightConnectionService connectionService; @@ -57,7 +58,7 @@ public void onServiceDisconnected(ComponentName name) { }; @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_insight_pairing_information); serialNumber = findViewById(R.id.serial_number); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/HistoryReadingDirection.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/HistoryReadingDirection.java index 327304eb214..1bf9a2d165d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/HistoryReadingDirection.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/HistoryReadingDirection.java @@ -3,6 +3,6 @@ public enum HistoryReadingDirection { FORWARD, - BACKWARD; + BACKWARD } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java index 874c908ca59..f6b3dc588cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/app_layer/history/history_events/HistoryEvent.java @@ -3,8 +3,11 @@ import info.nightscout.androidaps.plugins.pump.insight.ids.HistoryEventIDs; import info.nightscout.androidaps.plugins.pump.insight.utils.BOCUtil; import info.nightscout.androidaps.plugins.pump.insight.utils.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class HistoryEvent implements Comparable { + private static final Logger log = LoggerFactory.getLogger(HistoryEvent.class); private int eventYear; private int eventMonth; @@ -22,10 +25,8 @@ public static HistoryEvent deserialize(ByteBuf byteBuf) { else { try { event = eventClass.newInstance(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); + } catch (IllegalAccessException | InstantiationException e) { + log.error("Unhandled exception", e); } } event.parseHeader(byteBuf); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java index e964c9fa202..cecb6317764 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/connection_service/InsightConnectionService.java @@ -8,7 +8,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.PowerManager; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertCategory.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertCategory.java index c2355f2b6a8..95c39b77a2a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertCategory.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertCategory.java @@ -5,5 +5,5 @@ public enum AlertCategory { REMINDER, MAINTENANCE, WARNING, - ERROR; + ERROR } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertStatus.java index b3842edfed5..3b855284627 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertStatus.java @@ -3,5 +3,5 @@ public enum AlertStatus { ACTIVE, - SNOOZED; + SNOOZED } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertType.java index b2c8331a49f..92a59bd7fcc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertType.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/AlertType.java @@ -27,5 +27,5 @@ public enum AlertType { MAINTENANCE_30, ERROR_6, ERROR_10, - ERROR_13; + ERROR_13 } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BasalProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BasalProfile.java index 89cc68cbd5e..298c2957dc5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BasalProfile.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BasalProfile.java @@ -6,5 +6,5 @@ public enum BasalProfile { PROFILE_2, PROFILE_3, PROFILE_4, - PROFILE_5; + PROFILE_5 } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BatteryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BatteryType.java index 0cd5373c93b..e0e1e2b69f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BatteryType.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BatteryType.java @@ -4,5 +4,5 @@ public enum BatteryType { ALKALI, LITHIUM, - NI_MH; + NI_MH } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BolusType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BolusType.java index 7af48fbfb7e..0625e5d5e73 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BolusType.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/BolusType.java @@ -4,5 +4,5 @@ public enum BolusType { STANDARD, EXTENDED, - MULTIWAVE; + MULTIWAVE } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/CartridgeType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/CartridgeType.java index 8c2f8783872..480e4d2a549 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/CartridgeType.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/CartridgeType.java @@ -3,5 +3,5 @@ public enum CartridgeType { PREFILLED, - SELF_FILLED; + SELF_FILLED } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/MessagePriority.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/MessagePriority.java index 10a47283a39..f24846f514f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/MessagePriority.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/MessagePriority.java @@ -4,5 +4,5 @@ public enum MessagePriority { NORMAL, HIGHER, - HIGHEST; + HIGHEST } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/OperatingMode.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/OperatingMode.java index d7453947158..85500f50df2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/OperatingMode.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/OperatingMode.java @@ -4,5 +4,5 @@ public enum OperatingMode { STARTED, STOPPED, - PAUSED; + PAUSED } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/SymbolStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/SymbolStatus.java index 567e6afb136..b68362692bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/SymbolStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/descriptors/SymbolStatus.java @@ -4,5 +4,5 @@ public enum SymbolStatus { FULL, LOW, - EMPTY; + EMPTY } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/events/EventLocalInsightUpdateGUI.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/events/EventLocalInsightUpdateGUI.kt new file mode 100644 index 00000000000..d89515fe561 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/events/EventLocalInsightUpdateGUI.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.insight.events + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventLocalInsightUpdateGUI : EventUpdateGui() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/PairingStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/PairingStatus.java index 52131e2d1e4..899425f2799 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/PairingStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/PairingStatus.java @@ -4,5 +4,5 @@ public enum PairingStatus { CONFIRMED, REJECTED, - PENDING; + PENDING } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/SatlError.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/SatlError.java index 7e04018dc80..ac0b4ebebe1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/SatlError.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/satl/SatlError.java @@ -14,5 +14,5 @@ public enum SatlError { WRONG_STATE, INVALID_MESSAGE_TYPE, INVALID_PAYLOAD_LENGTH, - NONE; + NONE } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/utils/ByteBuf.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/utils/ByteBuf.java index 107634acf9f..0e5f275949d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/utils/ByteBuf.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/utils/ByteBuf.java @@ -1,8 +1,8 @@ package info.nightscout.androidaps.plugins.pump.insight.utils; -import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; public class ByteBuf { @@ -45,13 +45,11 @@ public void putByte(byte b) { } - public void putBytes(byte b, int count) { for (int i = 0; i < count; i++) bytes[size++] = b; } - public byte[] getBytes(int position, int length) { byte[] copy = new byte[length]; System.arraycopy(bytes, position, copy, 0, length); @@ -68,7 +66,7 @@ public byte[] readBytes(int length) { return copy; } - public byte[] readBytes() { + byte[] readBytes() { return readBytes(size); } @@ -82,15 +80,14 @@ public void putBytes(byte[] bytes) { } - - public byte[] getBytesLE(int position, int length) { + private byte[] getBytesLE(int position, int length) { byte[] copy = new byte[length]; for (int i = 0; i < length; i++) copy[i] = bytes[length - 1 - i + position]; return copy; } - public byte[] getBytesLE(int length) { + private byte[] getBytesLE(int length) { return getBytesLE(0, length); } @@ -100,13 +97,13 @@ public byte[] readBytesLE(int length) { return copy; } - public void putBytesLE(byte[] bytes, int length) { + private void putBytesLE(byte[] bytes, int length) { for (int i = 0; i < length; i++) this.bytes[size + length - 1 - i] = bytes[i]; size += length; } - public void putBytesLE(byte[] bytes) { + void putBytesLE(byte[] bytes) { putBytesLE(bytes, bytes.length); } @@ -116,12 +113,11 @@ public void putByteBuf(ByteBuf byteBuf) { } - - public short getUInt8(int position) { + private short getUInt8(int position) { return (short) (bytes[position] & 0xFF); } - public short getUInt8() { + private short getUInt8() { return getUInt8(0); } @@ -136,13 +132,12 @@ public void putUInt8(short value) { } - public int getUInt16LE(int position) { return (bytes[position++] & 0xFF | - (bytes[position] & 0xFF) << 8); + (bytes[position] & 0xFF) << 8); } - public int getUInt16LE() { + private int getUInt16LE() { return getUInt16LE(0); } @@ -158,14 +153,13 @@ public void putUInt16LE(int i) { } - - public double getUInt16Decimal(int position) { + private double getUInt16Decimal(int position) { return new BigDecimal(getUInt16LE(position)) .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP) .doubleValue(); } - public double getUInt16Decimal() { + private double getUInt16Decimal() { return getUInt16Decimal(0); } @@ -183,14 +177,13 @@ public void putUInt16Decimal(double d) { } - - public double getUInt32Decimal100(int position) { + private double getUInt32Decimal100(int position) { return new BigDecimal(getUInt32LE(position)) .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP) .doubleValue(); } - public double getUInt32Decimal100() { + private double getUInt32Decimal100() { return getUInt32Decimal100(0); } @@ -208,14 +201,13 @@ public void putUInt32Decimal100(double d) { } - - public double getUInt32Decimal1000(int position) { + private double getUInt32Decimal1000(int position) { return new BigDecimal(getUInt32LE(position)) .divide(new BigDecimal(1000), 3, RoundingMode.HALF_UP) .doubleValue(); } - public double getUInt32Decimal1000() { + private double getUInt32Decimal1000() { return getUInt32Decimal1000(0); } @@ -233,9 +225,8 @@ public void putUInt32Decimal1000(double d) { } - - public short getShort(int position) { - return (short) (bytes[position++] << 8 | + private short getShort(int position) { + return (short) (bytes[position++] << 8 | bytes[position] & 0xFF); } @@ -255,15 +246,14 @@ public void putShort(short s) { } - - public long getUInt32LE(int position) { + private long getUInt32LE(int position) { return ((long) bytes[position++] & 0xFF) | ((long) bytes[position++] & 0xFF) << 8 | ((long) bytes[position++] & 0xFF) << 16 | ((long) bytes[position] & 0xFF) << 24; } - public long getUInt32LE() { + private long getUInt32LE() { return getUInt32LE(0); } @@ -281,18 +271,12 @@ public void putUInt32LE(long l) { } - - public String getUTF16(int position, int stringLength) { - try { - String string = new String(getBytes(position, stringLength * 2 + 2), "UTF-16LE"); - return string.substring(0, string.indexOf(new String(new char[] {0, 0}))); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return null; + private String getUTF16(int position, int stringLength) { + String string = new String(getBytes(position, stringLength * 2 + 2), StandardCharsets.UTF_16LE); + return string.substring(0, string.indexOf(new String(new char[]{0, 0}))); } - public String getUTF16(int stringLength) { + private String getUTF16(int stringLength) { return getUTF16(0, stringLength); } @@ -303,27 +287,17 @@ public String readUTF16(int stringLength) { } public void putUTF16(String string, int stringLength) { - try { - putBytes(string.getBytes("UTF-16LE"), stringLength * 2); - putBytes((byte) 0, 2); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } + putBytes(string.getBytes(StandardCharsets.UTF_16LE), stringLength * 2); + putBytes((byte) 0, 2); } - - public String getASCII(int position, int stringLength) { - try { - String string = new String(getBytes(position, stringLength + 1), "US-ASCII"); - return string.substring(0, string.indexOf(0)); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return null; + private String getASCII(int position, int stringLength) { + String string = new String(getBytes(position, stringLength + 1), StandardCharsets.US_ASCII); + return string.substring(0, string.indexOf(0)); } - public String getASCII(int stringLength) { + private String getASCII(int stringLength) { return getASCII(0, stringLength); } @@ -334,16 +308,11 @@ public String readASCII(int stringLength) { } public void putASCII(String string, int stringLength) { - try { - putBytes(string.getBytes("UTF-16LE"), stringLength * 2); - putBytes((byte) 0, 1); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } + putBytes(string.getBytes(StandardCharsets.UTF_16LE), stringLength * 2); + putBytes((byte) 0, 1); } - public boolean getBoolean(int position) { return getUInt16LE(position) == 75; } @@ -363,7 +332,6 @@ public void putBoolean(boolean bool) { } - public static ByteBuf from(byte[] bytes, int length) { ByteBuf byteBuf = new ByteBuf(length); byteBuf.putBytes(bytes, length); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java index 89d11845817..18964865d86 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java @@ -19,10 +19,14 @@ import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.InstanceId; +import info.nightscout.androidaps.utils.TimeChangeType; /** @@ -236,8 +240,18 @@ public JSONObject getJSONStatus(Profile profile, String profileName) { } @Override - public String deviceID() { - return "MDI"; + public ManufacturerType manufacturer() { + return ManufacturerType.AndroidAPS; + } + + @Override + public PumpType model() { + return PumpType.MDI; + } + + @Override + public String serialNumber() { + return InstanceId.INSTANCE.instanceId(); } @Override @@ -247,7 +261,7 @@ public PumpDescription getPumpDescription() { @Override public String shortStatus(boolean veryShort) { - return deviceID(); + return model().getModel(); } @Override @@ -265,4 +279,10 @@ public boolean canHandleDST() { return true; } + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + } + + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt new file mode 100644 index 00000000000..69e0e0bbe8a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt @@ -0,0 +1,317 @@ +package info.nightscout.androidaps.plugins.pump.medtronic + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.os.Handler +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventPumpStatusChanged +import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState +import info.nightscout.androidaps.plugins.pump.medtronic.dialog.MedtronicHistoryActivity +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpConfigurationChanged +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.queue.events.EventQueueChanged +import info.nightscout.androidaps.utils.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.medtronic_fragment.* +import org.slf4j.LoggerFactory + + +class MedtronicFragment : Fragment() { + private val log = LoggerFactory.getLogger(L.PUMP) + private var disposable: CompositeDisposable = CompositeDisposable() + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + init { + refreshLoop = Runnable { + activity?.runOnUiThread { updateGUI() } + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.medtronic_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + medtronic_pumpstatus.setBackgroundColor(MainApp.gc(R.color.colorInitializingBorder)) + + medtronic_rl_status.text = MainApp.gs(RileyLinkServiceState.NotStarted.getResourceId(RileyLinkTargetDevice.MedtronicPump)) + + medtronic_pump_status.setTextColor(Color.WHITE) + medtronic_pump_status.text = "{fa-bed}" + + medtronic_history.setOnClickListener { + if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { + startActivity(Intent(context, MedtronicHistoryActivity::class.java)) + } else { + MedtronicUtil.displayNotConfiguredDialog(context) + } + } + + medtronic_refresh.setOnClickListener { + if (!MedtronicUtil.getPumpStatus().verifyConfiguration()) { + MedtronicUtil.displayNotConfiguredDialog(context) + } else { + medtronic_refresh.isEnabled = false + MedtronicPumpPlugin.getPlugin().resetStatusState() + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("Clicked refresh", object : Callback() { + override fun run() { + activity?.runOnUiThread { medtronic_refresh?.isEnabled = true } + } + }) + } + } + + medtronic_stats.setOnClickListener { + if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { + startActivity(Intent(context, RileyLinkStatusActivity::class.java)) + } else { + MedtronicUtil.displayNotConfiguredDialog(context) + } + } + } + + @Synchronized + override fun onResume() { + super.onResume() + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + disposable += RxBus + .toObservable(EventRefreshButtonState::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ medtronic_refresh.isEnabled = it.newState }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventMedtronicDeviceStatusChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (L.isEnabled(L.PUMP)) + log.info("onStatusEvent(EventMedtronicDeviceStatusChange): {}", it) + setDeviceStatus() + }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventMedtronicPumpValuesChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventTempBasalChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventMedtronicPumpConfigurationChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (L.isEnabled(L.PUMP)) + log.debug("EventMedtronicPumpConfigurationChanged triggered") + MedtronicUtil.getPumpStatus().verifyConfiguration() + updateGUI() + }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventPumpStatusChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventQueueChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + loopHandler.removeCallbacks(refreshLoop) + } + + @Synchronized + private fun setDeviceStatus() { + val pumpStatus: MedtronicPumpStatus = MedtronicUtil.getPumpStatus() + pumpStatus.rileyLinkServiceState = checkStatusSet(pumpStatus.rileyLinkServiceState, + RileyLinkUtil.getServiceState()) as RileyLinkServiceState? + + val resourceId = pumpStatus.rileyLinkServiceState.getResourceId(RileyLinkTargetDevice.MedtronicPump) + val rileyLinkError = RileyLinkUtil.getError() + medtronic_rl_status.text = + when { + pumpStatus.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isConnecting -> "{fa-bluetooth-b spin} " + MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isError && rileyLinkError == null -> "{fa-bluetooth-b} " + MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isError && rileyLinkError != null -> "{fa-bluetooth-b} " + MainApp.gs(rileyLinkError.getResourceId(RileyLinkTargetDevice.MedtronicPump)) + else -> "{fa-bluetooth-b} " + MainApp.gs(resourceId) + } + medtronic_rl_status.setTextColor(if (rileyLinkError != null) Color.RED else Color.WHITE) + + pumpStatus.rileyLinkError = checkStatusSet(pumpStatus.rileyLinkError, RileyLinkUtil.getError()) as RileyLinkError? + + medtronic_errors.text = + pumpStatus.rileyLinkError?.let { + MainApp.gs(it.getResourceId(RileyLinkTargetDevice.MedtronicPump)) + } ?: "-" + + pumpStatus.pumpDeviceState = checkStatusSet(pumpStatus.pumpDeviceState, + MedtronicUtil.getPumpDeviceState()) as PumpDeviceState? + + when (pumpStatus.pumpDeviceState) { + null, + PumpDeviceState.Sleeping -> medtronic_pump_status.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name()); + PumpDeviceState.NeverContacted, + PumpDeviceState.WakingUp, + PumpDeviceState.PumpUnreachable, + PumpDeviceState.ErrorWhenCommunicating, + PumpDeviceState.TimeoutWhenCommunicating, + PumpDeviceState.InvalidConfiguration -> medtronic_pump_status.text = " " + MainApp.gs(pumpStatus.pumpDeviceState.resourceId) + PumpDeviceState.Active -> { + val cmd = MedtronicUtil.getCurrentCommand() + if (cmd == null) + medtronic_pump_status.text = " " + MainApp.gs(pumpStatus.pumpDeviceState.resourceId) + else { + log.debug("Command: " + cmd) + val cmdResourceId = cmd.resourceId + if (cmd == MedtronicCommandType.GetHistoryData) { + medtronic_pump_status.text = MedtronicUtil.frameNumber?.let { + MainApp.gs(cmdResourceId, MedtronicUtil.pageNumber, MedtronicUtil.frameNumber) + } + ?: MainApp.gs(R.string.medtronic_cmd_desc_get_history_request, MedtronicUtil.pageNumber) + } else { + medtronic_pump_status.text = " " + (cmdResourceId?.let { MainApp.gs(it) } + ?: cmd.getCommandDescription()) + } + } + } + else -> log.warn("Unknown pump state: " + pumpStatus.pumpDeviceState) + } + + val status = ConfigBuilderPlugin.getPlugin().commandQueue.spannedStatus() + if (status.toString() == "") { + medtronic_queue.visibility = View.GONE + } else { + medtronic_queue.visibility = View.VISIBLE + medtronic_queue.text = status + } + } + + + private fun checkStatusSet(object1: Any?, object2: Any?): Any? { + return if (object1 == null) { + object2 + } else { + if (object1 != object2) { + object2 + } else + object1 + } + } + + // GUI functions + @Synchronized + fun updateGUI() { + if (medtronic_rl_status == null) return + val plugin = MedtronicPumpPlugin.getPlugin() + val pumpStatus = MedtronicUtil.getPumpStatus() + + setDeviceStatus() + + // last connection + if (pumpStatus.lastConnection != 0L) { + val minAgo = DateUtil.minAgo(pumpStatus.lastConnection) + val min = (System.currentTimeMillis() - pumpStatus.lastConnection) / 1000 / 60 + if (pumpStatus.lastConnection + 60 * 1000 > System.currentTimeMillis()) { + medtronic_lastconnection.setText(R.string.combo_pump_connected_now) + medtronic_lastconnection.setTextColor(Color.WHITE) + } else if (pumpStatus.lastConnection + 30 * 60 * 1000 < System.currentTimeMillis()) { + + if (min < 60) { + medtronic_lastconnection.text = MainApp.gs(R.string.minago, min) + } else if (min < 1440) { + val h = (min / 60).toInt() + medtronic_lastconnection.text = (MainApp.gq(R.plurals.objective_hours, h, h) + " " + + MainApp.gs(R.string.ago)) + } else { + val h = (min / 60).toInt() + val d = h / 24 + // h = h - (d * 24); + medtronic_lastconnection.text = (MainApp.gq(R.plurals.objective_days, d, d) + " " + + MainApp.gs(R.string.ago)) + } + medtronic_lastconnection.setTextColor(Color.RED) + } else { + medtronic_lastconnection.text = minAgo + medtronic_lastconnection.setTextColor(Color.WHITE) + } + } + + // last bolus + val bolus = pumpStatus.lastBolusAmount + val bolusTime = pumpStatus.lastBolusTime + if (bolus != null && bolusTime != null) { + val agoMsc = System.currentTimeMillis() - pumpStatus.lastBolusTime.time + val bolusMinAgo = agoMsc.toDouble() / 60.0 / 1000.0 + val unit = MainApp.gs(R.string.insulin_unit_shortname) + val ago: String + if (agoMsc < 60 * 1000) { + ago = MainApp.gs(R.string.combo_pump_connected_now) + } else if (bolusMinAgo < 60) { + ago = DateUtil.minAgo(pumpStatus.lastBolusTime.time) + } else { + ago = DateUtil.hourAgo(pumpStatus.lastBolusTime.time) + } + medtronic_lastbolus.text = MainApp.gs(R.string.combo_last_bolus, bolus, unit, ago) + } else { + medtronic_lastbolus.text = "" + } + + // base basal rate + medtronic_basabasalrate.text = ("(" + pumpStatus.activeProfileName + ") " + + MainApp.gs(R.string.pump_basebasalrate, plugin.baseBasalRate)) + + medtronic_tempbasal.text = TreatmentsPlugin.getPlugin() + .getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "" + + // battery + if (MedtronicUtil.getBatteryType() == BatteryType.None || pumpStatus.batteryVoltage == null) { + medtronic_pumpstate_battery.text = "{fa-battery-" + pumpStatus.batteryRemaining / 25 + "} " + } else { + medtronic_pumpstate_battery.text = "{fa-battery-" + pumpStatus.batteryRemaining / 25 + "} " + pumpStatus.batteryRemaining + "%" + String.format(" (%.2f V)", pumpStatus.batteryVoltage) + } + SetWarnColor.setColorInverse(medtronic_pumpstate_battery, pumpStatus.batteryRemaining.toDouble(), 25.0, 10.0) + + // reservoir + medtronic_reservoir.text = MainApp.gs(R.string.reservoirvalue, pumpStatus.reservoirRemainingUnits, pumpStatus.reservoirFullUnits) + SetWarnColor.setColorInverse(medtronic_reservoir, pumpStatus.reservoirRemainingUnits, 50.0, 20.0) + + medtronic_errors.text = pumpStatus.errorInfo + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java new file mode 100644 index 00000000000..6813f95202a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java @@ -0,0 +1,1629 @@ +package info.nightscout.androidaps.plugins.pump.medtronic; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +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.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.common.ManufacturerType; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.activities.ErrorHelperActivity; +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ResetRileyLinkConfigurationTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.ui.MedtronicUIComm; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.ui.MedtronicUITask; +import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfileEntry; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BasalProfileStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCustomActionType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicStatusRefreshType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventRefreshButtonState; +import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.TimeChangeType; + +import static info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.sendNotification; + +/** + * Created by andy on 23.04.18. + * + * @author Andy Rozman (andy.rozman@gmail.com) + */ +public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + protected static MedtronicPumpPlugin plugin = null; + private RileyLinkMedtronicService medtronicService; + private MedtronicPumpStatus pumpStatusLocal = null; + private MedtronicUIComm medtronicUIComm = new MedtronicUIComm(); + + // variables for handling statuses and history + private boolean firstRun = true; + private boolean isRefresh = false; + private boolean isBasalProfileInvalid = false; + private boolean basalProfileChanged = false; + private Map statusRefreshMap = new HashMap<>(); + private boolean isInitialized = false; + private MedtronicHistoryData medtronicHistoryData; + private MedtronicCommunicationManager medtronicCommunicationManager; + private PumpHistoryEntry lastPumpHistoryEntry; + + public static boolean isBusy = false; + private List busyTimestamps = new ArrayList<>(); + private boolean sentIdToFirebase = false; + private boolean hasTimeDateOrTimeZoneChanged = false; + + + private MedtronicPumpPlugin() { + + super(new PluginDescription() // + .mainType(PluginType.PUMP) // + .fragmentClass(MedtronicFragment.class.getName()) // + .pluginName(R.string.medtronic_name) // + .shortName(R.string.medtronic_name_short) // + .preferencesId(R.xml.pref_medtronic).description(R.string.description_pump_medtronic), // + PumpType.Medtronic_522_722 // we default to most basic model, correct model from config is loaded later + ); + + displayConnectionMessages = false; + + medtronicHistoryData = new MedtronicHistoryData(); + + serviceConnection = new ServiceConnection() { + + public void onServiceDisconnected(ComponentName name) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkMedtronicService is disconnected"); + medtronicService = null; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkMedtronicService is connected"); + RileyLinkMedtronicService.LocalBinder mLocalBinder = (RileyLinkMedtronicService.LocalBinder) service; + medtronicService = mLocalBinder.getServiceInstance(); + + new Thread(() -> { + + for (int i = 0; i < 20; i++) { + SystemClock.sleep(5000); + + if (MedtronicUtil.getPumpStatus() != null) { + if (isLoggingEnabled()) + LOG.debug("Starting Medtronic-RileyLink service"); + if (MedtronicUtil.getPumpStatus().setNotInPreInit()) { + break; + } + } + } + }).start(); + } + }; + } + + + public static MedtronicPumpPlugin getPlugin() { + if (plugin == null) + plugin = new MedtronicPumpPlugin(); + return plugin; + } + + + private String getLogPrefix() { + return "MedtronicPumpPlugin::"; + } + + + public MedtronicHistoryData getMedtronicHistoryData() { + return this.medtronicHistoryData; + } + + + @Override + public void initPumpStatusData() { + + this.pumpStatusLocal = new MedtronicPumpStatus(pumpDescription); + MedtronicUtil.setPumpStatus(pumpStatusLocal); + + pumpStatusLocal.lastConnection = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + pumpStatusLocal.lastDataTime = new LocalDateTime(pumpStatusLocal.lastConnection); + pumpStatusLocal.previousConnection = pumpStatusLocal.lastConnection; + + pumpStatusLocal.refreshConfiguration(); + + if (isLoggingEnabled()) + LOG.debug("initPumpStatusData: {}", this.pumpStatusLocal); + + this.pumpStatus = pumpStatusLocal; + + // this is only thing that can change, by being configured + pumpDescription.maxTempAbsolute = (pumpStatusLocal.maxBasal != null) ? pumpStatusLocal.maxBasal : 35.0d; + + // set first Medtronic Pump Start + if (!SP.contains(MedtronicConst.Statistics.FirstPumpStart)) { + SP.putLong(MedtronicConst.Statistics.FirstPumpStart, System.currentTimeMillis()); + } + + migrateSettings(); + + } + + + private void migrateSettings() { + + if ("US (916 MHz)".equals(SP.getString(MedtronicConst.Prefs.PumpFrequency, null))) { + SP.putString(MedtronicConst.Prefs.PumpFrequency, MainApp.gs(R.string.key_medtronic_pump_frequency_us_ca)); + } + + String encoding = SP.getString(MedtronicConst.Prefs.Encoding, null); + + if ("RileyLink 4b6b Encoding".equals(encoding)) { + SP.putString(MedtronicConst.Prefs.Encoding, MainApp.gs(R.string.key_medtronic_pump_encoding_4b6b_rileylink)); + } + + if ("Local 4b6b Encoding".equals(encoding)) { + SP.putString(MedtronicConst.Prefs.Encoding, MainApp.gs(R.string.key_medtronic_pump_encoding_4b6b_local)); + } + } + + + public void onStartCustomActions() { + + // check status every minute (if any status needs refresh we send readStatus command) + new Thread(() -> { + + do { + SystemClock.sleep(60000); + + if (this.isInitialized) { + + Map statusRefresh = workWithStatusRefresh( + StatusRefreshAction.GetData, null, null); + + if (doWeHaveAnyStatusNeededRefereshing(statusRefresh)) { + if (!ConfigBuilderPlugin.getPlugin().getCommandQueue().statusInQueue()) { + ConfigBuilderPlugin.getPlugin().getCommandQueue() + .readStatus("Scheduled Status Refresh", null); + } + } + + clearBusyQueue(); + } + + } while (serviceRunning); + + }).start(); + } + + + public Class getServiceClass() { + return RileyLinkMedtronicService.class; + } + + + @Override + public String deviceID() { + return "Medtronic"; + } + + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return false; + } + + + @Override + public boolean canHandleDST() { + return false; + } + + + // Pump Plugin + + private boolean isServiceSet() { + return medtronicService != null; + } + + + @Override + public boolean isInitialized() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug("MedtronicPumpPlugin::isInitialized"); + return isServiceSet() && isInitialized; + } + + + @Override + public boolean isBusy() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug("MedtronicPumpPlugin::isBusy"); + + if (isServiceSet()) { + + if (isBusy) + return true; + + if (busyTimestamps.size() > 0) { + + clearBusyQueue(); + + if (busyTimestamps.size() > 0) { + return true; + } + } + } + + return false; + } + + + private synchronized void clearBusyQueue() { + + if (busyTimestamps.size() == 0) { + return; + } + + Set deleteFromQueue = new HashSet<>(); + + for (Long busyTimestamp : busyTimestamps) { + + if (System.currentTimeMillis() > busyTimestamp) { + deleteFromQueue.add(busyTimestamp); + } + } + + if (deleteFromQueue.size() == busyTimestamps.size()) { + busyTimestamps.clear(); + setEnableCustomAction(MedtronicCustomActionType.ClearBolusBlock, false); + } + + if (deleteFromQueue.size() > 0) { + busyTimestamps.removeAll(deleteFromQueue); + } + + } + + + @Override + public boolean isConnected() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug("MedtronicPumpPlugin::isConnected"); + return isServiceSet() && medtronicService.isInitialized(); + } + + + @Override + public boolean isConnecting() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug("MedtronicPumpPlugin::isConnecting"); + return !isServiceSet() || !medtronicService.isInitialized(); + } + + + @Override + public void getPumpStatus() { + + getMDTPumpStatus(); + + if (firstRun) { + initializePump(!isRefresh); + } else { + refreshAnyStatusThatNeedsToBeRefreshed(); + } + + RxBus.INSTANCE.send(new EventMedtronicPumpValuesChanged()); + } + + + void resetStatusState() { + firstRun = true; + isRefresh = true; + } + + + private boolean isPumpNotReachable() { + + RileyLinkServiceState rileyLinkServiceState = MedtronicUtil.getServiceState(); + + if (rileyLinkServiceState == null) { + LOG.error("RileyLink unreachable. RileyLinkServiceState is null."); + return false; + } + + if (rileyLinkServiceState != RileyLinkServiceState.PumpConnectorReady // + && rileyLinkServiceState != RileyLinkServiceState.RileyLinkReady // + && rileyLinkServiceState != RileyLinkServiceState.TuneUpDevice) { + LOG.error("RileyLink unreachable."); + return false; + } + + return (!medtronicCommunicationManager.isDeviceReachable()); + } + + + private void refreshAnyStatusThatNeedsToBeRefreshed() { + + Map statusRefresh = workWithStatusRefresh(StatusRefreshAction.GetData, null, + null); + + if (!doWeHaveAnyStatusNeededRefereshing(statusRefresh)) { + return; + } + + boolean resetTime = false; + + if (isPumpNotReachable()) { + if (isLoggingEnabled()) + LOG.error("Pump unreachable."); + MedtronicUtil.sendNotification(MedtronicNotificationType.PumpUnreachable); + + return; + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + + + if (hasTimeDateOrTimeZoneChanged) { + + checkTimeAndOptionallySetTime(); + + // read time if changed, set new time + hasTimeDateOrTimeZoneChanged = false; + } + + + // execute + Set refreshTypesNeededToReschedule = new HashSet<>(); + + for (Map.Entry refreshType : statusRefresh.entrySet()) { + + if (refreshType.getValue() > 0 && System.currentTimeMillis() > refreshType.getValue()) { + + switch (refreshType.getKey()) { + case PumpHistory: { + readPumpHistory(); + } + break; + + case PumpTime: { + checkTimeAndOptionallySetTime(); + refreshTypesNeededToReschedule.add(refreshType.getKey()); + resetTime = true; + } + break; + + case BatteryStatus: + case RemainingInsulin: { + medtronicUIComm.executeCommand(refreshType.getKey().getCommandType()); + refreshTypesNeededToReschedule.add(refreshType.getKey()); + resetTime = true; + } + break; + + case Configuration: { + medtronicUIComm.executeCommand(refreshType.getKey().getCommandType()); + resetTime = true; + } + break; + } + } + + // reschedule + for (MedtronicStatusRefreshType refreshType2 : refreshTypesNeededToReschedule) { + scheduleNextRefresh(refreshType2); + } + + } + + if (resetTime) + pumpStatusLocal.setLastCommunicationToNow(); + + } + + + private boolean doWeHaveAnyStatusNeededRefereshing(Map statusRefresh) { + + for (Map.Entry refreshType : statusRefresh.entrySet()) { + + if (refreshType.getValue() > 0 && System.currentTimeMillis() > refreshType.getValue()) { + return true; + } + } + + return hasTimeDateOrTimeZoneChanged; + } + + + private void setRefreshButtonEnabled(boolean enabled) { + RxBus.INSTANCE.send(new EventRefreshButtonState(enabled)); + } + + + private void initializePump(boolean realInit) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "initializePump - start"); + + if (medtronicCommunicationManager == null) { + medtronicCommunicationManager = MedtronicCommunicationManager.getInstance(); + medtronicCommunicationManager.setDoWakeUpBeforeCommand(false); + } + + setRefreshButtonEnabled(false); + + getMDTPumpStatus(); + + if (isRefresh) { + if (isPumpNotReachable()) { + if (isLoggingEnabled()) + LOG.error(getLogPrefix() + "initializePump::Pump unreachable."); + MedtronicUtil.sendNotification(MedtronicNotificationType.PumpUnreachable); + + setRefreshButtonEnabled(true); + + return; + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + } + + // model (once) + if (MedtronicUtil.getMedtronicPumpModel() == null) { + medtronicUIComm.executeCommand(MedtronicCommandType.PumpModel); + } else { + if (pumpStatusLocal.medtronicDeviceType != MedtronicUtil.getMedtronicPumpModel()) { + if (isLoggingEnabled()) + LOG.warn(getLogPrefix() + "Configured pump is not the same as one detected."); + MedtronicUtil.sendNotification(MedtronicNotificationType.PumpTypeNotSame); + } + } + + this.pumpState = PumpDriverState.Connected; + + // time (1h) + checkTimeAndOptionallySetTime(); + + readPumpHistory(); + + // remaining insulin (>50 = 4h; 50-20 = 1h; 15m) + medtronicUIComm.executeCommand(MedtronicCommandType.GetRemainingInsulin); + scheduleNextRefresh(MedtronicStatusRefreshType.RemainingInsulin, 10); + + // remaining power (1h) + medtronicUIComm.executeCommand(MedtronicCommandType.GetBatteryStatus); + scheduleNextRefresh(MedtronicStatusRefreshType.BatteryStatus, 20); + + // configuration (once and then if history shows config changes) + medtronicUIComm.executeCommand(MedtronicCommandType.getSettings(MedtronicUtil.getMedtronicPumpModel())); + + // read profile (once, later its controlled by isThisProfileSet method) + getBasalProfiles(); + + int errorCount = medtronicUIComm.getInvalidResponsesCount(); + + if (errorCount >= 5) { + if (isLoggingEnabled()) + LOG.error("Number of error counts was 5 or more. Starting tunning."); + setRefreshButtonEnabled(true); + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + return; + } + + pumpStatusLocal.setLastCommunicationToNow(); + setRefreshButtonEnabled(true); + + if (!isRefresh) { + pumpState = PumpDriverState.Initialized; + } + + if (!sentIdToFirebase) { + Bundle params = new Bundle(); + params.putString("version", BuildConfig.VERSION); + MainApp.getFirebaseAnalytics().logEvent("MedtronicPumpInit", params); + + sentIdToFirebase = true; + } + + isInitialized = true; + // this.pumpState = PumpDriverState.Initialized; + + this.firstRun = false; + } + + private void getBasalProfiles() { + + MedtronicUITask medtronicUITask = medtronicUIComm.executeCommand(MedtronicCommandType.GetBasalProfileSTD); + + if (medtronicUITask.getResponseType() == MedtronicUIResponseType.Error) { + medtronicUIComm.executeCommand(MedtronicCommandType.GetBasalProfileSTD); + } + } + + + @Override + public boolean isThisProfileSet(Profile profile) { + MedtronicPumpStatus mdtPumpStatus = getMDTPumpStatus(); + LOG.debug("isThisProfileSet: basalInitalized={}", mdtPumpStatus.basalProfileStatus); + + if (!isInitialized) + return true; + + if (mdtPumpStatus.basalProfileStatus == BasalProfileStatus.NotInitialized) { + // this shouldn't happen, but if there was problem we try again + getBasalProfiles(); + return isProfileSame(profile); + } else if (mdtPumpStatus.basalProfileStatus == BasalProfileStatus.ProfileChanged) { + return false; + } else { + + } + + + return (getMDTPumpStatus().basalProfileStatus != BasalProfileStatus.ProfileOK) || isProfileSame(profile); + } + + + private boolean isProfileSame(Profile profile) { + + boolean invalid = false; + Double[] basalsByHour = getMDTPumpStatus().basalsByHour; + PumpType pumpType = getMDTPumpStatus().getPumpType(); + + if (isLoggingEnabled()) + LOG.debug("Current Basals (h): " + + (basalsByHour == null ? "null" : BasalProfile.getProfilesByHourToString(basalsByHour))); + + // int index = 0; + + if (basalsByHour == null) + return true; // we don't want to set profile again, unless we are sure + + StringBuilder stringBuilder = new StringBuilder("Requested Basals (h): "); + + for (Profile.ProfileValue basalValue : profile.getBasalValues()) { + + double basalValueValue = pumpType.determineCorrectBasalSize(basalValue.value); + + int hour = basalValue.timeAsSeconds / (60 * 60); + + if (!MedtronicUtil.isSame(basalsByHour[hour], basalValueValue)) { + invalid = true; + } + + stringBuilder.append(String.format(Locale.ENGLISH, "%.3f", basalValueValue)); + stringBuilder.append(" "); + } + + if (isLoggingEnabled()) { + LOG.debug(stringBuilder.toString()); + + if (!invalid) { + LOG.debug("Basal profile is same as AAPS one."); + } else { + LOG.debug("Basal profile on Pump is different than the AAPS one."); + } + } + + return (!invalid); + } + + + @Override + public long lastDataTime() { + getMDTPumpStatus(); + + if (pumpStatusLocal.lastConnection != 0) { + return pumpStatusLocal.lastConnection; + } + + return System.currentTimeMillis(); + } + + + @Override + public double getBaseBasalRate() { + return getMDTPumpStatus().getBasalProfileForHour(); + } + + + @Override + public double getReservoirLevel() { + return getMDTPumpStatus().reservoirRemainingUnits; + } + + + @Override + public int getBatteryLevel() { + return getMDTPumpStatus().batteryRemaining; + } + + + private MedtronicPumpStatus getMDTPumpStatus() { + if (pumpStatusLocal == null) { + // FIXME I don't know why this happens + if (isLoggingEnabled()) + LOG.warn("!!!! Reset Pump Status Local"); + pumpStatusLocal = MedtronicUtil.getPumpStatus(); + } + + return pumpStatusLocal; + } + + + protected void triggerUIChange() { + RxBus.INSTANCE.send(new EventMedtronicPumpValuesChanged()); + } + + private BolusDeliveryType bolusDeliveryType = BolusDeliveryType.Idle; + + private enum BolusDeliveryType { + Idle, // + DeliveryPrepared, // + Delivering, // + CancelDelivery + } + + + private void checkTimeAndOptionallySetTime() { + + if (isLoggingEnabled()) + LOG.info("MedtronicPumpPlugin::checkTimeAndOptionallySetTime - Start"); + + setRefreshButtonEnabled(false); + + if (isPumpNotReachable()) { + LOG.debug("MedtronicPumpPlugin::checkTimeAndOptionallySetTime - Pump Unreachable."); + setRefreshButtonEnabled(true); + return; + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + + medtronicUIComm.executeCommand(MedtronicCommandType.GetRealTimeClock); + + ClockDTO clock = MedtronicUtil.getPumpTime(); + + if (clock == null) { // retry + medtronicUIComm.executeCommand(MedtronicCommandType.GetRealTimeClock); + + clock = MedtronicUtil.getPumpTime(); + } + + if (clock == null) + return; + + int timeDiff = Math.abs(clock.timeDifference); + + if (timeDiff > 20) { + + if ((clock.localDeviceTime.getYear() <= 2015) || (timeDiff <= 24 * 60 * 60)) { + + if (isLoggingEnabled()) + LOG.info("MedtronicPumpPlugin::checkTimeAndOptionallySetTime - Time difference is {} s. Set time on pump.", timeDiff); + + medtronicUIComm.executeCommand(MedtronicCommandType.SetRealTimeClock); + + if (clock.timeDifference == 0) { + Notification notification = new Notification(Notification.INSIGHT_DATE_TIME_UPDATED, MainApp.gs(R.string.pump_time_updated), Notification.INFO, 60); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } + } else { + if ((clock.localDeviceTime.getYear() > 2015)) { + LOG.error("MedtronicPumpPlugin::checkTimeAndOptionallySetTime - Time difference over 24h requested [diff={}]. Doing nothing.", timeDiff); + sendNotification(MedtronicNotificationType.TimeChangeOver24h); + } + } + + } else { + if (isLoggingEnabled()) + LOG.info("MedtronicPumpPlugin::checkTimeAndOptionallySetTime - Time difference is {} s. Do nothing.", timeDiff); + } + + scheduleNextRefresh(MedtronicStatusRefreshType.PumpTime, 0); + } + + + @NonNull + protected PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { + + LOG.info("MedtronicPumpPlugin::deliverBolus - {}", BolusDeliveryType.DeliveryPrepared); + + setRefreshButtonEnabled(false); + + MedtronicPumpStatus mdtPumpStatus = getMDTPumpStatus(); + + if (detailedBolusInfo.insulin > mdtPumpStatus.reservoirRemainingUnits) { + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_bolus_could_not_be_delivered_no_insulin, + mdtPumpStatus.reservoirRemainingUnits, + detailedBolusInfo.insulin)); + } + + bolusDeliveryType = BolusDeliveryType.DeliveryPrepared; + + if (isPumpNotReachable()) { + LOG.debug("MedtronicPumpPlugin::deliverBolus - Pump Unreachable."); + return setNotReachable(true, false); + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + + if (bolusDeliveryType == BolusDeliveryType.CancelDelivery) { + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Delivery Canceled."); + return setNotReachable(true, true); + } + + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Starting wait period."); + + int sleepTime = SP.getInt(MedtronicConst.Prefs.BolusDelay, 10) * 1000; + + SystemClock.sleep(sleepTime); + + if (bolusDeliveryType == BolusDeliveryType.CancelDelivery) { + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Delivery Canceled, before wait period."); + return setNotReachable(true, true); + } + + // LOG.debug("MedtronicPumpPlugin::deliverBolus - End wait period. Start delivery"); + + try { + + bolusDeliveryType = BolusDeliveryType.Delivering; + + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Start delivery"); + + MedtronicUITask responseTask = medtronicUIComm.executeCommand(MedtronicCommandType.SetBolus, + detailedBolusInfo.insulin); + + Boolean response = (Boolean) responseTask.returnData; + + setRefreshButtonEnabled(true); + + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Response: {}", response); + + if (response) { + + if (bolusDeliveryType == BolusDeliveryType.CancelDelivery) { + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Delivery Canceled after Bolus started."); + + new Thread(() -> { + // Looper.prepare(); + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Show dialog - before"); + SystemClock.sleep(2000); + // LOG.debug("MedtronicPumpPlugin::deliverBolus - Show dialog. Context: " + // + MainApp.instance().getApplicationContext()); + + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", MainApp.gs(R.string.medtronic_cmd_cancel_bolus_not_supported)); + i.putExtra("title", MainApp.gs(R.string.combo_warning)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + + }).start(); + } + + long now = System.currentTimeMillis(); + + detailedBolusInfo.date = now; + detailedBolusInfo.deliverAt = now; // not sure about that one + + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, true); + + // we subtract insulin, exact amount will be visible with next remainingInsulin update. + getMDTPumpStatus().reservoirRemainingUnits -= detailedBolusInfo.insulin; + + incrementStatistics(detailedBolusInfo.isSMB ? MedtronicConst.Statistics.SMBBoluses + : MedtronicConst.Statistics.StandardBoluses); + + + // calculate time for bolus and set driver to busy for that time + int bolusTime = (int) (detailedBolusInfo.insulin * 42.0d); + long time = now + (bolusTime * 1000); + + this.busyTimestamps.add(time); + setEnableCustomAction(MedtronicCustomActionType.ClearBolusBlock, true); + + return new PumpEnactResult().success(true) // + .enacted(true) // + .bolusDelivered(detailedBolusInfo.insulin) // + .carbsDelivered(detailedBolusInfo.carbs); + + } else { + return new PumpEnactResult() // + .success(bolusDeliveryType == BolusDeliveryType.CancelDelivery) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_bolus_could_not_be_delivered)); + } + + } finally { + finishAction("Bolus"); + this.bolusDeliveryType = BolusDeliveryType.Idle; + } + } + + + private PumpEnactResult setNotReachable(boolean isBolus, boolean success) { + setRefreshButtonEnabled(true); + + if (isBolus) { + bolusDeliveryType = BolusDeliveryType.Idle; + } + + if (success) { + return new PumpEnactResult() // + .success(true) // + .enacted(false); + } else { + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_pump_status_pump_unreachable)); + } + } + + + public void stopBolusDelivering() { + + this.bolusDeliveryType = BolusDeliveryType.CancelDelivery; + + // if (isLoggingEnabled()) + // LOG.warn("MedtronicPumpPlugin::deliverBolus - Stop Bolus Delivery."); + } + + + private void incrementStatistics(String statsKey) { + long currentCount = SP.getLong(statsKey, 0L); + currentCount++; + SP.putLong(statsKey, currentCount); + } + + + // if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged), + // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, + boolean enforceNew) { + + setRefreshButtonEnabled(false); + + if (isPumpNotReachable()) { + + setRefreshButtonEnabled(true); + + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_pump_status_pump_unreachable)); + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + + getMDTPumpStatus(); + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: rate: {}, duration={}", absoluteRate, durationInMinutes); + + // read current TBR + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent == null) { + if (isLoggingEnabled()) + LOG.warn(getLogPrefix() + "setTempBasalAbsolute - Could not read current TBR, canceling operation."); + finishAction("TBR"); + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.medtronic_cmd_cant_read_tbr)); + } else { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: Current Basal: duration: {} min, rate={}", + tbrCurrent.getDurationMinutes(), tbrCurrent.getInsulinRate()); + } + + if (!enforceNew) { + + if (MedtronicUtil.isSame(tbrCurrent.getInsulinRate(), absoluteRate)) { + + boolean sameRate = true; + if (MedtronicUtil.isSame(0.0d, absoluteRate) && durationInMinutes > 0) { + // if rate is 0.0 and duration>0 then the rate is not the same + sameRate = false; + } + + if (sameRate) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - No enforceNew and same rate. Exiting."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + } + } + // if not the same rate, we cancel and start new + } + + // if TBR is running we will cancel it. + if (tbrCurrent.getInsulinRate() != 0.0f && tbrCurrent.getDurationMinutes() > 0) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - TBR running - so canceling it."); + + // CANCEL + + MedtronicUITask responseTask2 = medtronicUIComm.executeCommand(MedtronicCommandType.CancelTBR); + + Boolean response = (Boolean) responseTask2.returnData; + + if (response) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - Current TBR cancelled."); + } else { + if (isLoggingEnabled()) + LOG.error(getLogPrefix() + "setTempBasalAbsolute - Cancel TBR failed."); + + finishAction("TBR"); + + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.medtronic_cmd_cant_cancel_tbr_stop_op)); + } + } + + // now start new TBR + MedtronicUITask responseTask = medtronicUIComm.executeCommand(MedtronicCommandType.SetTemporaryBasal, + absoluteRate, durationInMinutes); + + Boolean response = (Boolean) responseTask.returnData; + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - setTBR. Response: " + response); + + if (response) { + // FIXME put this into UIPostProcessor + pumpStatusLocal.tempBasalStart = new Date(); + pumpStatusLocal.tempBasalAmount = absoluteRate; + pumpStatusLocal.tempBasalLength = durationInMinutes; + + TemporaryBasal tempStart = new TemporaryBasal() // + .date(System.currentTimeMillis()) // + .duration(durationInMinutes) // + .absolute(absoluteRate) // + .source(Source.USER); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStart); + + incrementStatistics(MedtronicConst.Statistics.TBRsSet); + + finishAction("TBR"); + + return new PumpEnactResult().success(true).enacted(true) // + .absolute(absoluteRate).duration(durationInMinutes); + + } else { + finishAction("TBR"); + + return new PumpEnactResult().success(false).enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_tbr_could_not_be_delivered)); + } + + } + + + @Override + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, + boolean enforceNew) { + if (percent == 0) { + return setTempBasalAbsolute(0.0d, durationInMinutes, profile, enforceNew); + } else { + double absoluteValue = profile.getBasal() * (percent / 100.0d); + getMDTPumpStatus(); + absoluteValue = pumpStatusLocal.pumpType.determineCorrectBasalSize(absoluteValue); + LOG.warn("setTempBasalPercent [MedtronicPumpPlugin] - You are trying to use setTempBasalPercent with percent other then 0% (%d). This will start setTempBasalAbsolute, with calculated value (%.3f). Result might not be 100% correct.", percent, absoluteValue); + return setTempBasalAbsolute(absoluteValue, durationInMinutes, profile, enforceNew); + } + } + + + private void finishAction(String overviewKey) { + + if (overviewKey != null) + RxBus.INSTANCE.send(new EventRefreshOverview(overviewKey)); + + triggerUIChange(); + + setRefreshButtonEnabled(true); + } + + + private void readPumpHistory() { + +// if (isLoggingEnabled()) +// LOG.error(getLogPrefix() + "readPumpHistory WIP."); + + readPumpHistoryLogic(); + + scheduleNextRefresh(MedtronicStatusRefreshType.PumpHistory); + + if (medtronicHistoryData.hasRelevantConfigurationChanged()) { + scheduleNextRefresh(MedtronicStatusRefreshType.Configuration, -1); + } + + if (medtronicHistoryData.hasPumpTimeChanged()) { + scheduleNextRefresh(MedtronicStatusRefreshType.PumpTime, -1); + } + + if (this.getMDTPumpStatus().basalProfileStatus != BasalProfileStatus.NotInitialized + && medtronicHistoryData.hasBasalProfileChanged()) { + medtronicHistoryData.processLastBasalProfileChange(getMDTPumpStatus()); + } + + PumpDriverState previousState = this.pumpState; + + if (medtronicHistoryData.isPumpSuspended()) { + this.pumpState = PumpDriverState.Suspended; + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "isPumpSuspended: true"); + } else { + if (previousState == PumpDriverState.Suspended) { + this.pumpState = PumpDriverState.Ready; + } + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "isPumpSuspended: false"); + } + + medtronicHistoryData.processNewHistoryData(); + + this.medtronicHistoryData.finalizeNewHistoryRecords(); + // this.medtronicHistoryData.setLastHistoryRecordTime(this.lastPumpHistoryEntry.atechDateTime); + + } + + + private void readPumpHistoryLogic() { + + LocalDateTime targetDate = null; + + if (lastPumpHistoryEntry == null) { + + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "readPumpHistoryLogic(): lastPumpHistoryEntry: null"); + + Long lastPumpHistoryEntryTime = getLastPumpEntryTime(); + + LocalDateTime timeMinus36h = new LocalDateTime(); + timeMinus36h = timeMinus36h.minusHours(36); + medtronicHistoryData.setIsInInit(true); + + if (lastPumpHistoryEntryTime == 0L) { + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "readPumpHistoryLogic(): lastPumpHistoryEntryTime: 0L - targetDate: " + + targetDate); + targetDate = timeMinus36h; + } else { + // LocalDateTime lastHistoryRecordTime = DateTimeUtil.toLocalDateTime(lastPumpHistoryEntryTime); + + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "readPumpHistoryLogic(): lastPumpHistoryEntryTime: {} - targetDate: {}", + lastPumpHistoryEntryTime, targetDate); + + medtronicHistoryData.setLastHistoryRecordTime(lastPumpHistoryEntryTime); + + LocalDateTime lastHistoryRecordTime = DateTimeUtil.toLocalDateTime(lastPumpHistoryEntryTime); + + lastHistoryRecordTime = lastHistoryRecordTime.minusHours(12); // we get last 12 hours of history to + // determine pump state + // (we don't process that data), we process only + + if (timeMinus36h.isAfter(lastHistoryRecordTime)) { + targetDate = timeMinus36h; + } + + targetDate = (timeMinus36h.isAfter(lastHistoryRecordTime) ? timeMinus36h : lastHistoryRecordTime); + + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "readPumpHistoryLogic(): targetDate: " + targetDate); + } + } else { + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "readPumpHistoryLogic(): lastPumpHistoryEntry: not null - {}", + MedtronicUtil.gsonInstance.toJson(lastPumpHistoryEntry)); + medtronicHistoryData.setIsInInit(false); + // medtronicHistoryData.setLastHistoryRecordTime(lastPumpHistoryEntry.atechDateTime); + + // targetDate = lastPumpHistoryEntry.atechDateTime; + } + + LOG.debug("HST: Target Date: {}", targetDate); + + MedtronicUITask responseTask2 = medtronicUIComm.executeCommand(MedtronicCommandType.GetHistoryData, + lastPumpHistoryEntry, targetDate); + + LOG.debug("HST: After task"); + + PumpHistoryResult historyResult = (PumpHistoryResult) responseTask2.returnData; + + LOG.debug("HST: History Result: {}", historyResult.toString()); + + PumpHistoryEntry latestEntry = historyResult.getLatestEntry(); + + if (isLoggingEnabled()) + LOG.debug(getLogPrefix() + "Last entry: " + latestEntry); + + if (latestEntry == null) // no new history to read + return; + + this.lastPumpHistoryEntry = latestEntry; + SP.putLong(MedtronicConst.Statistics.LastPumpHistoryEntry, latestEntry.atechDateTime); + + LOG.debug("HST: History: valid={}, unprocessed={}", historyResult.validEntries.size(), + historyResult.unprocessedEntries.size()); + + this.medtronicHistoryData.addNewHistory(historyResult); + this.medtronicHistoryData.filterNewEntries(); + + // determine if first run, if yes detrmine how much of update do we need + // first run: + // get last hiostory entry, if not there download 1.5 days of data + // - there: check if last entry is older than 1.5 days + // - yes: download 1.5 days + // - no: download with last entry + // - not there: download 1.5 days + // + // upload all new entries to NightScout (TBR, Bolus) + // determine pump status + // + // save last entry + // + // not first run: + // update to last entry + // - save + // - determine pump status + + // + + } + + private Long getLastPumpEntryTime() { + Long lastPumpEntryTime = SP.getLong(MedtronicConst.Statistics.LastPumpHistoryEntry, 0L); + + try { + LocalDateTime localDateTime = DateTimeUtil.toLocalDateTime(lastPumpEntryTime); + + if (localDateTime.getYear() != (new GregorianCalendar().get(Calendar.YEAR))) { + LOG.warn("Saved LastPumpHistoryEntry was invalid. Year was not the same."); + return 0L; + } + + return lastPumpEntryTime; + + } catch (Exception ex) { + LOG.warn("Saved LastPumpHistoryEntry was invalid."); + return 0L; + } + + } + + + private void scheduleNextRefresh(MedtronicStatusRefreshType refreshType) { + scheduleNextRefresh(refreshType, 0); + } + + + private void scheduleNextRefresh(MedtronicStatusRefreshType refreshType, int additionalTimeInMinutes) { + switch (refreshType) { + + case RemainingInsulin: { + double remaining = pumpStatusLocal.reservoirRemainingUnits; + int min; + if (remaining > 50) + min = 4 * 60; + else if (remaining > 20) + min = 60; + else + min = 15; + + workWithStatusRefresh(StatusRefreshAction.Add, refreshType, getTimeInFutureFromMinutes(min)); + } + break; + + case PumpTime: + case Configuration: + case BatteryStatus: + case PumpHistory: { + workWithStatusRefresh(StatusRefreshAction.Add, refreshType, + getTimeInFutureFromMinutes(refreshType.getRefreshTime() + additionalTimeInMinutes)); + } + break; + } + } + + private enum StatusRefreshAction { + Add, // + GetData + } + + + private synchronized Map workWithStatusRefresh(StatusRefreshAction action, // + MedtronicStatusRefreshType statusRefreshType, // + Long time) { + + switch (action) { + + case Add: { + statusRefreshMap.put(statusRefreshType, time); + return null; + } + + case GetData: { + return new HashMap<>(statusRefreshMap); + } + + default: + return null; + + } + + } + + + private long getTimeInFutureFromMinutes(int minutes) { + return System.currentTimeMillis() + getTimeInMs(minutes); + } + + + private long getTimeInMs(int minutes) { + return minutes * 60 * 1000L; + } + + + private TempBasalPair readTBR() { + MedtronicUITask responseTask = medtronicUIComm.executeCommand(MedtronicCommandType.ReadTemporaryBasal); + + if (responseTask.hasData()) { + TempBasalPair tbr = (TempBasalPair) responseTask.returnData; + + // we sometimes get rate returned even if TBR is no longer running + if (tbr.getDurationMinutes() == 0) { + tbr.setInsulinRate(0.0d); + } + + return tbr; + } else { + return null; + } + } + + + @Override + public PumpEnactResult cancelTempBasal(boolean enforceNew) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - started"); + + if (isPumpNotReachable()) { + + setRefreshButtonEnabled(true); + + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_pump_status_pump_unreachable)); + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + setRefreshButtonEnabled(false); + + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent != null) { + if (tbrCurrent.getInsulinRate() == 0.0f && tbrCurrent.getDurationMinutes() == 0) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - TBR already canceled."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + } + } else { + if (isLoggingEnabled()) + LOG.warn(getLogPrefix() + "cancelTempBasal - Could not read currect TBR, canceling operation."); + finishAction("TBR"); + return new PumpEnactResult().success(false).enacted(false) + .comment(MainApp.gs(R.string.medtronic_cmd_cant_read_tbr)); + } + + MedtronicUITask responseTask2 = medtronicUIComm.executeCommand(MedtronicCommandType.CancelTBR); + + Boolean response = (Boolean) responseTask2.returnData; + + finishAction("TBR"); + + if (response) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR successful."); + + TemporaryBasal tempBasal = new TemporaryBasal() // + .date(System.currentTimeMillis()) // + .duration(0) // + .source(Source.USER); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + + return new PumpEnactResult().success(true).enacted(true) // + .isTempCancel(true); + } else { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR failed."); + + return new PumpEnactResult().success(response).enacted(response) // + .comment(MainApp.gs(R.string.medtronic_cmd_cant_cancel_tbr)); + } + } + + @Override + public ManufacturerType manufacturer() { + return getMDTPumpStatus().pumpType.getManufacturer(); + } + + @Override + public PumpType model() { + return getMDTPumpStatus().pumpType; + } + + @Override + public String serialNumber() { + return getMDTPumpStatus().serialNumber; + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setNewBasalProfile"); + + // this shouldn't be needed, but let's do check if profile setting we are setting is same as current one + if (isProfileSame(profile)) { + return new PumpEnactResult() // + .success(true) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_basal_profile_not_set_is_same)); + } + + setRefreshButtonEnabled(false); + + if (isPumpNotReachable()) { + + setRefreshButtonEnabled(true); + + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_pump_status_pump_unreachable)); + } + + MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); + + BasalProfile basalProfile = convertProfileToMedtronicProfile(profile); + + String profileInvalid = isProfileValid(basalProfile); + + if (profileInvalid != null) { + return new PumpEnactResult() // + .success(false) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_set_profile_pattern_overflow, profileInvalid)); + } + + MedtronicUITask responseTask = medtronicUIComm.executeCommand(MedtronicCommandType.SetBasalProfileSTD, + basalProfile); + + Boolean response = (Boolean) responseTask.returnData; + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "Basal Profile was set: " + response); + + if (response) { + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + + return new PumpEnactResult().success(true).enacted(true); + } else { + Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + + return new PumpEnactResult().success(response).enacted(response) // + .comment(MainApp.gs(R.string.medtronic_cmd_basal_profile_could_not_be_set)); + } + } + + + private String isProfileValid(BasalProfile basalProfile) { + + StringBuilder stringBuilder = new StringBuilder(); + + MedtronicPumpStatus pumpStatus = getMDTPumpStatus(); + + if (pumpStatus.maxBasal == null) + return null; + + for (BasalProfileEntry profileEntry : basalProfile.getEntries()) { + + if (profileEntry.rate > pumpStatus.maxBasal) { + stringBuilder.append(profileEntry.startTime.toString("HH:mm")); + stringBuilder.append("="); + stringBuilder.append(profileEntry.rate); + } + } + + return stringBuilder.length() == 0 ? null : stringBuilder.toString(); + } + + + @NonNull + private BasalProfile convertProfileToMedtronicProfile(Profile profile) { + + MedtronicPumpStatus pumpStatus = getMDTPumpStatus(); + + PumpType pumpType = pumpStatus.pumpType; + + BasalProfile basalProfile = new BasalProfile(); + + for (int i = 0; i < 24; i++) { + double rate = profile.getBasalTimeFromMidnight(i * 60 * 60); + + double v = pumpType.determineCorrectBasalSize(rate); + + BasalProfileEntry basalEntry = new BasalProfileEntry(v, i, 0); + basalProfile.addEntry(basalEntry); + + } + + basalProfile.generateRawDataFromEntries(); + + return basalProfile; + } + + // OPERATIONS not supported by Pump or Plugin + + private List customActions = null; + + private CustomAction customActionWakeUpAndTune = new CustomAction(R.string.medtronic_custom_action_wake_and_tune, + MedtronicCustomActionType.WakeUpAndTune); + + private CustomAction customActionClearBolusBlock = new CustomAction( + R.string.medtronic_custom_action_clear_bolus_block, MedtronicCustomActionType.ClearBolusBlock, false); + + private CustomAction customActionResetRLConfig = new CustomAction( + R.string.medtronic_custom_action_reset_rileylink, MedtronicCustomActionType.ResetRileyLinkConfiguration, true); + + + @Override + public List getCustomActions() { + + if (customActions == null) { + this.customActions = Arrays.asList(customActionWakeUpAndTune, // + customActionClearBolusBlock, // + customActionResetRLConfig); + } + + return this.customActions; + } + + + @Override + public void executeCustomAction(CustomActionType customActionType) { + + MedtronicCustomActionType mcat = (MedtronicCustomActionType) customActionType; + + switch (mcat) { + + case WakeUpAndTune: { + if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + } else { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration)); + i.putExtra("title", MainApp.gs(R.string.combo_warning)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } + } + break; + + case ClearBolusBlock: { + this.busyTimestamps.clear(); + this.customActionClearBolusBlock.setEnabled(false); + refreshCustomActionsList(); + } + break; + + case ResetRileyLinkConfiguration: { + ServiceTaskExecutor.startTask(new ResetRileyLinkConfigurationTask()); + } + break; + + default: + break; + } + + } + + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + if (isLoggingEnabled()) + LOG.warn(getLogPrefix() + "Time, Date and/or TimeZone changed. "); + + this.hasTimeDateOrTimeZoneChanged = true; + } + + + public void setEnableCustomAction(MedtronicCustomActionType customAction, boolean isEnabled) { + + if (customAction == MedtronicCustomActionType.ClearBolusBlock) { + this.customActionClearBolusBlock.setEnabled(isEnabled); + } else if (customAction == MedtronicCustomActionType.ResetRileyLinkConfiguration) { + this.customActionResetRLConfig.setEnabled(isEnabled); + } + + refreshCustomActionsList(); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java new file mode 100644 index 00000000000..78e7fb44f75 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicCommunicationManager.java @@ -0,0 +1,965 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm; + +import android.os.SystemClock; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RFSpyResponse; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioPacket; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RadioResponse; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.WakeAndTuneTask; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RawHistoryPage; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkLongMessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.CarelinkShortMessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.GetHistoryPageCarelinkMessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.MessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PacketType; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpAckMessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpMessage; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Original file created by geoff on 5/30/16. + *

+ * Split into 2 implementations, so that we can split it by target device. - Andy + * This was mostly rewritten from Original version, and lots of commands and + * functionality added. + */ +public class MedtronicCommunicationManager extends RileyLinkCommunicationManager { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + private static final int MAX_COMMAND_TRIES = 3; + private static final int DEFAULT_TIMEOUT = 2000; + private static final long RILEYLINK_TIMEOUT = 15 * 60 * 1000; // 15 min + + static MedtronicCommunicationManager medtronicCommunicationManager; + String errorMessage; + private MedtronicConverter medtronicConverter; + private boolean debugSetCommands = false; + + private MedtronicPumpHistoryDecoder pumpHistoryDecoder; + private boolean doWakeUpBeforeCommand = true; + + + public MedtronicCommunicationManager(RFSpy rfspy) { + super(rfspy); + medtronicCommunicationManager = this; + this.medtronicConverter = new MedtronicConverter(); + this.pumpHistoryDecoder = new MedtronicPumpHistoryDecoder(); + MedtronicUtil.getPumpStatus().previousConnection = SP.getLong( + RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + } + + + public static MedtronicCommunicationManager getInstance() { + return medtronicCommunicationManager; + } + + + @Override + protected void configurePumpSpecificSettings() { + pumpStatus = MedtronicUtil.getPumpStatus(); + } + + + @Override + public E createResponseMessage(byte[] payload, Class clazz) { + PumpMessage pumpMessage = new PumpMessage(payload); + return (E) pumpMessage; + } + + + public void setDoWakeUpBeforeCommand(boolean doWakeUp) { + this.doWakeUpBeforeCommand = doWakeUp; + } + + + public boolean isDeviceReachable() { + return isDeviceReachable(false); + } + + + /** + * We do actual wakeUp and compare PumpModel with currently selected one. If returned model is + * not Unknown, pump is reachable. + * + * @return + */ + public boolean isDeviceReachable(boolean canPreventTuneUp) { + + PumpDeviceState state = MedtronicUtil.getPumpDeviceState(); + + if (state != PumpDeviceState.PumpUnreachable) + MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp); + + for (int retry = 0; retry < 5; retry++) { + + if (isLogEnabled()) + LOG.debug("isDeviceReachable. Waking pump... " + (retry != 0 ? " (retry " + retry + ")" : "")); + + boolean connected = connectToDevice(); + + if (connected) + return true; + + SystemClock.sleep(1000); + + } + + if (state != PumpDeviceState.PumpUnreachable) + MedtronicUtil.setPumpDeviceState(PumpDeviceState.PumpUnreachable); + + if (!canPreventTuneUp) { + + long diff = System.currentTimeMillis() - MedtronicUtil.getPumpStatus().lastConnection; + + if (diff > RILEYLINK_TIMEOUT) { + ServiceTaskExecutor.startTask(new WakeAndTuneTask()); + } + } + + return false; + } + + + private boolean connectToDevice() { + + PumpDeviceState state = MedtronicUtil.getPumpDeviceState(); + + byte[] pumpMsgContent = createPumpMessageContent(RLMessageType.ReadSimpleData); // simple + RFSpyResponse rfSpyResponse = rfspy.transmitThenReceive(new RadioPacket(pumpMsgContent), (byte) 0, (byte) 200, + (byte) 0, (byte) 0, 25000, (byte) 0); + if (isLogEnabled()) + LOG.info("wakeup: raw response is " + ByteUtil.shortHexString(rfSpyResponse.getRaw())); + + if (rfSpyResponse.wasTimeout()) { + LOG.error("isDeviceReachable. Failed to find pump (timeout)."); + } else if (rfSpyResponse.looksLikeRadioPacket()) { + RadioResponse radioResponse = new RadioResponse(); + + try { + + radioResponse.init(rfSpyResponse.getRaw()); + + if (radioResponse.isValid()) { + + PumpMessage pumpResponse = createResponseMessage(radioResponse.getPayload(), PumpMessage.class); + + if (!pumpResponse.isValid()) { + LOG.warn("Response is invalid ! [interrupted={}, timeout={}]", rfSpyResponse.wasInterrupted(), + rfSpyResponse.wasTimeout()); + } else { + + // radioResponse.rssi; + Object dataResponse = medtronicConverter.convertResponse(MedtronicCommandType.PumpModel, + pumpResponse.getRawContent()); + + MedtronicDeviceType pumpModel = (MedtronicDeviceType) dataResponse; + boolean valid = (pumpModel != MedtronicDeviceType.Unknown_Device); + + if (MedtronicUtil.getMedtronicPumpModel() == null && valid) { + MedtronicUtil.setMedtronicPumpModel(pumpModel); + } + + if (isLogEnabled()) + LOG.debug("isDeviceReachable. PumpModel is {} - Valid: {} (rssi={})", pumpModel.name(), valid, + radioResponse.rssi); + + if (valid) { + if (state == PumpDeviceState.PumpUnreachable) + MedtronicUtil.setPumpDeviceState(PumpDeviceState.WakingUp); + else + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + rememberLastGoodDeviceCommunicationTime(); + + return true; + + } else { + if (state != PumpDeviceState.PumpUnreachable) + MedtronicUtil.setPumpDeviceState(PumpDeviceState.PumpUnreachable); + } + + } + + } else { + LOG.warn("isDeviceReachable. Failed to parse radio response: " + + ByteUtil.shortHexString(rfSpyResponse.getRaw())); + } + + } catch (RileyLinkCommunicationException e) { + LOG.warn("isDeviceReachable. Failed to decode radio response: " + + ByteUtil.shortHexString(rfSpyResponse.getRaw())); + } + + } else { + LOG.warn("isDeviceReachable. Unknown response: " + ByteUtil.shortHexString(rfSpyResponse.getRaw())); + } + + return false; + } + + + @Override + public boolean tryToConnectToDevice() { + return isDeviceReachable(true); + } + + + private PumpMessage runCommandWithArgs(PumpMessage msg) throws RileyLinkCommunicationException { + + if (debugSetCommands) + LOG.debug("Run command with Args: "); + + PumpMessage rval; + PumpMessage shortMessage = makePumpMessage(msg.commandType, new CarelinkShortMessageBody(new byte[]{0})); + // look for ack from short message + PumpMessage shortResponse = sendAndListen(shortMessage); + if (shortResponse.commandType == MedtronicCommandType.CommandACK) { + if (debugSetCommands) + LOG.debug("Run command with Args: Got ACK response"); + + rval = sendAndListen(msg); + if (debugSetCommands) + LOG.debug("2nd Response: {}", rval); + + return rval; + } else { + if (isLogEnabled()) + LOG.error("runCommandWithArgs: Pump did not ack Attention packet"); + return new PumpMessage("No ACK after Attention packet."); + } + } + + + private PumpMessage runCommandWithFrames(MedtronicCommandType commandType, List> frames) + throws RileyLinkCommunicationException { + + if (isLogEnabled()) + LOG.debug("Run command with Frames: {}", commandType.name()); + + PumpMessage rval = null; + PumpMessage shortMessage = makePumpMessage(commandType, new CarelinkShortMessageBody(new byte[]{0})); + // look for ack from short message + PumpMessage shortResponse = sendAndListen(shortMessage); + + if (shortResponse.commandType != MedtronicCommandType.CommandACK) { + if (isLogEnabled()) + LOG.error("runCommandWithFrames: Pump did not ack Attention packet"); + + return new PumpMessage("No ACK after start message."); + } else { + if (isLogEnabled()) + LOG.debug("Run command with Frames: Got ACK response for Attention packet"); + } + + int frameNr = 1; + + for (List frame : frames) { + + byte[] frameData = MedtronicUtil.createByteArray(frame); + + // LOG.debug("Frame {} data:\n{}", frameNr, ByteUtil.getCompactString(frameData)); + + PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(frameData)); + + rval = sendAndListen(msg); + + // LOG.debug("PumpResponse: " + rval); + + if (rval.commandType != MedtronicCommandType.CommandACK) { + LOG.error("runCommandWithFrames: Pump did not ACK frame #{}", frameNr); + + LOG.error("Run command with Frames FAILED (command={}, response={})", commandType.name(), + rval.toString()); + + return new PumpMessage("No ACK after frame #" + frameNr); + } else { + if (isLogEnabled()) + LOG.debug("Run command with Frames: Got ACK response for frame #{}", (frameNr)); + } + + frameNr++; + } + + return rval; + + } + + + public PumpHistoryResult getPumpHistory(PumpHistoryEntry lastEntry, LocalDateTime targetDate) { + + PumpHistoryResult pumpTotalResult = new PumpHistoryResult(lastEntry, targetDate == null ? null + : DateTimeUtil.toATechDate(targetDate)); + + if (doWakeUpBeforeCommand) + wakeUp(receiverDeviceAwakeForMinutes, false); + + if (isLogEnabled()) + LOG.debug("Current command: " + MedtronicUtil.getCurrentCommand()); + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Active); + boolean doneWithError = false; + + for (int pageNumber = 0; pageNumber < 5; pageNumber++) { + + RawHistoryPage rawHistoryPage = new RawHistoryPage(); + // wakeUp(receiverDeviceAwakeForMinutes, false); + PumpMessage getHistoryMsg = makePumpMessage(MedtronicCommandType.GetHistoryData, + new GetHistoryPageCarelinkMessageBody(pageNumber)); + + if (isLogEnabled()) + LOG.info("getPumpHistory: Page {}", pageNumber); + // LOG.info("getPumpHistoryPage("+pageNumber+"): "+ByteUtil.shortHexString(getHistoryMsg.getTxData())); + // Ask the pump to transfer history (we get first frame?) + + PumpMessage firstResponse = null; + boolean failed = false; + + MedtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, null); + + for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) { + + try { + firstResponse = runCommandWithArgs(getHistoryMsg); + failed = false; + break; + } catch (RileyLinkCommunicationException e) { + if (isLogEnabled()) + LOG.error("First call for PumpHistory failed (retry={})", retries); + failed = true; + } + } + + if (failed) { + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + return pumpTotalResult; + } + + // LOG.info("getPumpHistoryPage("+pageNumber+"): " + ByteUtil.shortHexString(firstResponse.getContents())); + + PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody()); + GetHistoryPageCarelinkMessageBody currentResponse = new GetHistoryPageCarelinkMessageBody(firstResponse + .getMessageBody().getTxData()); + int expectedFrameNum = 1; + boolean done = false; + // while (expectedFrameNum == currentResponse.getFrameNumber()) { + + int failures = 0; + while (!done) { + // examine current response for problems. + byte[] frameData = currentResponse.getFrameData(); + if ((frameData != null) && (frameData.length > 0) + && currentResponse.getFrameNumber() == expectedFrameNum) { + // success! got a frame. + if (frameData.length != 64) { + if (isLogEnabled()) + LOG.warn("Expected frame of length 64, got frame of length " + frameData.length); + // but append it anyway? + } + // handle successful frame data + rawHistoryPage.appendData(currentResponse.getFrameData()); + // RileyLinkMedtronicService.getInstance().announceProgress(((100 / 16) * + // currentResponse.getFrameNumber() + 1)); + MedtronicUtil.setCurrentCommand(MedtronicCommandType.GetHistoryData, pageNumber, + currentResponse.getFrameNumber()); + + if (isLogEnabled()) + LOG.info("getPumpHistory: Got frame {} of Page {}", currentResponse.getFrameNumber(), pageNumber); + // Do we need to ask for the next frame? + if (expectedFrameNum < 16) { // This number may not be correct for pumps other than 522/722 + expectedFrameNum++; + } else { + done = true; // successful completion + } + } else { + if (frameData == null) { + if (isLogEnabled()) + LOG.error("null frame data, retrying"); + } else if (currentResponse.getFrameNumber() != expectedFrameNum) { + if (isLogEnabled()) + LOG.warn("Expected frame number {}, received {} (retrying)", expectedFrameNum, + currentResponse.getFrameNumber()); + } else if (frameData.length == 0) { + if (isLogEnabled()) + LOG.warn("Frame has zero length, retrying"); + } + failures++; + if (failures == 6) { + if (isLogEnabled()) + LOG.error( + "getPumpHistory: 6 failures in attempting to download frame {} of page {}, giving up.", + expectedFrameNum, pageNumber); + done = true; // failure completion. + doneWithError = true; + } + } + + if (!done) { + // ask for next frame + PumpMessage nextMsg = null; + + for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) { + + try { + nextMsg = sendAndListen(ackMsg); + break; + } catch (RileyLinkCommunicationException e) { + if (isLogEnabled()) + LOG.error("Problem acknowledging frame response. (retry={})", retries); + } + } + + if (nextMsg != null) + currentResponse = new GetHistoryPageCarelinkMessageBody(nextMsg.getMessageBody().getTxData()); + else { + if (isLogEnabled()) + LOG.error("We couldn't acknowledge frame from pump, aborting operation."); + } + } + } + + if (rawHistoryPage.getLength() != 1024) { + if (isLogEnabled()) + LOG.warn("getPumpHistory: short page. Expected length of 1024, found length of " + + rawHistoryPage.getLength()); + doneWithError = true; + } + + if (!rawHistoryPage.isChecksumOK()) { + if (isLogEnabled()) + LOG.error("getPumpHistory: checksum is wrong"); + doneWithError = true; + } + + if (doneWithError) { + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + return pumpTotalResult; + } + + rawHistoryPage.dumpToDebug(); + + List medtronicHistoryEntries = pumpHistoryDecoder + .processPageAndCreateRecords(rawHistoryPage); + + if (isLogEnabled()) + LOG.debug("getPumpHistory: Found {} history entries.", medtronicHistoryEntries.size()); + + pumpTotalResult.addHistoryEntries(medtronicHistoryEntries, pageNumber); + + if (isLogEnabled()) + LOG.debug("getPumpHistory: Search status: Search finished: {}", pumpTotalResult.isSearchFinished()); + + if (pumpTotalResult.isSearchFinished()) { + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + return pumpTotalResult; + } + } + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + return pumpTotalResult; + + } + + + public String getErrorResponse() { + return this.errorMessage; + } + + + @Override + public byte[] createPumpMessageContent(RLMessageType type) { + switch (type) { + case PowerOn: + return MedtronicUtil.buildCommandPayload(MedtronicCommandType.RFPowerOn, // + new byte[]{2, 1, (byte) receiverDeviceAwakeForMinutes}); // maybe this is better FIXME + + case ReadSimpleData: + return MedtronicUtil.buildCommandPayload(MedtronicCommandType.PumpModel, null); + } + return new byte[0]; + } + + + private PumpMessage makePumpMessage(MedtronicCommandType messageType, byte[] body) { + return makePumpMessage(messageType, body == null ? new CarelinkShortMessageBody() + : new CarelinkShortMessageBody(body)); + } + + + private PumpMessage makePumpMessage(MedtronicCommandType messageType) { + return makePumpMessage(messageType, (byte[]) null); + } + + + private PumpMessage makePumpMessage(MedtronicCommandType messageType, MessageBody messageBody) { + PumpMessage msg = new PumpMessage(); + msg.init(PacketType.Carelink, rileyLinkServiceData.pumpIDBytes, messageType, messageBody); + return msg; + } + + + private PumpMessage sendAndGetResponse(MedtronicCommandType commandType) throws RileyLinkCommunicationException { + + return sendAndGetResponse(commandType, null, DEFAULT_TIMEOUT); + } + + + /** + * Main wrapper method for sending data - (for getting responses) + * + * @param commandType + * @param bodyData + * @param timeoutMs + * @return + */ + private PumpMessage sendAndGetResponse(MedtronicCommandType commandType, byte[] bodyData, int timeoutMs) + throws RileyLinkCommunicationException { + // wakeUp + if (doWakeUpBeforeCommand) + wakeUp(receiverDeviceAwakeForMinutes, false); + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Active); + + // create message + PumpMessage msg; + + if (bodyData == null) + msg = makePumpMessage(commandType); + else + msg = makePumpMessage(commandType, bodyData); + + // send and wait for response + PumpMessage response = sendAndListen(msg, timeoutMs); + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + return response; + } + + + private PumpMessage sendAndListen(RLMessage msg) throws RileyLinkCommunicationException { + return sendAndListen(msg, 4000); // 2000 + } + + + // All pump communications go through this function. + private PumpMessage sendAndListen(RLMessage msg, int timeout_ms) throws RileyLinkCommunicationException { + return sendAndListen(msg, timeout_ms, PumpMessage.class); + } + + + private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType) { + + return sendAndGetResponseWithCheck(commandType, null); + } + + + private Object sendAndGetResponseWithCheck(MedtronicCommandType commandType, byte[] bodyData) { + + if (isLogEnabled()) + LOG.debug("getDataFromPump: {}", commandType); + + for (int retries = 0; retries < MAX_COMMAND_TRIES; retries++) { + + try { + PumpMessage response = null; + + response = sendAndGetResponse(commandType, bodyData, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries)); + + String check = checkResponseContent(response, commandType.commandDescription, + commandType.expectedLength); + + if (check == null) { + + Object dataResponse = medtronicConverter.convertResponse(commandType, response.getRawContent()); + + if (dataResponse != null) { + this.errorMessage = null; + if (isLogEnabled()) + LOG.debug("Converted response for {} is {}.", commandType.name(), dataResponse); + + return dataResponse; + } else { + this.errorMessage = "Error decoding response."; + } + } else { + this.errorMessage = check; + // return null; + } + + } catch (RileyLinkCommunicationException e) { + if (isLogEnabled()) + LOG.warn("Error getting response from RileyLink (error={}, retry={})", e.getMessage(), retries + 1); + } + + } + + return null; + } + + + private String checkResponseContent(PumpMessage response, String method, int expectedLength) { + + if (!response.isValid()) { + String responseData = String.format("%s: Invalid response.", method); + if (isLogEnabled()) + LOG.warn(responseData); + return responseData; + } + + byte[] contents = response.getRawContent(); + + if (contents != null) { + if (contents.length >= expectedLength) { + LOG.trace("{}: Content: {}", method, ByteUtil.shortHexString(contents)); + return null; + + } else { + String responseData = String.format( + "%s: Cannot return data. Data is too short [expected=%s, received=%s].", method, "" + + expectedLength, "" + contents.length); + + if (isLogEnabled()) + LOG.warn(responseData); + return responseData; + } + } else { + String responseData = String.format("%s: Cannot return data. Null response.", method); + LOG.warn(responseData); + return responseData; + } + } + + + // PUMP SPECIFIC COMMANDS + + public Float getRemainingInsulin() { + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRemainingInsulin); + + return responseObject == null ? null : (Float) responseObject; + } + + + public MedtronicDeviceType getPumpModel() { + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.PumpModel); + + return responseObject == null ? null : (MedtronicDeviceType) responseObject; + } + + + public BasalProfile getBasalProfile() { + + // wakeUp + if (doWakeUpBeforeCommand) + wakeUp(receiverDeviceAwakeForMinutes, false); + + MedtronicCommandType commandType = MedtronicCommandType.GetBasalProfileSTD; + + if (isLogEnabled()) + LOG.debug("getDataFromPump: {}", commandType); + + MedtronicUtil.setCurrentCommand(commandType); + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Active); + + for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) { + + try { + // create message + PumpMessage msg; + + msg = makePumpMessage(commandType); + + // send and wait for response + PumpMessage response = null; + + response = sendAndListen(msg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries)); + +// LOG.debug("1st Response: " + HexDump.toHexStringDisplayable(response.getRawContent())); +// LOG.debug("1st Response: " + HexDump.toHexStringDisplayable(response.getMessageBody().getTxData())); + + String check = checkResponseContent(response, commandType.commandDescription, 1); + + byte[] data = null; + + if (check == null) { + + data = response.getRawContentOfFrame(); + + PumpMessage ackMsg = makePumpMessage(MedtronicCommandType.CommandACK, new PumpAckMessageBody()); + + while (checkIfWeHaveMoreData(commandType, response, data)) { + + response = sendAndListen(ackMsg, DEFAULT_TIMEOUT + (DEFAULT_TIMEOUT * retries)); + +// LOG.debug("{} Response: {}", runs, HexDump.toHexStringDisplayable(response2.getRawContent())); +// LOG.debug("{} Response: {}", runs, +// HexDump.toHexStringDisplayable(response2.getMessageBody().getTxData())); + + String check2 = checkResponseContent(response, commandType.commandDescription, 1); + + if (check2 == null) { + + data = ByteUtil.concat(data, response.getRawContentOfFrame()); + + } else { + this.errorMessage = check2; + LOG.error("Error with response got GetProfile: " + check2); + } + } + + } else { + errorMessage = check; + } + + BasalProfile basalProfile = (BasalProfile) medtronicConverter.convertResponse(commandType, data); + + if (basalProfile != null) { + if (isLogEnabled()) + LOG.debug("Converted response for {} is {}.", commandType.name(), basalProfile); + + MedtronicUtil.setCurrentCommand(null); + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + return basalProfile; + } + + } catch (RileyLinkCommunicationException e) { + LOG.error("Error getting response from RileyLink (error={}, retry={})", e.getMessage(), retries + 1); + } + } + + LOG.warn("Error reading profile in max retries."); + MedtronicUtil.setCurrentCommand(null); + MedtronicUtil.setPumpDeviceState(PumpDeviceState.Sleeping); + + return null; + + } + + + private boolean checkIfWeHaveMoreData(MedtronicCommandType commandType, PumpMessage response, byte[] data) { + + if (commandType == MedtronicCommandType.GetBasalProfileSTD || // + commandType == MedtronicCommandType.GetBasalProfileA || // + commandType == MedtronicCommandType.GetBasalProfileB) { + byte[] responseRaw = response.getRawContentOfFrame(); + + int last = responseRaw.length - 1; + + LOG.debug("Length: " + data.length); + + if (data.length >= BasalProfile.MAX_RAW_DATA_SIZE) { + return false; + } + + if (responseRaw.length < 2) { + return false; + } + + return !(responseRaw[last] == 0x00 && responseRaw[last - 1] == 0x00 && responseRaw[last - 2] == 0x00); + } + + return false; + } + + + public ClockDTO getPumpTime() { + + ClockDTO clockDTO = new ClockDTO(); + clockDTO.localDeviceTime = new LocalDateTime(); + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetRealTimeClock); + + if (responseObject != null) { + clockDTO.pumpTime = (LocalDateTime) responseObject; + return clockDTO; + } + + return null; + } + + + public TempBasalPair getTemporaryBasal() { + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.ReadTemporaryBasal); + + return responseObject == null ? null : (TempBasalPair) responseObject; + } + + + public Map getPumpSettings() { + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.getSettings(MedtronicUtil + .getMedtronicPumpModel())); + + return responseObject == null ? null : (Map) responseObject; + } + + + public Boolean setBolus(double units) { + + if (isLogEnabled()) + LOG.info("setBolus: " + units); + + return setCommand(MedtronicCommandType.SetBolus, MedtronicUtil.getBolusStrokes(units)); + + } + + + public boolean setTBR(TempBasalPair tbr) { + + if (isLogEnabled()) + LOG.info("setTBR: " + tbr.getDescription()); + + return setCommand(MedtronicCommandType.SetTemporaryBasal, tbr.getAsRawData()); + } + + + public Boolean setPumpTime() { + + GregorianCalendar gc = new GregorianCalendar(); + gc.add(Calendar.SECOND, 5); + + if (isLogEnabled()) + LOG.info("setPumpTime: " + DateTimeUtil.toString(gc)); + + int i = 1; + byte[] data = new byte[8]; + data[0] = 7; + data[i] = (byte) gc.get(Calendar.HOUR_OF_DAY); + data[i + 1] = (byte) gc.get(Calendar.MINUTE); + data[i + 2] = (byte) gc.get(Calendar.SECOND); + + byte[] yearByte = MedtronicUtil.getByteArrayFromUnsignedShort(gc.get(Calendar.YEAR), true); + + data[i + 3] = yearByte[0]; + data[i + 4] = yearByte[1]; + + data[i + 5] = (byte) (gc.get(Calendar.MONTH) + 1); + data[i + 6] = (byte) gc.get(Calendar.DAY_OF_MONTH); + + //LOG.info("setPumpTime: Body: " + ByteUtil.getHex(data)); + + return setCommand(MedtronicCommandType.SetRealTimeClock, data); + + } + + + private boolean setCommand(MedtronicCommandType commandType, byte[] body) { + + for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) { + + try { + if (this.doWakeUpBeforeCommand) + wakeUp(false); + + if (debugSetCommands) + LOG.debug("{}: Body - {}", commandType.getCommandDescription(), + ByteUtil.getHex(body)); + + PumpMessage msg = makePumpMessage(commandType, new CarelinkLongMessageBody(body)); + + PumpMessage pumpMessage = runCommandWithArgs(msg); + + if (debugSetCommands) + LOG.debug("{}: {}", commandType.getCommandDescription(), pumpMessage.getResponseContent()); + + if (pumpMessage.commandType == MedtronicCommandType.CommandACK) { + return true; + } else { + LOG.warn("We received non-ACK response from pump: {}", pumpMessage.getResponseContent()); + } + + } catch (RileyLinkCommunicationException e) { + if (isLogEnabled()) + LOG.warn("Error getting response from RileyLink (error={}, retry={})", e.getMessage(), retries + 1); + } + } + + return false; + } + + + public boolean cancelTBR() { + return setTBR(new TempBasalPair(0.0d, false, 0)); + } + + + public BatteryStatusDTO getRemainingBattery() { + + Object responseObject = sendAndGetResponseWithCheck(MedtronicCommandType.GetBatteryStatus); + + return responseObject == null ? null : (BatteryStatusDTO) responseObject; + } + + + public Boolean setBasalProfile(BasalProfile basalProfile) { + + List> basalProfileFrames = MedtronicUtil.getBasalProfileFrames(basalProfile.getRawData()); + + for (int retries = 0; retries <= MAX_COMMAND_TRIES; retries++) { + + PumpMessage responseMessage = null; + try { + responseMessage = runCommandWithFrames(MedtronicCommandType.SetBasalProfileSTD, + basalProfileFrames); + + if (responseMessage.commandType == MedtronicCommandType.CommandACK) + return true; + + } catch (RileyLinkCommunicationException e) { + LOG.warn("Error getting response from RileyLink (error={}, retry={})", e.getMessage(), retries + 1); + } + + if (responseMessage != null) + LOG.warn("Set Basal Profile: Invalid response: commandType={},rawData={}", responseMessage.commandType, ByteUtil.shortHexString(responseMessage.getRawContent())); + else + LOG.warn("Set Basal Profile: Null response."); + } + + return false; + + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java new file mode 100644 index 00000000000..dc0a35f2e88 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/MedtronicConverter.java @@ -0,0 +1,430 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm; + +import org.joda.time.IllegalFieldValueException; +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by andy on 5/9/18. + * High level decoder for data returned through MedtroniUIComm + */ + +public class MedtronicConverter { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + MedtronicDeviceType pumpModel; + + + public Object convertResponse(MedtronicCommandType commandType, byte[] rawContent) { + + if ((rawContent == null || rawContent.length < 1) && commandType != MedtronicCommandType.PumpModel) { + LOG.warn("Content is empty or too short, no data to convert (type={},isNull={},length={})", + commandType.name(), rawContent == null, rawContent == null ? "-" : rawContent.length); + return null; + } + + if (isLogEnabled()) + LOG.debug("Raw response before convert: " + ByteUtil.shortHexString(rawContent)); + + this.pumpModel = MedtronicUtil.getMedtronicPumpModel(); + + switch (commandType) { + + case PumpModel: { + return decodeModel(rawContent); + } + + case GetRealTimeClock: { + return decodeTime(rawContent); + } + + case GetRemainingInsulin: { + return decodeRemainingInsulin(rawContent); + } + + case GetBatteryStatus: { + return decodeBatteryStatus(rawContent); // 1 + } + + case GetBasalProfileSTD: + case GetBasalProfileA: + case GetBasalProfileB: { + return decodeBasalProfile(rawContent); + + } + + case ReadTemporaryBasal: { + return new TempBasalPair(rawContent); // 5 + } + + case Settings_512: { + return decodeSettingsLoop(rawContent); + } + + case Settings: { + return decodeSettingsLoop(rawContent); + } + + case SetBolus: { + return rawContent; // 1 + } + + default: { + throw new RuntimeException("Unsupported command Type: " + commandType); + } + + } + + } + + + private BasalProfile decodeBasalProfile(byte[] rawContent) { + + BasalProfile basalProfile = new BasalProfile(rawContent); + + return basalProfile.verify() ? basalProfile : null; + } + + + private MedtronicDeviceType decodeModel(byte[] rawContent) { + + if ((rawContent == null || rawContent.length < 4)) { + LOG.warn("Error reading PumpModel, returning Unknown_Device"); + return MedtronicDeviceType.Unknown_Device; + } + + String rawModel = StringUtil.fromBytes(ByteUtil.substring(rawContent, 1, 3)); + MedtronicDeviceType pumpModel = MedtronicDeviceType.getByDescription(rawModel); + if (isLogEnabled()) + LOG.debug("PumpModel: [raw={}, resolved={}]", rawModel, pumpModel.name()); + + if (pumpModel != MedtronicDeviceType.Unknown_Device) { + if (!MedtronicUtil.isModelSet()) { + MedtronicUtil.setMedtronicPumpModel(pumpModel); + } + } + + return pumpModel; + } + + + private BatteryStatusDTO decodeBatteryStatus(byte[] rawData) { + // 00 7C 00 00 + + BatteryStatusDTO batteryStatus = new BatteryStatusDTO(); + + int status = rawData[0]; + + if (status == 0) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Normal; + } else if (status == 1) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Low; + } else if (status == 2) { + batteryStatus.batteryStatusType = BatteryStatusDTO.BatteryStatusType.Unknown; + } + + if (rawData.length > 1) { + + // if response in 3 bytes then we add additional information + double d = (ByteUtil.toInt(rawData[1], rawData[2]) * 1.0d) / 100.0d; + + batteryStatus.voltage = d; + batteryStatus.extendedDataReceived = true; + } + + return batteryStatus; + } + + + protected Float decodeRemainingInsulin(byte[] rawData) { + int startIdx = 0; + + this.pumpModel = MedtronicUtil.getMedtronicPumpModel(); + + int strokes = pumpModel == null ? 10 : pumpModel.getBolusStrokes(); + + if (strokes == 40) { + startIdx = 2; + } + + float value = ByteUtil.toInt(rawData[startIdx], rawData[startIdx + 1]) / (1.0f * strokes); + + if (isLogEnabled()) + LOG.debug("Remaining insulin: " + value); + return value; + } + + + private LocalDateTime decodeTime(byte[] rawContent) { + + int hours = ByteUtil.asUINT8(rawContent[0]); + int minutes = ByteUtil.asUINT8(rawContent[1]); + int seconds = ByteUtil.asUINT8(rawContent[2]); + int year = (ByteUtil.asUINT8(rawContent[4]) & 0x3f) + 1984; + int month = ByteUtil.asUINT8(rawContent[5]); + int day = ByteUtil.asUINT8(rawContent[6]); + try { + LocalDateTime pumpTime = new LocalDateTime(year, month, day, hours, minutes, seconds); + return pumpTime; + } catch (IllegalFieldValueException e) { + LOG.error( + "decodeTime: Failed to parse pump time value: year=%d, month=%d, hours=%d, minutes=%d, seconds=%d", + year, month, day, hours, minutes, seconds); + return null; + } + + } + + + public Map decodeSettingsLoop(byte[] rd) { + + Map map = new HashMap<>(); + + addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map); + addSettingToMap( + "PCFG_MAX_BASAL", + "" + + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()], + rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map); + addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h", + PumpConfigurationGroup.General, map); + + addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map); + + if (rd[10] == 1) { + String patt; + switch (rd[11]) { + case 0: + patt = "STD"; + break; + + case 1: + patt = "A"; + break; + + case 2: + patt = "B"; + break; + + default: + patt = "???"; + break; + } + + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map); + + } else { + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", "STD", PumpConfigurationGroup.Basal, map); + } + + addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map); + + return map; + } + + + private Map decodeSettings512(byte[] rd) { + + Map map = new HashMap<>(); + + addSettingToMap("PCFG_AUTOOFF_TIMEOUT", "" + rd[0], PumpConfigurationGroup.General, map); + + if (rd[1] == 4) { + addSettingToMap("PCFG_ALARM_MODE", "Silent", PumpConfigurationGroup.Sound, map); + } else { + addSettingToMap("PCFG_ALARM_MODE", "Normal", PumpConfigurationGroup.Sound, map); + addSettingToMap("PCFG_ALARM_BEEP_VOLUME", "" + rd[1], PumpConfigurationGroup.Sound, map); + } + + addSettingToMap("PCFG_AUDIO_BOLUS_ENABLED", parseResultEnable(rd[2]), PumpConfigurationGroup.Bolus, map); + + if (rd[2] == 1) { + addSettingToMap("PCFG_AUDIO_BOLUS_STEP_SIZE", "" + decodeBolusInsulin(ByteUtil.asUINT8(rd[3])), + PumpConfigurationGroup.Bolus, map); + } + + addSettingToMap("PCFG_VARIABLE_BOLUS_ENABLED", parseResultEnable(rd[4]), PumpConfigurationGroup.Bolus, map); + addSettingToMap("PCFG_MAX_BOLUS", "" + decodeMaxBolus(rd), PumpConfigurationGroup.Bolus, map); + addSettingToMap( + "PCFG_MAX_BASAL", + "" + + decodeBasalInsulin(ByteUtil.makeUnsignedShort(rd[getSettingIndexMaxBasal()], + rd[getSettingIndexMaxBasal() + 1])), PumpConfigurationGroup.Basal, map); + addSettingToMap("CFG_BASE_CLOCK_MODE", rd[getSettingIndexTimeDisplayFormat()] == 0 ? "12h" : "24h", + PumpConfigurationGroup.General, map); + + if (MedtronicDeviceType.isSameDevice(pumpModel, MedtronicDeviceType.Medtronic_523andHigher)) { + addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] == 0 ? 50 : 100), PumpConfigurationGroup.Insulin, + map); +// LOG.debug("Insulin concentration: " + rd[9]); + } else { + addSettingToMap("PCFG_INSULIN_CONCENTRATION", "" + (rd[9] != 0 ? 50 : 100), PumpConfigurationGroup.Insulin, + map); +// LOG.debug("Insulin concentration: " + rd[9]); + } + addSettingToMap("PCFG_BASAL_PROFILES_ENABLED", parseResultEnable(rd[10]), PumpConfigurationGroup.Basal, map); + + if (rd[10] == 1) { + String patt; + switch (rd[11]) { + case 0: + patt = "STD"; + break; + + case 1: + patt = "A"; + break; + + case 2: + patt = "B"; + break; + + default: + patt = "???"; + break; + } + + addSettingToMap("PCFG_ACTIVE_BASAL_PROFILE", patt, PumpConfigurationGroup.Basal, map); + + } + + addSettingToMap("CFG_MM_RF_ENABLED", parseResultEnable(rd[12]), PumpConfigurationGroup.General, map); + addSettingToMap("CFG_MM_BLOCK_ENABLED", parseResultEnable(rd[13]), PumpConfigurationGroup.General, map); + + addSettingToMap("PCFG_TEMP_BASAL_TYPE", rd[14] != 0 ? "Percent" : "Units", PumpConfigurationGroup.Basal, map); + + if (rd[14] == 1) { + addSettingToMap("PCFG_TEMP_BASAL_PERCENT", "" + rd[15], PumpConfigurationGroup.Basal, map); + } + + addSettingToMap("CFG_PARADIGM_LINK_ENABLE", parseResultEnable(rd[16]), PumpConfigurationGroup.General, map); + + decodeInsulinActionSetting(rd, map); + + return map; + } + + + public void addSettingToMap(String key, String value, PumpConfigurationGroup group, Map map) { + map.put(key, new PumpSettingDTO(key, value, group)); + } + + + public Map decodeSettings(byte[] rd) { + Map map = decodeSettings512(rd); + + addSettingToMap("PCFG_MM_RESERVOIR_WARNING_TYPE_TIME", rd[18] != 0 ? "PCFG_MM_RESERVOIR_WARNING_TYPE_TIME" + : "PCFG_MM_RESERVOIR_WARNING_TYPE_UNITS", PumpConfigurationGroup.Other, map); + + addSettingToMap("PCFG_MM_SRESERVOIR_WARNING_POINT", "" + ByteUtil.asUINT8(rd[19]), + PumpConfigurationGroup.Other, map); + + addSettingToMap("CFG_MM_KEYPAD_LOCKED", parseResultEnable(rd[20]), PumpConfigurationGroup.Other, map); + + if (MedtronicDeviceType.isSameDevice(pumpModel, MedtronicDeviceType.Medtronic_523andHigher)) { + + addSettingToMap("PCFG_BOLUS_SCROLL_STEP_SIZE", "" + rd[21], PumpConfigurationGroup.Bolus, map); + addSettingToMap("PCFG_CAPTURE_EVENT_ENABLE", parseResultEnable(rd[22]), PumpConfigurationGroup.Other, map); + addSettingToMap("PCFG_OTHER_DEVICE_ENABLE", parseResultEnable(rd[23]), PumpConfigurationGroup.Other, map); + addSettingToMap("PCFG_OTHER_DEVICE_PAIRED_STATE", parseResultEnable(rd[24]), PumpConfigurationGroup.Other, + map); + } + + return map; + } + + + protected String parseResultEnable(int i) { + switch (i) { + case 0: + return "No"; + case 1: + return "Yes"; + default: + return "???"; + } + } + + + public float getStrokesPerUnit(boolean isBasal) { + return isBasal ? 40.0f : 10; // pumpModel.getBolusStrokes(); + } + + + // 512 + public void decodeInsulinActionSetting(byte[] ai, Map map) { + if (MedtronicDeviceType.isSameDevice(pumpModel, MedtronicDeviceType.Medtronic_512_712)) { + addSettingToMap("PCFG_INSULIN_ACTION_TYPE", (ai[17] != 0 ? "Regular" : "Fast"), + PumpConfigurationGroup.Insulin, map); + } else { + int i = ai[17]; + String s = ""; + + if ((i == 0) || (i == 1)) { + s = ai[17] != 0 ? "Regular" : "Fast"; + } else { + if (i == 15) + s = "Unset"; + else + s = "Curve: " + i; + } + + addSettingToMap("PCFG_INSULIN_ACTION_TYPE", s, PumpConfigurationGroup.Insulin, map); + } + } + + + public double decodeBasalInsulin(int i) { + return (double) i / (double) getStrokesPerUnit(true); + } + + + public double decodeBolusInsulin(int i) { + + return (double) i / (double) getStrokesPerUnit(false); + } + + + private int getSettingIndexMaxBasal() { + return is523orHigher() ? 7 : 6; + } + + + private int getSettingIndexTimeDisplayFormat() { + return is523orHigher() ? 9 : 8; + } + + + public double decodeMaxBolus(byte[] ai) { + return is523orHigher() ? decodeBolusInsulin(ByteUtil.toInt(ai[5], ai[6])) : decodeBolusInsulin(ByteUtil + .asUINT8(ai[5])); + } + + + private boolean is523orHigher() { + return (MedtronicDeviceType.isSameDevice(pumpModel, MedtronicDeviceType.Medtronic_523andHigher)); + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoder.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoder.java new file mode 100644 index 00000000000..fa0e58a15ab --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoder.java @@ -0,0 +1,191 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public abstract class MedtronicHistoryDecoder implements MedtronicHistoryDecoderInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + protected ByteUtil bitUtils; + + // STATISTICS (remove at later time or not) + protected boolean statisticsEnabled = true; + protected Map unknownOpCodes; + protected Map> mapStatistics; + protected MedtronicDeviceType deviceType; + + + public MedtronicHistoryDecoder() { + } + + + // public abstract Class getHistoryEntryClass(); + + // public abstract RecordDecodeStatus decodeRecord(T record); + + public abstract void postProcess(); + + + protected abstract void runPostDecodeTasks(); + + + // TODO_ extend this to also use bigger pages (for now we support only 1024 pages) + private List checkPage(RawHistoryPage page, boolean partial) throws RuntimeException { + List byteList = new ArrayList(); + + // if (!partial && page.getData().length != 1024 /* page.commandType.getRecordLength() */) { + // LOG.error("Page size is not correct. Size should be {}, but it was {} instead.", 1024, + // page.getData().length); + // // throw exception perhaps + // return byteList; + // } + + if (MedtronicUtil.getMedtronicPumpModel() == null) { + LOG.error("Device Type is not defined."); + return byteList; + } + + if (page.getData().length != 1024) { + return ByteUtil.getListFromByteArray(page.getData()); + } else if (page.isChecksumOK()) { + return ByteUtil.getListFromByteArray(page.getOnlyData()); + } else { + return null; + } + } + + + public List processPageAndCreateRecords(RawHistoryPage rawHistoryPage) { + return processPageAndCreateRecords(rawHistoryPage, false); + } + + + protected void prepareStatistics() { + if (!statisticsEnabled) + return; + + unknownOpCodes = new HashMap(); + mapStatistics = new HashMap>(); + + for (RecordDecodeStatus stat : RecordDecodeStatus.values()) { + mapStatistics.put(stat, new HashMap()); + } + } + + + protected void addToStatistics(MedtronicHistoryEntryInterface pumpHistoryEntry, RecordDecodeStatus status, + Integer opCode) { + if (!statisticsEnabled) + return; + + if (opCode != null) { + if (!unknownOpCodes.containsKey(opCode)) { + unknownOpCodes.put(opCode, opCode); + } + return; + } + + if (!mapStatistics.get(status).containsKey(pumpHistoryEntry.getEntryTypeName())) { + mapStatistics.get(status).put(pumpHistoryEntry.getEntryTypeName(), ""); + } + } + + + protected void showStatistics() { + StringBuilder sb = new StringBuilder(); + + for (Map.Entry unknownEntry : unknownOpCodes.entrySet()) { + StringUtil.appendToStringBuilder(sb, "" + unknownEntry.getKey(), ", "); + } + + if (isLogEnabled()) + LOG.debug("STATISTICS OF PUMP DECODE"); + + if (unknownOpCodes.size() > 0) { + LOG.warn("Unknown Op Codes: {}", sb.toString()); + } + + for (Map.Entry> entry : mapStatistics.entrySet()) { + sb = new StringBuilder(); + + if (entry.getKey() != RecordDecodeStatus.OK) { + if (entry.getValue().size() == 0) + continue; + + for (Map.Entry entrysub : entry.getValue().entrySet()) { + StringUtil.appendToStringBuilder(sb, entrysub.getKey(), ", "); + } + + String spaces = StringUtils.repeat(" ", 14 - entry.getKey().name().length()); + + if (isLogEnabled()) + LOG.debug(" {}{} - {}. Elements: {}", entry.getKey().name(), spaces, entry.getValue().size(), + sb.toString()); + } else { + if (isLogEnabled()) + LOG.debug(" {} - {}", entry.getKey().name(), entry.getValue().size()); + } + } + } + + + private int getUnsignedByte(byte value) { + if (value < 0) + return value + 256; + else + return value; + } + + + protected int getUnsignedInt(int value) { + if (value < 0) + return value + 256; + else + return value; + } + + + public String getFormattedFloat(float value, int decimals) { + return StringUtil.getFormatedValueUS(value, decimals); + } + + + private List processPageAndCreateRecords(RawHistoryPage rawHistoryPage, boolean partial) { + List dataClear = checkPage(rawHistoryPage, partial); + List records = createRecords(dataClear); + + for (T record : records) { + decodeRecord(record); + } + + runPostDecodeTasks(); + + return records; + } + + protected boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoderInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoderInterface.java new file mode 100644 index 00000000000..b98b2d7d332 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryDecoderInterface.java @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +import java.util.List; + +/** + * Created by andy on 3/10/19. + */ + +public interface MedtronicHistoryDecoderInterface { + + RecordDecodeStatus decodeRecord(T record); + + List createRecords(List dataClear); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.java new file mode 100644 index 00000000000..3b9dfbb48d7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntry.java @@ -0,0 +1,316 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +import com.google.gson.annotations.Expose; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + *

+ * Author: Andy {andy.rozman@gmail.com} + */ + +public abstract class MedtronicHistoryEntry implements MedtronicHistoryEntryInterface { + + protected List rawData; + + public static final Logger LOG = LoggerFactory.getLogger(MedtronicHistoryEntry.class); + + protected int[] sizes = new int[3]; + + protected byte[] head; + protected byte[] datetime; + protected byte[] body; + + // protected LocalDateTime dateTime; + + public long id; + + @Expose + public String DT; + + @Expose + public Long atechDateTime; + + @Expose + protected Map decodedData; + + public long phoneDateTime; // time on phone + + /** + * Pump id that will be used with AAPS object (time * 1000 + historyType (max is FF = 255) + */ + protected Long pumpId; + + /** + * if history object is already linked to AAPS object (either Treatment, TempBasal or TDD (tdd's + * are not actually + * linked)) + */ + public boolean linked = false; + + /** + * Linked object, see linked + */ + public Object linkedObject = null; + + + public void setLinkedObject(Object linkedObject) { + this.linked = true; + this.linkedObject = linkedObject; + } + + + public void setData(List listRawData, boolean doNotProcess) { + this.rawData = listRawData; + + // System.out.println("Head: " + sizes[0] + ", dates: " + sizes[1] + + // ", body=" + sizes[2]); + + if (doNotProcess) + return; + + head = new byte[getHeadLength() - 1]; + for (int i = 1; i < (getHeadLength()); i++) { + head[i - 1] = listRawData.get(i); + } + + if (getDateTimeLength() > 0) { + datetime = new byte[getDateTimeLength()]; + + for (int i = getHeadLength(), j = 0; j < getDateTimeLength(); i++, j++) { + datetime[j] = listRawData.get(i); + } + } + + if (getBodyLength() > 0) { + body = new byte[getBodyLength()]; + + for (int i = (getHeadLength() + getDateTimeLength()), j = 0; j < getBodyLength(); i++, j++) { + body[j] = listRawData.get(i); + } + + } + + } + + + public String getDateTimeString() { + return this.DT == null ? "Unknown" : this.DT; + } + + + public String getDecodedDataAsString() { + if (decodedData == null) + if (isNoDataEntry()) + return "No data"; + else + return ""; + else + return decodedData.toString(); + } + + + public boolean hasData() { + return (decodedData != null) || (isNoDataEntry()) || getEntryTypeName().equals("UnabsorbedInsulin"); + } + + + public boolean isNoDataEntry() { + return (sizes[0] == 2 && sizes[1] == 5 && sizes[2] == 0); + } + + + public Map getDecodedData() { + return this.decodedData; + } + + + public Object getDecodedDataEntry(String key) { + return this.decodedData != null ? this.decodedData.get(key) : null; + } + + + public boolean hasDecodedDataEntry(String key) { + return this.decodedData.containsKey(key); + } + + + public boolean showRaw() { + return getEntryTypeName().equals("EndResultTotals"); + } + + + public int getHeadLength() { + return sizes[0]; + } + + + public int getDateTimeLength() { + return sizes[1]; + } + + + public int getBodyLength() { + return sizes[2]; + } + + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + if (this.DT == null) { + LOG.error("DT is null. RawData={}", ByteUtil.getHex(this.rawData)); + } + + sb.append(getToStringStart()); + sb.append(", DT: " + (this.DT == null ? "null" : StringUtil.getStringInLength(this.DT, 19))); + sb.append(", length="); + sb.append(getHeadLength()); + sb.append(","); + sb.append(getDateTimeLength()); + sb.append(","); + sb.append(getBodyLength()); + sb.append("("); + sb.append((getHeadLength() + getDateTimeLength() + getBodyLength())); + sb.append(")"); + + boolean hasData = hasData(); + + if (hasData) { + sb.append(", data=" + getDecodedDataAsString()); + } + + if (hasData && !showRaw()) { + sb.append("]"); + return sb.toString(); + } + + if (head != null) { + sb.append(", head="); + sb.append(ByteUtil.shortHexString(this.head)); + } + + if (datetime != null) { + sb.append(", datetime="); + sb.append(ByteUtil.shortHexString(this.datetime)); + } + + if (body != null) { + sb.append(", body="); + sb.append(ByteUtil.shortHexString(this.body)); + } + + sb.append(", rawData="); + sb.append(ByteUtil.shortHexString(this.rawData)); + sb.append("]"); + + // sb.append(" DT: "); + // sb.append(this.dateTime == null ? " - " : this.dateTime.toString("dd.MM.yyyy HH:mm:ss")); + + // sb.append(" Ext: "); + + return sb.toString(); + } + + + public abstract int getOpCode(); + + + public abstract String getToStringStart(); + + + public List getRawData() { + return rawData; + } + + + public byte getRawDataByIndex(int index) { + return rawData.get(index); + } + + + public int getUnsignedRawDataByIndex(int index) { + return ByteUtil.convertUnsignedByteToInt(rawData.get(index)); + } + + + public void setRawData(List rawData) { + this.rawData = rawData; + } + + + public byte[] getHead() { + return head; + } + + + public void setHead(byte[] head) { + this.head = head; + } + + + public byte[] getDatetime() { + return datetime; + } + + + public void setDatetime(byte[] datetime) { + this.datetime = datetime; + } + + + public byte[] getBody() { + return body; + } + + + public void setBody(byte[] body) { + this.body = body; + } + + + public void setAtechDateTime(long dt) { + this.atechDateTime = dt; + this.DT = DateTimeUtil.toString(this.atechDateTime); + } + + + public void addDecodedData(String key, Object value) { + if (decodedData == null) + decodedData = new HashMap<>(); + + decodedData.put(key, value); + } + + + public String toShortString() { + if (head == null) { + return "Unidentified record. "; + } else { + return "HistoryRecord: head=[" + ByteUtil.shortHexString(this.head) + "]"; + } + } + + public boolean containsDecodedData(String key) { + if (decodedData == null) + return false; + + return decodedData.containsKey(key); + } + + // if we extend to CGMS this need to be changed back + // public abstract PumpHistoryEntryType getEntryType(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntryInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntryInterface.java new file mode 100644 index 00000000000..38b7e1dbebf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/MedtronicHistoryEntryInterface.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +import java.util.List; + +/** + * Created by andy on 7/24/18. + */ +public interface MedtronicHistoryEntryInterface { + + String getEntryTypeName(); + + void setData(List listRawData, boolean doNotProcess); + + int getDateLength(); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RawHistoryPage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RawHistoryPage.java new file mode 100644 index 00000000000..77ae2982356 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RawHistoryPage.java @@ -0,0 +1,88 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.CRC; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by geoff on 6/4/16. + */ +public class RawHistoryPage { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPBTCOMM); + + private byte[] data = new byte[0]; + + + public RawHistoryPage() { + } + + + public void appendData(byte[] newdata) { + data = ByteUtil.concat(data, newdata); + } + + + public byte[] getData() { + return data; + } + + + public byte[] getOnlyData() { + return Arrays.copyOfRange(data, 0, 1022); + } + + + public int getLength() { + return data.length; + } + + + public boolean isChecksumOK() { + if (getLength() != 1024) { + return false; + } + byte[] computedCRC = CRC.calculate16CCITT(ByteUtil.substring(data, 0, 1022)); + + int crcCalculated = ByteUtil.toInt(computedCRC[0], computedCRC[1]); + int crcStored = ByteUtil.toInt(data[1022], data[1023]); + + if (crcCalculated != crcStored) { + LOG.error("Stored CRC ({}) is different than calculated ({}), but ignored for now.", crcStored, + crcCalculated); + } else { + if (MedtronicUtil.isLowLevelDebug()) + LOG.debug("CRC ok."); + } + + return crcCalculated == crcStored; + } + + + public void dumpToDebug() { + int linesize = 80; + int offset = 0; + + StringBuffer sb = new StringBuffer(); + + while (offset < data.length) { + int bytesToLog = linesize; + if (offset + linesize > data.length) { + bytesToLog = data.length - offset; + } + sb.append(ByteUtil.shortHexString(ByteUtil.substring(data, offset, bytesToLog)) + " "); + // sb.append("\n"); + + offset += linesize; + } + + LOG.debug("History Page Data:\n{}", sb.toString()); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RecordDecodeStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RecordDecodeStatus.java new file mode 100644 index 00000000000..18cb02cfadb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/RecordDecodeStatus.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public enum RecordDecodeStatus { + OK("OK "), // + Ignored("IGNORE "), // + NotSupported("N/A YET"), // + Error("ERROR "), // + WIP("WIP "), // + Unknown("UNK "); + + String description; + + + RecordDecodeStatus(String description) { + this.description = description; + + } + + + public String getDescription() { + return description; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntry.java new file mode 100644 index 00000000000..4d35426fb80 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntry.java @@ -0,0 +1,92 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.LocalDateTime; + +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public class CGMSHistoryEntry extends MedtronicHistoryEntry { + + private CGMSHistoryEntryType entryType; + private Integer opCode; // this is set only when we have unknown entry... + + + public CGMSHistoryEntryType getEntryType() { + return entryType; + } + + + public void setEntryType(CGMSHistoryEntryType entryType) { + this.entryType = entryType; + + this.sizes[0] = entryType.getHeadLength(); + this.sizes[1] = entryType.getDateLength(); + this.sizes[2] = entryType.getBodyLength(); + } + + + @Override + public String getEntryTypeName() { + return this.entryType.name(); + } + + + public void setData(List listRawData, boolean doNotProcess) { + if (this.entryType.schemaSet) { + super.setData(listRawData, doNotProcess); + } else { + this.rawData = listRawData; + } + } + + + @Override + public int getDateLength() { + return this.entryType.getDateLength(); + } + + + @Override + public int getOpCode() { + if (opCode == null) + return entryType.getOpCode(); + else + return opCode; + } + + + public void setOpCode(Integer opCode) { + this.opCode = opCode; + } + + + public boolean hasTimeStamp() { + return (this.entryType.hasDate()); + } + + + @Override + public String getToStringStart() { + + return "CGMSHistoryEntry [type=" + StringUtils.rightPad(entryType.name(), 18) + " [" + + StringUtils.leftPad("" + getOpCode(), 3) + ", 0x" + ByteUtil.getCorrectHexValue(getOpCode()) + "]"; + } + + + public void setDateTime(LocalDateTime timeStamp, int getIndex) { + + setAtechDateTime(DateTimeUtil.toATechDate(timeStamp.plusMinutes(getIndex * 5))); + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntryType.java new file mode 100644 index 00000000000..ef24ef136dd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/CGMSHistoryEntryType.java @@ -0,0 +1,135 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms; + +import java.util.HashMap; +import java.util.Map; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public enum CGMSHistoryEntryType { + + None(0, "None", 1, 0, 0, DateType.None), // + + DataEnd(0x01, "DataEnd", 1, 0, 0, DateType.PreviousTimeStamp), // + SensorWeakSignal(0x02, "SensorWeakSignal", 1, 0, 0, DateType.PreviousTimeStamp), // + SensorCal(0x03, "SensorCal", 1, 0, 1, DateType.PreviousTimeStamp), // + SensorPacket(0x04, "SensorPacket", 1, 0, 1, DateType.PreviousTimeStamp), + SensorError(0x05, "SensorError", 1, 0, 1, DateType.PreviousTimeStamp), + SensorDataLow(0x06, "SensorDataLow", 1, 0, 1, DateType.PreviousTimeStamp), + SensorDataHigh(0x07, "SensorDataHigh", 1, 0, 1, DateType.PreviousTimeStamp), + SensorTimestamp(0x08, "SensorTimestamp", 1, 4, 0, DateType.MinuteSpecific), // + BatteryChange(0x0a, "BatteryChange", 1, 4, 0, DateType.MinuteSpecific), // + SensorStatus(0x0b, "SensorStatus", 1, 4, 0, DateType.MinuteSpecific), // + DateTimeChange(0x0c, "DateTimeChange", 1, 4, 0, DateType.SecondSpecific), // + SensorSync(0x0d, "SensorSync',packet_size=4", 1, 4, 0, DateType.MinuteSpecific), // + CalBGForGH(0x0e, "CalBGForGH',packet_size=5", 1, 4, 1, DateType.MinuteSpecific), // + SensorCalFactor(0x0f, "SensorCalFactor", 1, 4, 2, DateType.MinuteSpecific), // + Something10(0x10, "10-Something", 1, 4, 0, DateType.MinuteSpecific), // + Something19(0x13, "19-Something", 1, 0, 0, DateType.PreviousTimeStamp), + GlucoseSensorData(0xFF, "GlucoseSensorData", 1, 0, 0, DateType.PreviousTimeStamp); + + private static Map opCodeMap = new HashMap<>(); + + static { + for (CGMSHistoryEntryType type : values()) { + opCodeMap.put(type.opCode, type); + } + } + + public boolean schemaSet; + private int opCode; + private String description; + private int headLength; + private int dateLength; + private int bodyLength; + private int totalLength; + private DateType dateType; + + + CGMSHistoryEntryType(int opCode, String name, int head, int date, int body, DateType dateType) { + this.opCode = opCode; + this.description = name; + this.headLength = head; + this.dateLength = date; + this.bodyLength = body; + this.totalLength = (head + date + body); + this.schemaSet = true; + this.dateType = dateType; + } + + + // private CGMSHistoryEntryType(int opCode, String name, int length) + // { + // this.opCode = opCode; + // this.description = name; + // this.headLength = 0; + // this.dateLength = 0; + // this.bodyLength = 0; + // this.totalLength = length + 1; // opCode + // } + + public static CGMSHistoryEntryType getByCode(int opCode) { + if (opCodeMap.containsKey(opCode)) { + return opCodeMap.get(opCode); + } else + return CGMSHistoryEntryType.None; + } + + + public int getCode() { + return this.opCode; + } + + + public int getTotalLength() { + return totalLength; + } + + + public int getOpCode() { + return opCode; + } + + + public String getDescription() { + return description; + } + + + public int getHeadLength() { + return headLength; + } + + + public int getDateLength() { + return dateLength; + } + + + public int getBodyLength() { + return bodyLength; + } + + + public DateType getDateType() { + return dateType; + } + + + public boolean hasDate() { + return (this.dateType == DateType.MinuteSpecific) || (this.dateType == DateType.SecondSpecific); + } + + public enum DateType { + None, // + MinuteSpecific, // + SecondSpecific, // + PreviousTimeStamp // + + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.java new file mode 100644 index 00000000000..5a0f1e7983b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/cgms/MedtronicCGMSHistoryDecoder.java @@ -0,0 +1,470 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.cgms; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public class MedtronicCGMSHistoryDecoder extends MedtronicHistoryDecoder { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + + // CGMSValuesWriter cgmsValuesWriter = null; + + public MedtronicCGMSHistoryDecoder() { + } + + + public RecordDecodeStatus decodeRecord(CGMSHistoryEntry record) { + try { + return decodeRecord(record, false); + } catch (Exception ex) { + LOG.error(" Error decoding: type={}, ex={}", record.getEntryType().name(), ex.getMessage(), ex); + return RecordDecodeStatus.Error; + } + } + + + public RecordDecodeStatus decodeRecord(CGMSHistoryEntry entry, boolean x) { + + if (entry.getDateTimeLength() > 0) { + parseDate(entry); + } + + switch (entry.getEntryType()) { + + case SensorPacket: + decodeSensorPacket(entry); + break; + + case SensorError: + decodeSensorError(entry); + break; + + case SensorDataLow: + decodeDataHighLow(entry, 40); + break; + + case SensorDataHigh: + decodeDataHighLow(entry, 400); + break; + + case SensorTimestamp: + decodeSensorTimestamp(entry); + break; + + case SensorCal: + decodeSensorCal(entry); + break; + + case SensorCalFactor: + decodeSensorCalFactor(entry); + break; + + case SensorSync: + decodeSensorSync(entry); + break; + + case SensorStatus: + decodeSensorStatus(entry); + break; + + case CalBGForGH: + decodeCalBGForGH(entry); + break; + + case GlucoseSensorData: + decodeGlucoseSensorData(entry); + break; + + // just timestamp + case BatteryChange: + case Something10: + case DateTimeChange: + break; + + // just relative timestamp + case Something19: + case DataEnd: + case SensorWeakSignal: + break; + + case None: + break; + + } + + return RecordDecodeStatus.NotSupported; + } + + + @Override + public void postProcess() { + + } + + + public List createRecords(List dataClearInput) { + + List dataClear = reverseList(dataClearInput, Byte.class); + + prepareStatistics(); + + int counter = 0; + + List outList = new ArrayList(); + + // create CGMS entries (without dates) + do { + int opCode = getUnsignedInt(dataClear.get(counter)); + counter++; + + CGMSHistoryEntryType entryType; + + if (opCode == 0) { + // continue; + } else if ((opCode > 0) && (opCode < 20)) { + entryType = CGMSHistoryEntryType.getByCode(opCode); + + if (entryType == CGMSHistoryEntryType.None) { + this.unknownOpCodes.put(opCode, opCode); + LOG.warn("GlucoseHistoryEntry with unknown code: " + opCode); + + CGMSHistoryEntry pe = new CGMSHistoryEntry(); + pe.setEntryType(CGMSHistoryEntryType.None); + pe.setOpCode(opCode); + + pe.setData(Arrays.asList((byte) opCode), false); + + outList.add(pe); + } else { + // System.out.println("OpCode: " + opCode); + + List listRawData = new ArrayList(); + listRawData.add((byte) opCode); + + for (int j = 0; j < (entryType.getTotalLength() - 1); j++) { + listRawData.add(dataClear.get(counter)); + counter++; + } + + CGMSHistoryEntry pe = new CGMSHistoryEntry(); + pe.setEntryType(entryType); + + pe.setOpCode(opCode); + pe.setData(listRawData, false); + + // System.out.println("Record: " + pe); + + outList.add(pe); + } + } else { + CGMSHistoryEntry pe = new CGMSHistoryEntry(); + pe.setEntryType(CGMSHistoryEntryType.GlucoseSensorData); + + pe.setData(Arrays.asList((byte) opCode), false); + + outList.add(pe); + } + + } while (counter < dataClear.size()); + + List reversedOutList = reverseList(outList, CGMSHistoryEntry.class); + + Long timeStamp = null; + LocalDateTime dateTime = null; + int getIndex = 0; + + for (CGMSHistoryEntry entry : reversedOutList) { + + decodeRecord(entry); + + if (entry.hasTimeStamp()) { + timeStamp = entry.atechDateTime; + dateTime = DateTimeUtil.toLocalDateTime(timeStamp); + getIndex = 0; + } else if (entry.getEntryType() == CGMSHistoryEntryType.GlucoseSensorData) { + getIndex++; + if (dateTime != null) + entry.setDateTime(dateTime, getIndex); + } else { + if (dateTime != null) + entry.setDateTime(dateTime, getIndex); + } + + if (isLogEnabled()) + LOG.debug("Record: {}", entry); + } + + return reversedOutList; + + } + + + private List reverseList(List dataClearInput, Class clazz) { + + List outList = new ArrayList(); + + for (int i = dataClearInput.size() - 1; i > 0; i--) { + outList.add(dataClearInput.get(i)); + } + + return outList; + } + + + private int parseMinutes(int one) { + return (one & Integer.parseInt("0111111", 2)); + } + + + private int parseHours(int one) { + return (one & 0x1F); + } + + + private int parseDay(int one) { + return one & 0x1F; + } + + + private int parseMonths(int first_byte, int second_byte) { + + int first_two_bits = first_byte >> 6; + int second_two_bits = second_byte >> 6; + + return (first_two_bits << 2) + second_two_bits; + } + + + private int parseYear(int year) { + return (year & 0x0F) + 2000; + } + + + private Long parseDate(CGMSHistoryEntry entry) { + + if (!entry.getEntryType().hasDate()) + return null; + + byte[] data = entry.getDatetime(); + + if (entry.getEntryType().getDateType() == CGMSHistoryEntryType.DateType.MinuteSpecific) { + + Long atechDateTime = DateTimeUtil.toATechDate(parseYear(data[3]), parseMonths(data[0], data[1]), + parseDay(data[2]), parseHours(data[0]), parseMinutes(data[1]), 0); + + entry.setAtechDateTime(atechDateTime); + + return atechDateTime; + + } else if (entry.getEntryType().getDateType() == CGMSHistoryEntryType.DateType.SecondSpecific) { + LOG.warn("parseDate for SecondSpecific type is not implemented."); + throw new RuntimeException(); + // return null; + } else + return null; + + } + + + private void decodeGlucoseSensorData(CGMSHistoryEntry entry) { + int sgv = entry.getUnsignedRawDataByIndex(0) * 2; + entry.addDecodedData("sgv", sgv); + } + + + private void decodeCalBGForGH(CGMSHistoryEntry entry) { + + int amount = ((entry.getRawDataByIndex(3) & 0b00100000) << 3) | entry.getRawDataByIndex(5); + // + String originType; + + switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) { + case 0x00: + originType = "rf"; + break; + + default: + originType = "unknown"; + + } + + entry.addDecodedData("amount", amount); + entry.addDecodedData("originType", originType); + + } + + + private void decodeSensorSync(CGMSHistoryEntry entry) { + + String syncType; + + switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) { + case 0x01: + syncType = "new"; + break; + + case 0x02: + syncType = "old"; + break; + + default: + syncType = "find"; + break; + + } + + entry.addDecodedData("syncType", syncType); + } + + + private void decodeSensorStatus(CGMSHistoryEntry entry) { + + String statusType; + + switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) { + case 0x00: + statusType = "off"; + break; + + case 0x01: + statusType = "on"; + break; + + case 0x02: + statusType = "lost"; + break; + + default: + statusType = "unknown"; + } + + entry.addDecodedData("statusType", statusType); + + } + + + private void decodeSensorCalFactor(CGMSHistoryEntry entry) { + + double factor = (entry.getRawDataByIndex(5) << 8 | entry.getRawDataByIndex(6)) / 1000.0d; + + entry.addDecodedData("factor", factor); + } + + + private void decodeSensorCal(CGMSHistoryEntry entry) { + + String calibrationType; + + switch (entry.getRawDataByIndex(1)) { + case 0x00: + calibrationType = "meter_bg_now"; + break; + + case 0x01: + calibrationType = "waiting"; + break; + + case 0x02: + calibrationType = "cal_error"; + break; + + default: + calibrationType = "unknown"; + } + + entry.addDecodedData("calibrationType", calibrationType); + + } + + + private void decodeSensorTimestamp(CGMSHistoryEntry entry) { + + String sensorTimestampType; + + switch (entry.getRawDataByIndex(3) >> 5 & 0b00000011) { + + case 0x00: + sensorTimestampType = "LastRf"; + break; + + case 0x01: + sensorTimestampType = "PageEnd"; + break; + + case 0x02: + sensorTimestampType = "Gap"; + break; + + default: + sensorTimestampType = "Unknown"; + break; + + } + + entry.addDecodedData("sensorTimestampType", sensorTimestampType); + } + + + private void decodeSensorPacket(CGMSHistoryEntry entry) { + + String packetType; + + switch (entry.getRawDataByIndex(1)) { + case 0x02: + packetType = "init"; + break; + + default: + packetType = "unknown"; + } + + entry.addDecodedData("packetType", packetType); + } + + + private void decodeSensorError(CGMSHistoryEntry entry) { + + String errorType; + + switch (entry.getRawDataByIndex(1)) { + case 0x01: + errorType = "end"; + break; + + default: + errorType = "unknown"; + } + + entry.addDecodedData("errorType", errorType); + } + + + private void decodeDataHighLow(CGMSHistoryEntry entry, int sgv) { + entry.addDecodedData("sgv", sgv); + } + + + @Override + protected void runPostDecodeTasks() { + this.showStatistics(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java new file mode 100644 index 00000000000..71f387d1760 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/MedtronicPumpHistoryDecoder.java @@ -0,0 +1,713 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump; + +import android.util.Log; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryDecoder; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.RecordDecodeStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusWizardDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.DailyTotalsDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + * + * Author: Andy {andy.rozman@gmail.com} + */ + +public class MedtronicPumpHistoryDecoder extends MedtronicHistoryDecoder { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private PumpHistoryEntry tbrPreviousRecord; + private PumpHistoryEntry changeTimeRecord; + private MedtronicDeviceType deviceType; + private static final String TAG = "MdtPumpHistoryDecoder"; + + + public MedtronicPumpHistoryDecoder() { + } + + + public List createRecords(List dataClear) { + prepareStatistics(); + + int counter = 0; + int record = 0; + boolean incompletePacket = false; + deviceType = MedtronicUtil.getMedtronicPumpModel(); + + List outList = new ArrayList(); + String skipped = null; + int elementStart = 0; + + if (dataClear.size() == 0) { + Log.e(TAG, "Empty page."); + return outList; + } + + do { + int opCode = dataClear.get(counter); + boolean special = false; + incompletePacket = false; + + if (opCode == 0) { + counter++; + if (skipped == null) + skipped = "0x00"; + else + skipped += " 0x00"; + continue; + } else { + if (skipped != null) { + Log.w(TAG, " ... Skipped " + skipped); + skipped = null; + } + } + + PumpHistoryEntryType entryType = PumpHistoryEntryType.getByCode(opCode); + + PumpHistoryEntry pe = new PumpHistoryEntry(); + pe.setEntryType(entryType); + pe.setOffset(counter); + + counter++; + + if (counter >= 1022) { + break; + } + + List listRawData = new ArrayList(); + listRawData.add((byte) opCode); + + if (entryType == PumpHistoryEntryType.UnabsorbedInsulin + || entryType == PumpHistoryEntryType.UnabsorbedInsulin512) { + int elements = dataClear.get(counter); + listRawData.add((byte) elements); + counter++; + + int els = getUnsignedInt(elements); + + for (int k = 0; k < (els - 2); k++) { + listRawData.add((byte) dataClear.get(counter)); + counter++; + } + + special = true; + } else { + + for (int j = 0; j < (entryType.getTotalLength() - 1); j++) { + + try { + listRawData.add(dataClear.get(counter)); + counter++; + } catch (Exception ex) { + LOG.error("OpCode: " + ByteUtil.shortHexString((byte) opCode) + ", Invalid package: " + + ByteUtil.getHex(listRawData)); + // throw ex; + incompletePacket = true; + break; + } + + } + + if (incompletePacket) + break; + + } + + if (entryType == PumpHistoryEntryType.None) { + LOG.error("Error in code. We should have not come into this branch."); + } else { + + if (pe.getEntryType() == PumpHistoryEntryType.UnknownBasePacket) { + pe.setOpCode(opCode); + } + + if (entryType.getHeadLength() == 0) + special = true; + + pe.setData(listRawData, special); + + RecordDecodeStatus decoded = decodeRecord(pe); + + if ((decoded == RecordDecodeStatus.OK) || (decoded == RecordDecodeStatus.Ignored)) { + //Log.i(TAG, "#" + record + " " + decoded.getDescription() + " " + pe); + } else { + Log.w(TAG, "#" + record + " " + decoded.getDescription() + " " + pe); + } + + addToStatistics(pe, decoded, null); + + record++; + + if (decoded == RecordDecodeStatus.OK) // we add only OK records, all others are ignored + { + outList.add(pe); + } + } + + } while (counter < dataClear.size()); + + return outList; + } + + + public RecordDecodeStatus decodeRecord(PumpHistoryEntry record) { + try { + return decodeRecord(record, false); + } catch (Exception ex) { + LOG.error(" Error decoding: type={}, ex={}", record.getEntryType().name(), ex.getMessage(), ex); + return RecordDecodeStatus.Error; + } + } + + + public RecordDecodeStatus decodeRecord(PumpHistoryEntry entry, boolean x) { + + if (entry.getDateTimeLength() > 0) { + decodeDateTime(entry); + } + + switch (entry.getEntryType()) { + + // Valid entries, but not processed + case ChangeBasalPattern: + case CalBGForPH: + case ChangeRemoteId: + case ClearAlarm: + case ChangeAlarmNotifyMode: // ChangeUtility: + case EnableDisableRemote: + case BGReceived: // Ian3F: CGMS + case SensorAlert: // Ian08 CGMS + case ChangeTimeFormat: + case ChangeReservoirWarningTime: + case ChangeBolusReminderEnable: + case ChangeBolusReminderTime: + case ChangeChildBlockEnable: + case BolusWizardEnabled: + case ChangeBGReminderOffset: + case ChangeAlarmClockTime: + case ChangeMeterId: + case ChangeParadigmID: + case JournalEntryMealMarker: + case JournalEntryExerciseMarker: + case DeleteBolusReminderTime: + case SetAutoOff: + case SelfTest: + case JournalEntryInsulinMarker: + case JournalEntryOtherMarker: + case ChangeBolusWizardSetup512: + case ChangeSensorSetup2: + case ChangeSensorAlarmSilenceConfig: + case ChangeSensorRateOfChangeAlertSetup: + case ChangeBolusScrollStepSize: + case ChangeBolusWizardSetup: + case ChangeVariableBolus: + case ChangeAudioBolus: + case ChangeBGReminderEnable: + case ChangeAlarmClockEnable: + case BolusReminder: + case DeleteAlarmClockTime: + case ChangeCarbUnits: + case ChangeWatchdogEnable: + case ChangeOtherDeviceID: + case ReadOtherDevicesIDs: + case BGReceived512: + case SensorStatus: + case ReadCaptureEventEnabled: + case ChangeCaptureEventEnable: + case ReadOtherDevicesStatus: + return RecordDecodeStatus.OK; + + case Sensor_0x54: + case Sensor_0x55: + case Sensor_0x51: + case Sensor_0x52: + case EventUnknown_MM522_0x45: + case EventUnknown_MM522_0x46: + case EventUnknown_MM522_0x47: + case EventUnknown_MM522_0x48: + case EventUnknown_MM522_0x49: + case EventUnknown_MM522_0x4a: + case EventUnknown_MM522_0x4b: + case EventUnknown_MM522_0x4c: + case EventUnknown_MM512_0x10: + case EventUnknown_MM512_0x2e: + case EventUnknown_MM512_0x37: + case EventUnknown_MM512_0x38: + case EventUnknown_MM512_0x4e: + case EventUnknown_MM522_0x70: + case EventUnknown_MM512_0x88: + case EventUnknown_MM512_0x94: + case EventUnknown_MM522_0xE8: + case EventUnknown_0x4d: + case EventUnknown_MM522_0x25: + case EventUnknown_MM522_0x05: + LOG.debug(" -- ignored Unknown Pump Entry: " + entry); + return RecordDecodeStatus.Ignored; + + case UnabsorbedInsulin: + case UnabsorbedInsulin512: + return RecordDecodeStatus.Ignored; + + // **** Implemented records **** + + case DailyTotals522: + case DailyTotals523: + case DailyTotals515: + case EndResultTotals: + return decodeDailyTotals(entry); + + case ChangeBasalProfile_OldProfile: + case ChangeBasalProfile_NewProfile: + return decodeBasalProfile(entry); + + case BasalProfileStart: + return decodeBasalProfileStart(entry); + + case ChangeTime: + changeTimeRecord = entry; + return RecordDecodeStatus.OK; + + case NewTimeSet: + decodeChangeTime(entry); + return RecordDecodeStatus.OK; + + case TempBasalDuration: + // decodeTempBasal(entry); + return RecordDecodeStatus.OK; + + case TempBasalRate: + // decodeTempBasal(entry); + return RecordDecodeStatus.OK; + + case Bolus: + decodeBolus(entry); + return RecordDecodeStatus.OK; + + case BatteryChange: + decodeBatteryActivity(entry); + return RecordDecodeStatus.OK; + + case LowReservoir: + decodeLowReservoir(entry); + return RecordDecodeStatus.OK; + + case LowBattery: + case Suspend: + case Resume: + case Rewind: + case NoDeliveryAlarm: + case ChangeTempBasalType: + case ChangeMaxBolus: + case ChangeMaxBasal: + case ClearSettings: + case SaveSettings: + return RecordDecodeStatus.OK; + + case BolusWizard: + return decodeBolusWizard(entry); + + case BolusWizard512: + return decodeBolusWizard512(entry); + + case Prime: + decodePrime(entry); + return RecordDecodeStatus.OK; + + case TempBasalCombined: + return RecordDecodeStatus.Ignored; + + case None: + case UnknownBasePacket: + return RecordDecodeStatus.Error; + + default: { + LOG.debug("Not supported: " + entry.getEntryType()); + return RecordDecodeStatus.NotSupported; + } + + } + + // return RecordDecodeStatus.Error; + + } + + + private RecordDecodeStatus decodeDailyTotals(PumpHistoryEntry entry) { + + entry.addDecodedData("Raw Data", ByteUtil.getHex(entry.getRawData())); + + DailyTotalsDTO totals = new DailyTotalsDTO(entry); + + entry.addDecodedData("Object", totals); + + return RecordDecodeStatus.OK; + } + + + private RecordDecodeStatus decodeBasalProfile(PumpHistoryEntry entry) { + + // LOG.debug("decodeBasalProfile: {}", entry); + + BasalProfile basalProfile = new BasalProfile(); + basalProfile.setRawDataFromHistory(entry.getBody()); + + // LOG.debug("decodeBasalProfile BasalProfile: {}", basalProfile); + + entry.addDecodedData("Object", basalProfile); + + return RecordDecodeStatus.OK; + } + + + private void decodeChangeTime(PumpHistoryEntry entry) { + if (changeTimeRecord == null) + return; + + entry.setDisplayableValue(entry.getDateTimeString()); + + this.changeTimeRecord = null; + } + + + private void decodeBatteryActivity(PumpHistoryEntry entry) { + // this.writeData(PumpBaseType.Event, entry.getHead()[0] == 0 ? PumpEventType.BatteryRemoved : + // PumpEventType.BatteryReplaced, entry.getATechDate()); + + entry.setDisplayableValue(entry.getHead()[0] == 0 ? "Battery Removed" : "Battery Replaced"); + } + + + public static String getFormattedValue(float value, int decimals) { + return String.format(Locale.ENGLISH, "%." + decimals + "f", value); + } + + + private RecordDecodeStatus decodeBasalProfileStart(PumpHistoryEntry entry) { + byte[] body = entry.getBody(); + // int bodyOffset = headerSize + timestampSize; + int offset = body[0] * 1000 * 30 * 60; + Float rate = null; + int index = entry.getHead()[0]; + + if (MedtronicDeviceType.isSameDevice(MedtronicUtil.getMedtronicPumpModel(), + MedtronicDeviceType.Medtronic_523andHigher)) { + rate = body[1] * 0.025f; + } + + //LOG.info("Basal Profile Start: offset={}, rate={}, index={}, body_raw={}", offset, rate, index, body); + + if (rate == null) { + LOG.warn("Basal Profile Start (ERROR): offset={}, rate={}, index={}, body_raw={}", offset, rate, index, + body); + return RecordDecodeStatus.Error; + } else { + entry.addDecodedData("Value", getFormattedFloat(rate, 3)); + entry.setDisplayableValue(getFormattedFloat(rate, 3)); + return RecordDecodeStatus.OK; + } + + } + + + private RecordDecodeStatus decodeBolusWizard(PumpHistoryEntry entry) { + byte[] body = entry.getBody(); + + BolusWizardDTO dto = new BolusWizardDTO(); + + float bolusStrokes = 10.0f; + + if (MedtronicDeviceType.isSameDevice(MedtronicUtil.getMedtronicPumpModel(), + MedtronicDeviceType.Medtronic_523andHigher)) { + // https://github.com/ps2/minimed_rf/blob/master/lib/minimed_rf/log_entries/bolus_wizard.rb#L102 + bolusStrokes = 40.0f; + + dto.carbs = ((body[1] & 0x0c) << 6) + body[0]; + + dto.bloodGlucose = ((body[1] & 0x03) << 8) + entry.getHead()[0]; + dto.carbRatio = body[1] / 10.0f; + // carb_ratio (?) = (((self.body[2] & 0x07) << 8) + self.body[3]) / + // 10.0s + dto.insulinSensitivity = new Float(body[4]); + dto.bgTargetLow = (int)body[5]; + dto.bgTargetHigh = (int)body[14]; + dto.correctionEstimate = (((body[9] & 0x38) << 5) + body[6]) / bolusStrokes; + dto.foodEstimate = ((body[7] << 8) + body[8]) / bolusStrokes; + dto.unabsorbedInsulin = ((body[10] << 8) + body[11]) / bolusStrokes; + dto.bolusTotal = ((body[12] << 8) + body[13]) / bolusStrokes; + } else { + dto.bloodGlucose = (((body[1] & 0x0F) << 8) | entry.getHead()[0]); + dto.carbs = (int)body[0]; + dto.carbRatio = Float.valueOf(body[2]); + dto.insulinSensitivity = new Float(body[3]); + dto.bgTargetLow = (int)body[4]; + dto.bgTargetHigh = (int)body[12]; + dto.bolusTotal = body[11] / bolusStrokes; + dto.foodEstimate = body[6] / bolusStrokes; + dto.unabsorbedInsulin = body[9] / bolusStrokes; + dto.bolusTotal = body[11] / bolusStrokes; + dto.correctionEstimate = (body[7] + (body[5] & 0x0F)) / bolusStrokes; + } + + if (dto.bloodGlucose != null && dto.bloodGlucose < 0) { + dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.byteValue()); + } + + dto.atechDateTime = entry.atechDateTime; + entry.addDecodedData("Object", dto); + entry.setDisplayableValue(dto.getDisplayableValue()); + + return RecordDecodeStatus.OK; + } + + + private RecordDecodeStatus decodeBolusWizard512(PumpHistoryEntry entry) { + byte[] body = entry.getBody(); + + BolusWizardDTO dto = new BolusWizardDTO(); + + float bolusStrokes = 10.0f; + + dto.bloodGlucose = ((body[1] & 0x03 << 8) | entry.getHead()[0]); + dto.carbs = (body[1] & 0xC) << 6 | body[0]; // (int)body[0]; + dto.carbRatio = Float.valueOf(body[2]); + dto.insulinSensitivity = new Float(body[3]); + dto.bgTargetLow = (int)body[4]; + dto.foodEstimate = body[6] / 10.0f; + dto.correctionEstimate = (body[7] + (body[5] & 0x0F)) / bolusStrokes; + dto.unabsorbedInsulin = body[9] / bolusStrokes; + dto.bolusTotal = body[11] / bolusStrokes; + dto.bgTargetHigh = dto.bgTargetLow; + + if (dto.bloodGlucose != null && dto.bloodGlucose < 0) { + dto.bloodGlucose = ByteUtil.convertUnsignedByteToInt(dto.bloodGlucose.byteValue()); + } + + dto.atechDateTime = entry.atechDateTime; + entry.addDecodedData("Object", dto); + entry.setDisplayableValue(dto.getDisplayableValue()); + + return RecordDecodeStatus.OK; + } + + + private void decodeLowReservoir(PumpHistoryEntry entry) { + float amount = (getUnsignedInt(entry.getHead()[0]) * 1.0f / 10.0f) * 2; + + entry.setDisplayableValue(getFormattedValue(amount, 1)); + } + + + private void decodePrime(PumpHistoryEntry entry) { + float amount = bitUtils.toInt(entry.getHead()[2], entry.getHead()[3]) / 10.0f; + float fixed = bitUtils.toInt(entry.getHead()[0], entry.getHead()[1]) / 10.0f; + +// amount = (double)(asUINT8(data[4]) << 2) / 40.0; +// programmedAmount = (double)(asUINT8(data[2]) << 2) / 40.0; +// primeType = programmedAmount == 0 ? "manual" : "fixed"; + + entry.addDecodedData("Amount", amount); + entry.addDecodedData("FixedAmount", fixed); + + entry.setDisplayableValue("Amount=" + getFormattedValue(amount, 2) + ", Fixed Amount=" + + getFormattedValue(fixed, 2)); + } + + + private void decodeChangeTempBasalType(PumpHistoryEntry entry) { + entry.addDecodedData("isPercent", ByteUtil.asUINT8(entry.getRawDataByIndex(0)) == 1); // index moved from 1 -> 0 + } + + + private void decodeBgReceived(PumpHistoryEntry entry) { + entry.addDecodedData("amount", (ByteUtil.asUINT8(entry.getRawDataByIndex(0)) << 3) + (ByteUtil.asUINT8(entry.getRawDataByIndex(3)) >> 5)); + entry.addDecodedData("meter", ByteUtil.substring(entry.getRawData(), 6, 3)); // index moved from 1 -> 0 + } + + + private void decodeCalBGForPH(PumpHistoryEntry entry) { + entry.addDecodedData("amount", ((ByteUtil.asUINT8(entry.getRawDataByIndex(5)) & 0x80) << 1) + ByteUtil.asUINT8(entry.getRawDataByIndex(0))); // index moved from 1 -> 0 + } + + + private void decodeNoDeliveryAlarm(PumpHistoryEntry entry) { + //rawtype = asUINT8(data[1]); + // not sure if this is actually NoDelivery Alarm? + } + + + @Override + public void postProcess() { + } + + + @Override + protected void runPostDecodeTasks() { + this.showStatistics(); + } + + + private void decodeBolus(PumpHistoryEntry entry) { + BolusDTO bolus = new BolusDTO(); + + byte[] data = entry.getHead(); + + if (MedtronicDeviceType.isSameDevice(MedtronicUtil.getMedtronicPumpModel(), + MedtronicDeviceType.Medtronic_523andHigher)) { + bolus.setRequestedAmount(ByteUtil.toInt(data[0], data[1]) / 40.0d); + bolus.setDeliveredAmount(ByteUtil.toInt(data[2], data[3]) / 40.0d); + bolus.setInsulinOnBoard(ByteUtil.toInt(data[4], data[5]) / 40.0d); + bolus.setDuration(data[6] * 30); + } else { + bolus.setRequestedAmount(ByteUtil.asUINT8(data[0]) / 10.0d); + bolus.setDeliveredAmount(ByteUtil.asUINT8(data[1]) / 10.0d); + bolus.setDuration(ByteUtil.asUINT8(data[2]) * 30); + } + + bolus.setBolusType((bolus.getDuration() != null && (bolus.getDuration() > 0)) ? PumpBolusType.Extended + : PumpBolusType.Normal); + bolus.setAtechDateTime(entry.atechDateTime); + + entry.addDecodedData("Object", bolus); + entry.setDisplayableValue(bolus.getDisplayableValue()); + + } + + + private void decodeTempBasal(PumpHistoryEntry entry) { + + if (this.tbrPreviousRecord == null) { + // LOG.debug(this.tbrPreviousRecord.toString()); + this.tbrPreviousRecord = entry; + return; + } + + decodeTempBasal(this.tbrPreviousRecord, entry); + + tbrPreviousRecord = null; + } + + + public static void decodeTempBasal(PumpHistoryEntry tbrPreviousRecord, PumpHistoryEntry entry) { + + PumpHistoryEntry tbrRate = null, tbrDuration = null; + + if (entry.getEntryType() == PumpHistoryEntryType.TempBasalRate) { + tbrRate = entry; + } else { + tbrDuration = entry; + } + + if (tbrRate != null) { + tbrDuration = tbrPreviousRecord; + } else { + tbrRate = tbrPreviousRecord; + } + + TempBasalPair tbr = new TempBasalPair(tbrRate.getHead()[0], tbrDuration.getHead()[0], (ByteUtil.asUINT8(tbrRate + .getDatetime()[4]) >> 3) == 0); + + // System.out.println("TBR: amount=" + tbr.getInsulinRate() + ", duration=" + tbr.getDurationMinutes() + // // + " min. Packed: " + tbr.getValue() + // ); + + entry.addDecodedData("Object", tbr); + entry.setDisplayableValue(tbr.getDescription()); + + } + + + private void decodeDateTime(PumpHistoryEntry entry) { + byte[] dt = entry.getDatetime(); + + if (dt == null) { + LOG.warn("DateTime not set."); + } + + if (entry.getDateTimeLength() == 5) { + + int seconds = dt[0] & 0x3F; + int minutes = dt[1] & 0x3F; + int hour = dt[2] & 0x1F; + + int month = ((dt[0] >> 4) & 0x0c) + ((dt[1] >> 6) & 0x03); + // ((dt[0] & 0xC0) >> 6) | ((dt[1] & 0xC0) >> 4); + + int dayOfMonth = dt[3] & 0x1F; + int year = fix2DigitYear(dt[4] & 0x3F); // Assuming this is correct, need to verify. Otherwise this will be + // a problem in 2016. + + entry.setAtechDateTime(DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds)); + + } else if (entry.getDateTimeLength() == 2) { + int low = ByteUtil.asUINT8(dt[0]) & 0x1F; + int mhigh = (ByteUtil.asUINT8(dt[0]) & 0xE0) >> 4; + int mlow = (ByteUtil.asUINT8(dt[1]) & 0x80) >> 7; + int month = mhigh + mlow; + // int dayOfMonth = low + 1; + int dayOfMonth = dt[0] & 0x1F; + int year = 2000 + (ByteUtil.asUINT8(dt[1]) & 0x7F); + + int hour = 0; + int minutes = 0; + int seconds = 0; + + //LOG.debug("DT: {} {} {}", year, month, dayOfMonth); + + if (dayOfMonth == 32) { + LOG.warn("Entry: Day 32 {} = [{}] {}", entry.getEntryType().name(), + ByteUtil.getHex(entry.getRawData()), entry); + } + + if (isEndResults(entry.getEntryType())) { + hour = 23; + minutes = 59; + seconds = 59; + } + + entry.setAtechDateTime(DateTimeUtil.toATechDate(year, month, dayOfMonth, hour, minutes, seconds)); + + } else { + LOG.warn("Unknown datetime format: " + entry.getDateTimeLength()); + } + + } + + + private boolean isEndResults(PumpHistoryEntryType entryType) { + + return (entryType == PumpHistoryEntryType.EndResultTotals || + entryType == PumpHistoryEntryType.DailyTotals515 || + entryType == PumpHistoryEntryType.DailyTotals522 || + entryType == PumpHistoryEntryType.DailyTotals523); + } + + + private int fix2DigitYear(int year) { + if (year > 90) { + year += 1900; + } else { + year += 2000; + } + + return year; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.java new file mode 100644 index 00000000000..5a290c6a024 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntry.java @@ -0,0 +1,177 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump; + +import com.google.gson.annotations.Expose; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.MedtronicHistoryEntry; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + *

+ * Author: Andy {andy.rozman@gmail.com} + */ + +public class PumpHistoryEntry extends MedtronicHistoryEntry { + + private static Logger LOG = LoggerFactory.getLogger(PumpHistoryEntry.class); + + @Expose + private PumpHistoryEntryType entryType; + private Integer opCode; // this is set only when we have unknown entry... + private int offset; + private String displayableValue = ""; + + + public PumpHistoryEntryType getEntryType() { + return entryType; + } + + + public void setEntryType(PumpHistoryEntryType entryType) { + this.entryType = entryType; + + this.sizes[0] = entryType.getHeadLength(); + this.sizes[1] = entryType.getDateLength(); + this.sizes[2] = entryType.getBodyLength(); + + if (this.entryType != null && this.atechDateTime != null) + setPumpId(); + } + + + private void setPumpId() { + this.pumpId = this.entryType.getCode() + (this.atechDateTime * 1000L); + } + + + @Override + public int getOpCode() { + if (opCode == null) + return entryType.getOpCode(); + else + return opCode; + } + + + public void setOpCode(Integer opCode) { + this.opCode = opCode; + } + + + @Override + public String getToStringStart() { + return "PumpHistoryEntry [type=" + StringUtil.getStringInLength(entryType.name(), 20) + " [" + + StringUtil.getStringInLength("" + getOpCode(), 3) + ", 0x" + + ByteUtil.shortHexString((byte) getOpCode()) + "]"; + } + + + public String toString() { + Object object = this.getDecodedDataEntry("Object"); + + if (object == null) { + return super.toString(); + } else { + return "PumpHistoryEntry [DT: " + DT + ", Object=" + object.toString() + "]"; + } + } + + + public int getOffset() { + return offset; + } + + + public void setOffset(int offset) { + this.offset = offset; + } + + + @Override + public String getEntryTypeName() { + return this.entryType.name(); + } + + + @Override + public int getDateLength() { + return this.entryType.getDateLength(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + + if (!(o instanceof PumpHistoryEntry)) + return false; + + PumpHistoryEntry that = (PumpHistoryEntry) o; + + return entryType == that.entryType && // + this.atechDateTime == that.atechDateTime; // && // + // Objects.equals(this.decodedData, that.decodedData); + } + + + @Override + public int hashCode() { + return Objects.hash(entryType, opCode, offset); + } + + + // public boolean isAfter(LocalDateTime dateTimeIn) { + // // LOG.debug("Entry: " + this.dateTime); + // // LOG.debug("Datetime: " + dateTimeIn); + // // LOG.debug("Item after: " + this.dateTime.isAfter(dateTimeIn)); + // return this.dateTime.isAfter(dateTimeIn); + // } + + public boolean isAfter(long atechDateTime) { + if (this.atechDateTime == null) { + LOG.error("Date is null. Show object: " + toString()); + return false; // FIXME shouldn't happen + } + + return atechDateTime < this.atechDateTime; + } + + + public void setDisplayableValue(String displayableValue) { + this.displayableValue = displayableValue; + } + + + public String getDisplayableValue() { + return displayableValue; + } + + public static class Comparator implements java.util.Comparator { + + @Override + public int compare(PumpHistoryEntry o1, PumpHistoryEntry o2) { + int data = (int) (o2.atechDateTime - o1.atechDateTime); + + if (data != 0) + return data; + + return o2.getEntryType().getCode() - o1.getEntryType().getCode(); + } + } + + + public Long getPumpId() { + setPumpId(); + + return pumpId; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntryType.java new file mode 100644 index 00000000000..15b86fdb8b3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryEntryType.java @@ -0,0 +1,433 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * This file was taken from GGC - GNU Gluco Control (ggc.sourceforge.net), application for diabetes + * management and modified/extended for AAPS. + *

+ * Author: Andy {andy.rozman@gmail.com} + */ + +public enum PumpHistoryEntryType // implements CodeEnum +{ + + None(0, "None", PumpHistoryEntryGroup.Unknown, 1, 0, 0), + + Bolus(0x01, "Bolus", PumpHistoryEntryGroup.Bolus, 4, 5, 0), // 523+[H=8] 9/13 + + Prime(0x03, "Prime", PumpHistoryEntryGroup.Prime, 5, 5, 0), // + + /**/EventUnknown_MM522_0x05((byte) 0x05, "Unknown Event 0x05", PumpHistoryEntryGroup.Unknown, 2, 5, 28), // + NoDeliveryAlarm(0x06, "No Delivery", PumpHistoryEntryGroup.Alarm, 4, 5, 0), // + EndResultTotals(0x07, "End Result Totals", PumpHistoryEntryGroup.Statistic, 5, 2, 0), + ChangeBasalProfile_OldProfile(0x08, "Change Basal Profile (Old)", PumpHistoryEntryGroup.Basal, 2, 5, 145), + ChangeBasalProfile_NewProfile(0x09, "Change Basal Profile (New)", PumpHistoryEntryGroup.Basal, 2, 5, 145), // + /**/EventUnknown_MM512_0x10(0x10, "Unknown Event 0x10", PumpHistoryEntryGroup.Unknown), // 29, 5, 0 + CalBGForPH(0x0a, "BG Capture", PumpHistoryEntryGroup.Glucose), // + SensorAlert(0x0b, "Sensor Alert", PumpHistoryEntryGroup.Alarm, 3, 5, 0), // Ian08 + ClearAlarm(0x0c, "Clear Alarm", PumpHistoryEntryGroup.Alarm, 2, 5, 0), // 2,5,4 + + // Andy0d(0x0d, "Unknown", 2, 5, 0), + + ChangeBasalPattern(0x14, "Change Basal Pattern", PumpHistoryEntryGroup.Basal), // + TempBasalDuration(0x16, "TBR Duration", PumpHistoryEntryGroup.Basal), // + ChangeTime(0x17, "Change Time", PumpHistoryEntryGroup.Configuration), // + NewTimeSet(0x18, "New Time Set", PumpHistoryEntryGroup.Notification), // + LowBattery(0x19, "LowBattery", PumpHistoryEntryGroup.Notification), // + BatteryChange(0x1a, "Battery Change", PumpHistoryEntryGroup.Notification), // + SetAutoOff(0x1b, "Set Auto Off", PumpHistoryEntryGroup.Configuration), // + Suspend(0x1e, "Suspend", PumpHistoryEntryGroup.Basal), // + Resume(0x1f, "Resume", PumpHistoryEntryGroup.Basal), // + SelfTest(0x20, "Self Test", PumpHistoryEntryGroup.Statistic), // + Rewind(0x21, "Rewind", PumpHistoryEntryGroup.Prime), // + ClearSettings(0x22, "Clear Settings", PumpHistoryEntryGroup.Configuration), // + ChangeChildBlockEnable(0x23, "Change Child Block Enable", PumpHistoryEntryGroup.Configuration), // + ChangeMaxBolus(0x24, "Change Max Bolus", PumpHistoryEntryGroup.Configuration), // + /**/EventUnknown_MM522_0x25(0x25, "Unknown Event 0x25", PumpHistoryEntryGroup.Unknown), // 8? + EnableDisableRemote(0x26, "Enable/Disable Remote", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // 2, 5, 14 V6:2,5,14 + ChangeRemoteId(0x27, "Change Remote ID", PumpHistoryEntryGroup.Configuration), // ?? + + ChangeMaxBasal(0x2c, "Change Max Basal", PumpHistoryEntryGroup.Configuration), // + BolusWizardEnabled(0x2d, "Bolus Wizard Enabled", PumpHistoryEntryGroup.Configuration), // V3 ? + /**/EventUnknown_MM512_0x2e(0x2e, "Unknown Event 0x2e", PumpHistoryEntryGroup.Unknown, 2, 5, 100), // + BolusWizard512(0x2f, "Bolus Wizard (512)", PumpHistoryEntryGroup.Bolus, 2, 5, 12), // + UnabsorbedInsulin512(0x30, "Unabsorbed Insulin (512)", PumpHistoryEntryGroup.Statistic, 5, 0, 0), // FIXME + ChangeBGReminderOffset(0x31, "Change BG Reminder Offset", PumpHistoryEntryGroup.Configuration), // + ChangeAlarmClockTime(0x32, "Change Alarm Clock Time", PumpHistoryEntryGroup.Configuration), // + TempBasalRate(0x33, "TBR Rate", PumpHistoryEntryGroup.Basal, 2, 5, 1), // + LowReservoir(0x34, "Low Reservoir", PumpHistoryEntryGroup.Notification), // + ChangeAlarmClock(0x35, "Change Alarm Clock", PumpHistoryEntryGroup.Configuration), // + ChangeMeterId(0x36, "Change Meter ID", PumpHistoryEntryGroup.Configuration), // + /**/EventUnknown_MM512_0x37(0x37, "Unknown Event 0x37", PumpHistoryEntryGroup.Unknown), // V:MM512 + /**/EventUnknown_MM512_0x38(0x38, "Unknown Event 0x38", PumpHistoryEntryGroup.Unknown), // + BGReceived512(0x39, "BG Received (512)", PumpHistoryEntryGroup.Glucose), // + /**/EventUnknown_MM512_0x3a(0x3a, "Unknown Event 0x3a", PumpHistoryEntryGroup.Unknown), // + SensorStatus(0x3b, "Sensor Status", PumpHistoryEntryGroup.Glucose), // + ChangeParadigmID(0x3c, "Change Paradigm ID", PumpHistoryEntryGroup.Configuration, 2, 5, 14), // V3 ? V6: 2,5,14 ?? is it this length or just 7 + EventUnknown_MM512_0x3D(0x3d, "Unknown Event 0x3D", PumpHistoryEntryGroup.Unknown), // + EventUnknown_MM512_0x3E(0x3e, "Unknown Event 0x3E", PumpHistoryEntryGroup.Unknown), // + BGReceived(0x3f, "BG Received", PumpHistoryEntryGroup.Glucose, 2, 5, 3), // Ian3F + JournalEntryMealMarker(0x40, "Meal Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 2), // is size just 7??? V6 + JournalEntryExerciseMarker(0x41, "Exercise Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // ?? + JournalEntryInsulinMarker(0x42, "Insulin Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 0), // V6 = body(0)/was=1 + JournalEntryOtherMarker(0x43, "Other Marker", PumpHistoryEntryGroup.Bolus, 2, 5, 1), // V6 = body(1)/was=0 + EnableSensorAutoCal(0x44, "Enable Sensor AutoCal", PumpHistoryEntryGroup.Glucose), // + /**/EventUnknown_MM522_0x45(0x45, "Unknown Event 0x45", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x46(0x46, "Unknown Event 0x46", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x47(0x47, "Unknown Event 0x47", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x48(0x48, "Unknown Event 0x48", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x49(0x49, "Unknown Event 0x49", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x4a(0x4a, "Unknown Event 0x4a", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x4b(0x4b, "Unknown Event 0x4b", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_MM522_0x4c(0x4c, "Unknown Event 0x4c", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + /**/EventUnknown_0x4d(0x4d, "Unknown Event 0x4d", PumpHistoryEntryGroup.Unknown), // V5: 512: 7, 522: 8 ????NS + /**/EventUnknown_MM512_0x4e(0x4e, "Unknown Event 0x4e", PumpHistoryEntryGroup.Unknown), // /**/ + ChangeBolusWizardSetup512(0x4f, "Bolus Wizard Setup (512)", PumpHistoryEntryGroup.Configuration, 2, 5, 32), // + ChangeSensorSetup2(0x50, "Sensor Setup2", PumpHistoryEntryGroup.Configuration, 2, 5, 30), // Ian50 + /**/Sensor_0x51(0x51, "Unknown Event 0x51", PumpHistoryEntryGroup.Unknown), // + /**/Sensor_0x52(0x52, "Unknown Event 0x52", PumpHistoryEntryGroup.Unknown), // + ChangeSensorAlarmSilenceConfig(0x53, "Sensor Alarm Silence Config", PumpHistoryEntryGroup.Configuration, 2, 5, 1), // 8 + + /**/Sensor_0x54(0x54, "Unknown Event 0x54", PumpHistoryEntryGroup.Unknown), // Ian54 + /**/Sensor_0x55(0x55, "Unknown Event 0x55", PumpHistoryEntryGroup.Unknown), // + ChangeSensorRateOfChangeAlertSetup(0x56, "Sensor Rate Of Change Alert Setup", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12 + ChangeBolusScrollStepSize(0x57, "Change Bolus Scroll Step Size", PumpHistoryEntryGroup.Configuration), // + + // V4 + // Andy58(0x58, "Unknown", 13, 5, 0), // TO DO is this one really there ??? + + ChangeBolusWizardSetup(0x5a, "Bolus Wizard Setup (512)", PumpHistoryEntryGroup.Configuration, 2, 5, 137), // V2: 522+[B=143] // V6 124 -> 144 + BolusWizard(0x5b, "Bolus Wizard Estimate", PumpHistoryEntryGroup.Configuration, 2, 5, 13), // 15 // + UnabsorbedInsulin(0x5c, "Unabsorbed Insulin", PumpHistoryEntryGroup.Statistic, 5, 0, 0), // head[1] -> body + SaveSettings(0x5d, "Save Settings", PumpHistoryEntryGroup.Configuration), // + ChangeVariableBolus(0x5e, "Change Variable Bolus", PumpHistoryEntryGroup.Configuration), // + ChangeAudioBolus(0x5f, "Easy Bolus Enabled", PumpHistoryEntryGroup.Configuration), // V3 ? + ChangeBGReminderEnable(0x60, "BG Reminder Enable", PumpHistoryEntryGroup.Configuration), // questionable60 + ChangeAlarmClockEnable(0x61, "Alarm Clock Enable", PumpHistoryEntryGroup.Configuration), // + ChangeTempBasalType((byte) 0x62, "Change Basal Type", PumpHistoryEntryGroup.Configuration), // ChangeTempBasalTypePumpEvent + ChangeAlarmNotifyMode(0x63, "Change Alarm Notify Mode", PumpHistoryEntryGroup.Configuration), // + ChangeTimeFormat(0x64, "Change Time Format", PumpHistoryEntryGroup.Configuration), // + ChangeReservoirWarningTime((byte) 0x65, "Change Reservoir Warning Time", PumpHistoryEntryGroup.Configuration), // + ChangeBolusReminderEnable(0x66, "Change Bolus Reminder Enable", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9 + ChangeBolusReminderTime((byte) 0x67, "Change Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9 + DeleteBolusReminderTime((byte) 0x68, "Delete Bolus Reminder Time", PumpHistoryEntryGroup.Configuration, 2, 5, 2), // 9 + BolusReminder(0x69, "Bolus Reminder", PumpHistoryEntryGroup.Configuration, 2, 5, 0), // Ian69 + DeleteAlarmClockTime(0x6a, "Delete Alarm Clock Time", PumpHistoryEntryGroup.Configuration, 2, 5, 7), // 14 + + DailyTotals515(0x6c, "Daily Totals (515)", PumpHistoryEntryGroup.Statistic, 1, 2, 35), // v4: 0,0,36. v5: 1,2,33 + DailyTotals522(0x6d, "Daily Totals (522)", PumpHistoryEntryGroup.Statistic, 1, 2, 41), // + DailyTotals523(0x6e, "Daily Totals (523)", PumpHistoryEntryGroup.Statistic, 1, 2, 49), // 1102014-03-17T00:00:00 + ChangeCarbUnits((byte) 0x6f, "Change Carb Units", PumpHistoryEntryGroup.Configuration), // + /**/EventUnknown_MM522_0x70((byte) 0x70, "Unknown Event 0x70", PumpHistoryEntryGroup.Unknown, 2, 5, 1), // + + BasalProfileStart(0x7b, "Basal Profile Start", PumpHistoryEntryGroup.Basal, 2, 5, 3), // // 722 + ChangeWatchdogEnable((byte) 0x7c, "Change Watchdog Enable", PumpHistoryEntryGroup.Configuration), // + ChangeOtherDeviceID((byte) 0x7d, "Change Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 30), // + + ChangeWatchdogMarriageProfile(0x81, "Change Watchdog Marriage Profile", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // 12 + DeleteOtherDeviceID(0x82, "Delete Other Device ID", PumpHistoryEntryGroup.Configuration, 2, 5, 5), // + ChangeCaptureEventEnable(0x83, "Change Capture Event Enable", PumpHistoryEntryGroup.Configuration), // + + /**/EventUnknown_MM512_0x88(0x88, "Unknown Event 0x88", PumpHistoryEntryGroup.Unknown), // + + /**/EventUnknown_MM512_0x94(0x94, "Unknown Event 0x94", PumpHistoryEntryGroup.Unknown), // + // IanA8(0xA8, "xx", 10, 5, 0), // + + // Andy90(0x90, "Unknown", 7, 5, 0), + + // AndyB4(0xb4, "Unknown", 7, 5, 0), + // Andy4A(0x4a, "Unknown", 5, 5, 0), + + // head[1], + // body[49] op[0x6e] + + /**/EventUnknown_MM522_0xE8(0xe8, "Unknown Event 0xE8", PumpHistoryEntryGroup.Unknown, 2, 5, 25), // + + ReadOtherDevicesIDs(0xf0, "Read Other Devices IDs", PumpHistoryEntryGroup.Configuration), // ? + ReadCaptureEventEnabled(0xf1, "Read Capture Event Enabled", PumpHistoryEntryGroup.Configuration), // ? + ChangeCaptureEventEnable2(0xf2, "Change Capture Event Enable2", PumpHistoryEntryGroup.Configuration), // ? + ReadOtherDevicesStatus(0xf3, "Read Other Devices Status", PumpHistoryEntryGroup.Configuration), // ? + + TempBasalCombined(0xfe, "TBR", PumpHistoryEntryGroup.Basal), // + UnknownBasePacket(0xff, "Unknown Base Packet", PumpHistoryEntryGroup.Unknown); + + private static Map opCodeMap = new HashMap(); + private static PumpHistoryEntryType tddType; + + static { + for (PumpHistoryEntryType type : values()) { + opCodeMap.put(type.opCode, type); + } + + setSpecialRulesForEntryTypes(); + } + + private int opCode; + private String description; + private int headLength = 0; + private int dateLength; + // private MinimedDeviceType deviceType; + private int bodyLength; + private int totalLength; + // special rules need to be put in list from highest to lowest (e.g.: + // 523andHigher=12, 515andHigher=10 and default (set in cnstr) would be 8) + private List specialRulesHead; + private List specialRulesBody; + private boolean hasSpecialRules = false; + private PumpHistoryEntryGroup group = PumpHistoryEntryGroup.Unknown; + private static Object TDDType; + + + PumpHistoryEntryType(int opCode, String name, PumpHistoryEntryGroup group) { + this(opCode, name, group, 2, 5, 0); + } + + + PumpHistoryEntryType(int opCode, PumpHistoryEntryGroup group) { + this(opCode, null, group, 2, 5, 0); + } + + + PumpHistoryEntryType(int opCode, PumpHistoryEntryGroup group, int head, int date, int body) { + this(opCode, null, group, head, date, body); + } + + + PumpHistoryEntryType(int opCode, String name, PumpHistoryEntryGroup group, int head, int date, int body) { + this.opCode = (byte) opCode; + this.description = name; + this.headLength = head; + this.dateLength = date; + this.bodyLength = body; + this.totalLength = (head + date + body); + this.group = group; + } + + + static void setSpecialRulesForEntryTypes() { + EndResultTotals.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 3)); + Bolus.addSpecialRuleHead(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 8)); + // BolusWizardChange.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_522andHigher, 143)); + //ChangeBolusWizardSetup.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 137)); // V5: + // 522 + // has + // old + // form + BolusWizard.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 15)); + BolusReminder.addSpecialRuleBody(new SpecialRule(MedtronicDeviceType.Medtronic_523andHigher, 2)); + } + + + public static PumpHistoryEntryType getByCode(int opCode) { + if (opCodeMap.containsKey(opCode)) { + return opCodeMap.get(opCode); + } else { + return PumpHistoryEntryType.UnknownBasePacket; + } + } + + + // + // private PumpHistoryEntryType(int opCode, String name, int head, int date, + // int body) + // { + // this.opCode = (byte) opCode; + // this.description = name; + // this.headLength = head; + // this.dateLength = date; + // this.bodyLength = body; + // this.totalLength = (head + date + body); + // } + // + + public static boolean isAAPSRelevantEntry(PumpHistoryEntryType entryType) { + return (entryType == PumpHistoryEntryType.Bolus || // Treatments + entryType == PumpHistoryEntryType.TempBasalRate || // + entryType == PumpHistoryEntryType.TempBasalDuration || // + + entryType == PumpHistoryEntryType.Prime || // Pump Status Change + entryType == PumpHistoryEntryType.Suspend || // + entryType == PumpHistoryEntryType.Resume || // + entryType == PumpHistoryEntryType.Rewind || // + entryType == PumpHistoryEntryType.NoDeliveryAlarm || // no delivery + entryType == PumpHistoryEntryType.BasalProfileStart || // + + entryType == PumpHistoryEntryType.ChangeTime || // Time Change + entryType == PumpHistoryEntryType.NewTimeSet || // + + entryType == PumpHistoryEntryType.ChangeBasalPattern || // Configuration + entryType == PumpHistoryEntryType.ClearSettings || // + entryType == PumpHistoryEntryType.SaveSettings || // + entryType == PumpHistoryEntryType.ChangeMaxBolus || // + entryType == PumpHistoryEntryType.ChangeMaxBasal || // + entryType == PumpHistoryEntryType.ChangeTempBasalType || // + + entryType == PumpHistoryEntryType.ChangeBasalProfile_NewProfile || // Basal profile + + entryType == PumpHistoryEntryType.DailyTotals515 || // Daily Totals + entryType == PumpHistoryEntryType.DailyTotals522 || // + entryType == PumpHistoryEntryType.DailyTotals523 || // + entryType == PumpHistoryEntryType.EndResultTotals); + } + + + public static boolean isRelevantEntry() { + return true; + } + + + public int getCode() { + return this.opCode; + } + + + public int getTotalLength() { + if (hasSpecialRules()) { + return getHeadLength() + getBodyLength() + getDateLength(); + } else { + return totalLength; + } + } + + + private boolean hasSpecialRules() { + return hasSpecialRules; + } + + + void addSpecialRuleHead(SpecialRule rule) { + if (isEmpty(specialRulesHead)) { + specialRulesHead = new ArrayList(); + } + + specialRulesHead.add(rule); + hasSpecialRules = true; + } + + + void addSpecialRuleBody(SpecialRule rule) { + if (isEmpty(specialRulesBody)) { + specialRulesBody = new ArrayList(); + } + + specialRulesBody.add(rule); + hasSpecialRules = true; + } + + + public int getOpCode() { + return opCode; + } + + + public String getDescription() { + return this.description == null ? name() : this.description; + } + + + public int getHeadLength() { + if (hasSpecialRules) { + if (isNotEmpty(specialRulesHead)) { + return determineSizeByRule(headLength, specialRulesHead); + } else { + return headLength; + } + } else { + return headLength; + } + } + + + public int getDateLength() { + return dateLength; + } + + + public int getBodyLength() { + if (hasSpecialRules) { + if (isNotEmpty(specialRulesBody)) { + return determineSizeByRule(bodyLength, specialRulesBody); + } else { + return bodyLength; + } + } else { + return bodyLength; + } + } + + + private boolean isNotEmpty(List list) { + return list != null && !list.isEmpty(); + } + + + private boolean isEmpty(List list) { + return list == null || list.isEmpty(); + } + + + // byte[] dh = { 2, 3 }; + + private int determineSizeByRule(int defaultValue, List rules) { + int size = defaultValue; + + for (SpecialRule rule : rules) { + if (MedtronicDeviceType.isSameDevice(MedtronicUtil.getMedtronicPumpModel(), rule.deviceType)) { + size = rule.size; + break; + } + } + + return size; + } + + + public PumpHistoryEntryGroup getGroup() { + + return group; + } + + enum DateFormat { + None(0), // + LongDate(5), // + ShortDate(2); + + private int length; + + + DateFormat(int length) { + this.length = length; + } + + + public int getLength() { + return length; + } + + + public void setLength(int length) { + this.length = length; + } + } + + public static class SpecialRule { + + MedtronicDeviceType deviceType; + int size; + + + public SpecialRule(MedtronicDeviceType deviceType, int size) { + this.deviceType = deviceType; + this.size = size; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryResult.java new file mode 100644 index 00000000000..58c9b77d6b8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/history/pump/PumpHistoryResult.java @@ -0,0 +1,187 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; + +/** + * History page contains data, sorted from newest to oldest (0=newest..n=oldest) + * + * Created by andy on 9/23/18. + */ +public class PumpHistoryResult { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private boolean searchFinished = false; + private PumpHistoryEntry searchEntry = null; + private Long searchDate = null; + private SearchType searchType = SearchType.None; + public List unprocessedEntries; + public List validEntries; + + + public PumpHistoryResult(PumpHistoryEntry searchEntry, Long targetDate) { + if (searchEntry != null) { + /* + * this.searchEntry = searchEntry; + * this.searchType = SearchType.LastEntry; + * LOG.debug("PumpHistoryResult. Search parameters: Last Entry: " + searchEntry.atechDateTime + " type=" + * + searchEntry.getEntryType().name()); + */ + this.searchDate = searchEntry.atechDateTime; + this.searchType = SearchType.Date; + if (isLogEnabled()) + LOG.debug("PumpHistoryResult. Search parameters: Date(with searchEntry): " + targetDate); + } else if (targetDate != null) { + this.searchDate = targetDate; + this.searchType = SearchType.Date; + if (isLogEnabled()) + LOG.debug("PumpHistoryResult. Search parameters: Date: " + targetDate); + } + + // this.unprocessedEntries = new ArrayList<>(); + this.validEntries = new ArrayList<>(); + } + + + public void addHistoryEntries(List entries, int page) { + this.unprocessedEntries = entries; + //LOG.debug("PumpHistoryResult. Unprocessed entries: {}", MedtronicUtil.getGsonInstance().toJson(entries)); + processEntries(); + } + + // TODO Bug #145 need to check if we had timeChange that went -1, that situation needs to be evaluated separately + public void processEntries() { + int olderEntries = 0; + + Collections.reverse(this.unprocessedEntries); + + switch (searchType) { + case None: + //LOG.debug("PE. None search"); + this.validEntries.addAll(this.unprocessedEntries); + break; + + case LastEntry: { + LOG.debug("PE. Last entry search"); + + //Collections.sort(this.unprocessedEntries, new PumpHistoryEntry.Comparator()); + + LOG.debug("PE. PumpHistoryResult. Search entry date: " + searchEntry.atechDateTime); + + Long date = searchEntry.atechDateTime; + + for (PumpHistoryEntry unprocessedEntry : unprocessedEntries) { + + if (unprocessedEntry.equals(searchEntry)) { + //LOG.debug("PE. Item found {}.", unprocessedEntry); + searchFinished = true; + break; + } + + //LOG.debug("PE. Entry {} added.", unprocessedEntry); + this.validEntries.add(unprocessedEntry); + } + } + break; + case Date: { + LOG.debug("PE. Date search: Search date: {}", this.searchDate); + + + for (PumpHistoryEntry unprocessedEntry : unprocessedEntries) { + + if (unprocessedEntry.atechDateTime == null || unprocessedEntry.atechDateTime == 0) { + LOG.debug("PE. PumpHistoryResult. Search entry date: Entry with no date: {}", unprocessedEntry); + continue; + } + + if (unprocessedEntry.isAfter(this.searchDate)) { + this.validEntries.add(unprocessedEntry); + } else { +// LOG.debug("PE. PumpHistoryResult. Not after.. Unprocessed Entry [year={},entry={}]", +// DateTimeUtil.getYear(unprocessedEntry.atechDateTime), unprocessedEntry); + if (DateTimeUtil.getYear(unprocessedEntry.atechDateTime) > 2015) + olderEntries++; + } + } + + if (olderEntries > 0) { + //Collections.sort(this.validEntries, new PumpHistoryEntry.Comparator()); + + searchFinished = true; + } + } + break; + + } // switch + + //LOG.debug("PE. Valid Entries: {}", validEntries); + } + + + public String toString() { + return "PumpHistoryResult [unprocessed=" + (unprocessedEntries != null ? "" + unprocessedEntries.size() : "0") + // + ", valid=" + (validEntries != null ? "" + validEntries.size() : "0") + // + ", searchEntry=" + searchEntry + // + ", searchDate=" + searchDate + // + ", searchType=" + searchType + // + ", searchFinished=" + searchFinished + // + "]"; + + } + + + /** + * Return latest entry (entry with highest date time) + * + * @return + */ + public PumpHistoryEntry getLatestEntry() { + if (this.validEntries == null || this.validEntries.size() == 0) + return null; + else { + return this.validEntries.get(0); + // PumpHistoryEntry pumpHistoryEntry = this.validEntries.get(0); + // + // if (pumpHistoryEntry.getEntryType() == PumpHistoryEntryType.EndResultTotals) + // return pumpHistoryEntry; + // else + // return this.validEntries.get(1); + } + } + + + public boolean isSearchRequired() { + return searchType != SearchType.None; + } + + + public boolean isSearchFinished() { + return searchFinished; + } + + + public List getValidEntries() { + return validEntries; + } + + enum SearchType { + None, // + LastEntry, // + Date + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkLongMessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkLongMessageBody.java new file mode 100644 index 00000000000..67b773999e3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkLongMessageBody.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by geoff on 6/2/16. + */ +public class CarelinkLongMessageBody extends MessageBody { + + public static final int LONG_MESSAGE_BODY_LENGTH = 65; + protected byte[] data; + + + public CarelinkLongMessageBody() { + init(new byte[0]); + } + + + public CarelinkLongMessageBody(byte[] payload) { + init(payload); + } + + + public CarelinkLongMessageBody(List payload) { + init(MedtronicUtil.createByteArray(payload)); + } + + + @Override + public void init(byte[] rxData) { + + if (rxData != null && rxData.length == LONG_MESSAGE_BODY_LENGTH) { + data = rxData; + } else { + data = new byte[LONG_MESSAGE_BODY_LENGTH]; + if (rxData != null) { + int size = rxData.length < LONG_MESSAGE_BODY_LENGTH ? rxData.length : LONG_MESSAGE_BODY_LENGTH; + for (int i = 0; i < size; i++) { + data[i] = rxData[i]; + } + } + } + } + + + @Override + public int getLength() { + return LONG_MESSAGE_BODY_LENGTH; + } + + + @Override + public byte[] getTxData() { + return data; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkShortMessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkShortMessageBody.java new file mode 100644 index 00000000000..e2ebbff0853 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/CarelinkShortMessageBody.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +/** + * Created by geoff on 5/29/16. + */ +// Andy: See comments in message body +public class CarelinkShortMessageBody extends MessageBody { + + byte[] body; + + + public CarelinkShortMessageBody() { + init(new byte[] { 0 }); + } + + + public CarelinkShortMessageBody(byte[] data) { + init(data); + } + + + @Override + public int getLength() { + return body.length; + } + + + @Override + public void init(byte[] rxData) { + body = rxData; + } + + + public byte[] getRxData() { + return body; + } + + + public void setRxData(byte[] rxData) { + init(rxData); + } + + + @Override + public byte[] getTxData() { + return body; + } + + + public void setTxData(byte[] txData) { + init(txData); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/GetHistoryPageCarelinkMessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/GetHistoryPageCarelinkMessageBody.java new file mode 100644 index 00000000000..25e8aa087c5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/GetHistoryPageCarelinkMessageBody.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +/** + * Created by geoff on 6/2/16. + */ +public class GetHistoryPageCarelinkMessageBody extends CarelinkLongMessageBody { + + // public boolean wasLastFrame = false; + // public int frameNumber = 0; + // public byte[] frame = new byte[] {}; + + public GetHistoryPageCarelinkMessageBody(byte[] frameData) { + init(frameData); + } + + + public GetHistoryPageCarelinkMessageBody(int pageNum) { + init(pageNum); + } + + + @Override + public int getLength() { + return data.length; + } + + + @Override + public void init(byte[] rxData) { + super.init(rxData); + } + + + public void init(int pageNum) { + byte numArgs = 1; + super.init(new byte[] { numArgs, (byte)pageNum }); + } + + + public int getFrameNumber() { + if (data.length > 0) { + return data[0] & 0x7f; + } + return 255; + } + + + public boolean wasLastFrame() { + return (data[0] & 0x80) != 0; + } + + + public byte[] getFrameData() { + return ByteUtil.substring(data, 1, data.length - 1); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/MessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/MessageBody.java new file mode 100644 index 00000000000..fa4a3817bef --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/MessageBody.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +/** + * Created by geoff on 5/29/16. + */ +public class MessageBody { + + public int getLength() { + return 0; + } + + + public void init(byte[] rxData) { + } + + + public byte[] getTxData() { + return new byte[]{}; + } + + + public String toString() { + StringBuilder sb = new StringBuilder(getClass().getSimpleName()); + + sb.append(" [txData="); + sb.append(ByteUtil.shortHexString(getTxData())); + sb.append("]"); + + return sb.toString(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PacketType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PacketType.java new file mode 100644 index 00000000000..8d02063d66e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PacketType.java @@ -0,0 +1,47 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +import java.util.HashMap; +import java.util.Map; + +/** + * Created by geoff on 5/29/16. + * refactored into enum + */ +public enum PacketType { + Invalid(0x00), // + MySentry(0xa2), // + Meter(0xa5), // + Carelink(0xa7), // + Sensor(0xa8) // + ; + + public static Map mapByValue; + + static { + mapByValue = new HashMap<>(); + + for (PacketType packetType : values()) { + mapByValue.put(packetType.value, packetType); + } + } + + private byte value = 0; + + + PacketType(int value) { + this.value = (byte)value; + } + + + public static PacketType getByValue(short value) { + if (mapByValue.containsKey(value)) + return mapByValue.get(value); + else + return PacketType.Invalid; + } + + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpAckMessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpAckMessageBody.java new file mode 100644 index 00000000000..4cfda089759 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpAckMessageBody.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +/** + * Created by geoff on 5/29/16. + */ +public class PumpAckMessageBody extends CarelinkShortMessageBody { + + public PumpAckMessageBody() { + init(new byte[] { 0 }); + } + + + public PumpAckMessageBody(byte[] bodyData) { + init(bodyData); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpMessage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpMessage.java new file mode 100644 index 00000000000..4d81d81bfeb --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/PumpMessage.java @@ -0,0 +1,219 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; + +/** + * Created by geoff on 5/29/16. + */ +public class PumpMessage implements RLMessage { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public PacketType packetType = PacketType.Carelink; + public byte[] address = new byte[]{0, 0, 0}; + public MedtronicCommandType commandType; + public Byte invalidCommandType; + public MessageBody messageBody = new MessageBody(); + public String error = null; + + public static final int FRAME_DATA_LENGTH = 64; + + + public PumpMessage(String error) { + this.error = error; + } + + + public PumpMessage(byte[] rxData) { + init(rxData); + } + + + public PumpMessage() { + + } + + + public boolean isErrorResponse() { + return (this.error != null); + } + + + public void init(PacketType packetType, byte[] address, MedtronicCommandType commandType, MessageBody messageBody) { + this.packetType = packetType; + this.address = address; + this.commandType = commandType; + this.messageBody = messageBody; + } + + + public void init(byte[] rxData) { + if (rxData == null) { + return; + } + if (rxData.length > 0) { + this.packetType = PacketType.getByValue(rxData[0]); + } + if (rxData.length > 3) { + this.address = ByteUtil.substring(rxData, 1, 3); + } + if (rxData.length > 4) { + this.commandType = MedtronicCommandType.getByCode(rxData[4]); + if (this.commandType == MedtronicCommandType.InvalidCommand) { + if (isLogEnabled()) + LOG.error("PumpMessage - Unknown commandType " + rxData[4]); + } + } + if (rxData.length > 5) { + this.messageBody = MedtronicCommandType.constructMessageBody(commandType, + ByteUtil.substring(rxData, 5, rxData.length - 5)); + } + } + + + @Override + public byte[] getTxData() { + byte[] rval = ByteUtil.concat(new byte[]{(byte) packetType.getValue()}, address); + rval = ByteUtil.concat(rval, commandType.getCommandCode()); + rval = ByteUtil.concat(rval, messageBody.getTxData()); + return rval; + } + + + public byte[] getContents() { + return ByteUtil.concat(new byte[]{commandType.getCommandCode()}, messageBody.getTxData()); + } + + + // rawContent = just response without code (contents-2, messageBody.txData-1); + public byte[] getRawContent() { + + if ((messageBody == null) || (messageBody.getTxData() == null) || (messageBody.getTxData().length == 0)) + return null; + + byte[] data = messageBody.getTxData(); + + int length = ByteUtil.asUINT8(data[0]); // length is not always correct so, we check whole array if we have + // data, after length + int originalLength = length; + + // check if displayed length is invalid + if (length > data.length - 1) { + return data; + } + + // check Old Way + boolean oldWay = false; + for (int i = (length + 1); i < data.length; i++) { + if (data[i] != 0x00) { + oldWay = true; + } + } + + if (oldWay) { + length = data.length - 1; + } + + byte[] arrayOut = new byte[length]; + + System.arraycopy(messageBody.getTxData(), 1, arrayOut, 0, length); + +// if (isLogEnabled()) +// LOG.debug("PumpMessage - Length: " + length + ", Original Length: " + originalLength + ", CommandType: " +// + commandType); + + return arrayOut; + } + + + public byte[] getRawContentOfFrame() { + byte[] raw = messageBody.getTxData(); + return ByteUtil.substring(raw, 1, Math.min(FRAME_DATA_LENGTH, raw.length - 1)); + } + + + public boolean isValid() { + if (packetType == null) + return false; + if (address == null) + return false; + if (commandType == null) + return false; + if (messageBody == null) + return false; + return true; + } + + + public MessageBody getMessageBody() { + return messageBody; + } + + + public String getResponseContent() { + StringBuilder sb = new StringBuilder("PumpMessage [response="); + boolean showData = true; + + if (commandType != null) { + if (commandType == MedtronicCommandType.CommandACK) { + sb.append("Acknowledged"); + showData = false; + } else if (commandType == MedtronicCommandType.CommandNAK) { + sb.append("NOT Acknowledged"); + showData = false; + } else { + sb.append(commandType.name()); + } + } else { + sb.append("Unknown_Type"); + sb.append(" (" + invalidCommandType + ")"); + } + + if (showData) { + sb.append(", rawResponse="); + sb.append(ByteUtil.shortHexString(getRawContent())); + } + + sb.append("]"); + + return sb.toString(); + } + + + public String toString() { + StringBuilder sb = new StringBuilder("PumpMessage ["); + + sb.append("packetType="); + sb.append(packetType == null ? "null" : packetType.name()); + + sb.append(", address=("); + sb.append(ByteUtil.shortHexString(this.address)); + + sb.append("), commandType="); + sb.append(commandType == null ? "null" : commandType.name()); + + if (invalidCommandType != null) { + sb.append(", invalidCommandType="); + sb.append(invalidCommandType); + } + + sb.append(", messageBody=("); + sb.append(this.messageBody == null ? "null" : this.messageBody); + + sb.append(")]"); + + return sb.toString(); + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/UnknownMessageBody.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/UnknownMessageBody.java new file mode 100644 index 00000000000..675a794bd86 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/message/UnknownMessageBody.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.message; + +/** + * Created by geoff on 5/29/16. + */ +public class UnknownMessageBody extends MessageBody { + + public byte[] rxData; + + + public UnknownMessageBody(byte[] data) { + this.rxData = data; + } + + + @Override + public int getLength() { + return 0; + } + + + @Override + public void init(byte[] rxData) { + } + + + public byte[] getRxData() { + return rxData; + } + + + public void setRxData(byte[] rxData) { + this.rxData = rxData; + } + + + @Override + public byte[] getTxData() { + return rxData; + } + + + public void setTxData(byte[] txData) { + this.rxData = txData; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java new file mode 100644 index 00000000000..35fa1841477 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java @@ -0,0 +1,109 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by andy on 6/14/18. + */ +public class MedtronicUIComm { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + MedtronicCommunicationManager mcmInstance = null; + MedtronicUIPostprocessor uiPostprocessor = new MedtronicUIPostprocessor(); + + + private MedtronicCommunicationManager getCommunicationManager() { + if (mcmInstance == null) { + mcmInstance = MedtronicCommunicationManager.getInstance(); + } + + return mcmInstance; + } + + + public synchronized MedtronicUITask executeCommand(MedtronicCommandType commandType, Object... parameters) { + + if (isLogEnabled()) + LOG.warn("Execute Command: " + commandType.name()); + + MedtronicUITask task = new MedtronicUITask(commandType, parameters); + + MedtronicUtil.setCurrentCommand(commandType); + + // new Thread(() -> { + // LOG.warn("@@@ Start Thread"); + // + // task.execute(getCommunicationManager()); + // + // LOG.warn("@@@ End Thread"); + // }); + + task.execute(getCommunicationManager()); + + // for (int i = 0; i < getMaxWaitTime(commandType); i++) { + // synchronized (task) { + // // try { + // // + // // //task.wait(1000); + // // } catch (InterruptedException e) { + // // LOG.error("executeCommand InterruptedException", e); + // // } + // + // + // SystemClock.sleep(1000); + // } + // + // if (task.isReceived()) { + // break; + // } + // } + + if (!task.isReceived() && isLogEnabled()) { + LOG.warn("Reply not received for " + commandType); + } + + task.postProcess(uiPostprocessor); + + return task; + + } + + + /** + * We return 25s as waitTime (17 for wakeUp, and addtional 8 for data retrieval) for normal commands and + * 120s for History. Real time for returning data would be arround 5s, but lets be sure. + * + * @param commandType + * @return + */ + private int getMaxWaitTime(MedtronicCommandType commandType) { + if (commandType == MedtronicCommandType.GetHistoryData) + return 120; + else + return 25; + } + + + public int getInvalidResponsesCount() { + return getCommunicationManager().getNotConnectedCount(); + } + + + public void startTunning() { + RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump); + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIPostprocessor.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIPostprocessor.java new file mode 100644 index 00000000000..e79531d4fca --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIPostprocessor.java @@ -0,0 +1,253 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui; + +import org.joda.time.DateTimeZone; +import org.joda.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BatteryStatusDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BasalProfileStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +import static info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil.sendNotification; + +/** + * Created by andy on 6/15/18. + */ + +public class MedtronicUIPostprocessor { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + MedtronicPumpStatus pumpStatus; + + + public MedtronicUIPostprocessor() { + pumpStatus = MedtronicUtil.getPumpStatus(); + } + + + // this is mostly intended for command that return certain statuses (Remaining Insulin, ...), and + // where responses won't be directly used + public void postProcessData(MedtronicUITask uiTask) { + + switch (uiTask.commandType) { + + case SetBasalProfileSTD: { + Boolean response = (Boolean) uiTask.returnData; + + if (response) { + BasalProfile basalProfile = (BasalProfile) uiTask.getParameter(0); + + pumpStatus.basalsByHour = basalProfile.getProfilesByHour(); + } + } + break; + + case GetBasalProfileSTD: { + BasalProfile basalProfile = (BasalProfile) uiTask.returnData; + + try { + Double[] profilesByHour = basalProfile.getProfilesByHour(); + + if (profilesByHour != null) { + pumpStatus.basalsByHour = profilesByHour; + pumpStatus.basalProfileStatus = BasalProfileStatus.ProfileOK; + } else { + uiTask.responseType = MedtronicUIResponseType.Error; + uiTask.errorDescription = "No profile found."; + LOG.error("Basal Profile was NOT valid. [{}]", basalProfile.basalProfileToStringError()); + } + } catch (Exception ex) { + LOG.error("Basal Profile was returned, but was invalid. [{}]", basalProfile.basalProfileToStringError()); + uiTask.responseType = MedtronicUIResponseType.Error; + uiTask.errorDescription = "No profile found."; + } + } + break; + + case SetBolus: { + pumpStatus.lastBolusAmount = uiTask.getDoubleFromParameters(0); + pumpStatus.lastBolusTime = new Date(); + } + break; + + case GetRemainingInsulin: { + pumpStatus.reservoirRemainingUnits = (Float) uiTask.returnData; + } + break; + + case CancelTBR: { + pumpStatus.tempBasalStart = null; + pumpStatus.tempBasalAmount = null; + pumpStatus.tempBasalLength = null; + } + break; + + case GetRealTimeClock: { + processTime(uiTask); + } + break; + + case SetRealTimeClock: { + boolean response = (Boolean) uiTask.returnData; + + if (isLogEnabled()) + LOG.debug("New time was {} set.", response ? "" : "NOT"); + + if (response) { + MedtronicUtil.getPumpTime().timeDifference = 0; + } + } + break; + + + case GetBatteryStatus: { + BatteryStatusDTO batteryStatusDTO = (BatteryStatusDTO) uiTask.returnData; + + pumpStatus.batteryRemaining = batteryStatusDTO.getCalculatedPercent(pumpStatus.batteryType); + + if (batteryStatusDTO.voltage != null) { + pumpStatus.batteryVoltage = batteryStatusDTO.voltage; + } + + if (isLogEnabled()) + LOG.info("BatteryStatus: {}", batteryStatusDTO.toString()); + + } + break; + + case PumpModel: { + if (pumpStatus.medtronicDeviceType != MedtronicUtil.getMedtronicPumpModel()) { + if (isLogEnabled()) + LOG.warn("Configured pump is different then pump detected !"); + sendNotification(MedtronicNotificationType.PumpTypeNotSame); + } + } + break; + + case Settings_512: + case Settings: { + postProcessSettings(uiTask); + } + break; + + // no postprocessing + + default: + if (isLogEnabled()) + LOG.trace("Post-processing not implemented for {}.", uiTask.commandType.name()); + + } + + } + + + private void processTime(MedtronicUITask uiTask) { + + ClockDTO clockDTO = (ClockDTO) uiTask.returnData; + + Duration dur = new Duration(clockDTO.pumpTime.toDateTime(DateTimeZone.UTC), + clockDTO.localDeviceTime.toDateTime(DateTimeZone.UTC)); + + clockDTO.timeDifference = (int) dur.getStandardSeconds(); + + MedtronicUtil.setPumpTime(clockDTO); + + if (isLogEnabled()) + LOG.debug("Pump Time: " + clockDTO.localDeviceTime + ", DeviceTime=" + clockDTO.pumpTime + // + ", diff: " + dur.getStandardSeconds() + " s"); + +// if (dur.getStandardMinutes() >= 10) { +// if (isLogEnabled()) +// LOG.warn("Pump clock needs update, pump time: " + clockDTO.pumpTime.toString("HH:mm:ss") + " (difference: " +// + dur.getStandardSeconds() + " s)"); +// sendNotification(MedtronicNotificationType.PumpWrongTimeUrgent); +// } else if (dur.getStandardMinutes() >= 4) { +// if (isLogEnabled()) +// LOG.warn("Pump clock needs update, pump time: " + clockDTO.pumpTime.toString("HH:mm:ss") + " (difference: " +// + dur.getStandardSeconds() + " s)"); +// sendNotification(MedtronicNotificationType.PumpWrongTimeNormal); +// } + + } + + + private void postProcessSettings(MedtronicUITask uiTask) { + + Map settings = (Map) uiTask.returnData; + + MedtronicUtil.setSettings(settings); + + PumpSettingDTO checkValue = null; + + if (pumpStatus == null) { + if (isLogEnabled()) + LOG.debug("Pump Status: was null"); + pumpStatus = MedtronicUtil.getPumpStatus(); + if (isLogEnabled()) + LOG.debug("Pump Status: " + this.pumpStatus); + } + + this.pumpStatus.verifyConfiguration(); + + // check profile + if (!"Yes".equals(settings.get("PCFG_BASAL_PROFILES_ENABLED").value)) { + if (isLogEnabled()) + LOG.error("Basal profiles are not enabled on pump."); + sendNotification(MedtronicNotificationType.PumpBasalProfilesNotEnabled); + + } else { + checkValue = settings.get("PCFG_ACTIVE_BASAL_PROFILE"); + + if (!"STD".equals(checkValue.value)) { + if (isLogEnabled()) + LOG.error("Basal profile set on pump is incorrect (must be STD)."); + sendNotification(MedtronicNotificationType.PumpIncorrectBasalProfileSelected); + } + } + + // TBR + + checkValue = settings.get("PCFG_TEMP_BASAL_TYPE"); + + if (!"Units".equals(checkValue.value)) { + if (isLogEnabled()) + LOG.error("Wrong TBR type set on pump (must be Absolute)."); + sendNotification(MedtronicNotificationType.PumpWrongTBRTypeSet); + } + + // MAXes + + checkValue = settings.get("PCFG_MAX_BOLUS"); + + if (!MedtronicUtil.isSame(Double.parseDouble(checkValue.value), pumpStatus.maxBolus)) { + LOG.error("Wrong Max Bolus set on Pump (current={}, required={}).", checkValue.value, pumpStatus.maxBolus); + sendNotification(MedtronicNotificationType.PumpWrongMaxBolusSet, pumpStatus.maxBolus); + } + + checkValue = settings.get("PCFG_MAX_BASAL"); + + if (!MedtronicUtil.isSame(Double.parseDouble(checkValue.value), pumpStatus.maxBasal)) { + if (isLogEnabled()) + LOG.error("Wrong Max Basal set on Pump (current={}, required={}).", checkValue.value, pumpStatus.maxBasal); + sendNotification(MedtronicNotificationType.PumpWrongMaxBasalSet, pumpStatus.maxBasal); + } + + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUITask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUITask.java new file mode 100644 index 00000000000..d6df56bb57b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUITask.java @@ -0,0 +1,228 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicUIResponseType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by andy on 6/14/18. + */ + +public class MedtronicUITask { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public MedtronicCommandType commandType; + public Object returnData; + String errorDescription; + // boolean invalid = false; + private Object[] parameters; + // private boolean received; + MedtronicUIResponseType responseType; + + + public MedtronicUITask(MedtronicCommandType commandType) { + this.commandType = commandType; + } + + + public MedtronicUITask(MedtronicCommandType commandType, Object... parameters) { + this.commandType = commandType; + this.parameters = parameters; + } + + + public void execute(MedtronicCommunicationManager communicationManager) { + + if (isLogEnabled()) + LOG.debug("MedtronicUITask: @@@ In execute. {}", commandType); + + switch (commandType) { + case PumpModel: { + returnData = communicationManager.getPumpModel(); + } + break; + + case GetBasalProfileSTD: { + returnData = communicationManager.getBasalProfile(); + } + break; + + case GetRemainingInsulin: { + returnData = communicationManager.getRemainingInsulin(); + } + break; + + case GetRealTimeClock: { + returnData = communicationManager.getPumpTime(); + MedtronicUtil.setPumpTime(null); + } + break; + + case SetRealTimeClock: { + returnData = communicationManager.setPumpTime(); + } + break; + + case GetBatteryStatus: { + returnData = communicationManager.getRemainingBattery(); + } + break; + + case SetTemporaryBasal: { + TempBasalPair tbr = getTBRSettings(); + if (tbr != null) { + returnData = communicationManager.setTBR(tbr); + } + } + break; + + case ReadTemporaryBasal: { + returnData = communicationManager.getTemporaryBasal(); + } + break; + + + case Settings: + case Settings_512: { + returnData = communicationManager.getPumpSettings(); + } + break; + + case SetBolus: { + Double amount = getDoubleFromParameters(0); + + if (amount != null) + returnData = communicationManager.setBolus(amount); + } + break; + + case CancelTBR: { + returnData = communicationManager.cancelTBR(); + } + break; + + case SetBasalProfileSTD: + case SetBasalProfileA: { + BasalProfile profile = (BasalProfile) parameters[0]; + + returnData = communicationManager.setBasalProfile(profile); + } + break; + + case GetHistoryData: { + returnData = communicationManager.getPumpHistory((PumpHistoryEntry) parameters[0], + (LocalDateTime) parameters[1]); + } + break; + + default: { + LOG.warn("This commandType is not supported (yet) - {}.", commandType); + // invalid = true; + responseType = MedtronicUIResponseType.Invalid; + } + + } + + if (responseType == null) { + if (returnData == null) { + errorDescription = communicationManager.getErrorResponse(); + this.responseType = MedtronicUIResponseType.Error; + } else { + this.responseType = MedtronicUIResponseType.Data; + } + } + + } + + + private TempBasalPair getTBRSettings() { + return new TempBasalPair(getDoubleFromParameters(0), // + false, // + getIntegerFromParameters(1)); + } + + + private Float getFloatFromParameters(int index) { + return (Float) parameters[index]; + } + + + public Double getDoubleFromParameters(int index) { + return (Double) parameters[index]; + } + + + public Integer getIntegerFromParameters(int index) { + return (Integer) parameters[index]; + } + + + public Object getResult() { + return returnData; + } + + + public boolean isReceived() { + return (returnData != null || errorDescription != null); + } + + + void postProcess(MedtronicUIPostprocessor postprocessor) { + + if (isLogEnabled()) + LOG.debug("MedtronicUITask: @@@ In execute. {}", commandType); + + if (responseType == MedtronicUIResponseType.Data) { + postprocessor.postProcessData(this); + } + + if (responseType == MedtronicUIResponseType.Invalid) { + RxBus.INSTANCE.send(new EventMedtronicDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating, + "Unsupported command in MedtronicUITask")); + } else if (responseType == MedtronicUIResponseType.Error) { + RxBus.INSTANCE.send(new EventMedtronicDeviceStatusChange(PumpDeviceState.ErrorWhenCommunicating, + errorDescription)); + } else { + RxBus.INSTANCE.send(new EventMedtronicPumpValuesChanged()); + MedtronicUtil.getPumpStatus().setLastCommunicationToNow(); + } + + MedtronicUtil.setCurrentCommand(null); + } + + + public boolean hasData() { + return (responseType == MedtronicUIResponseType.Data); + } + + + public Object getParameter(int index) { + return parameters[index]; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + + public MedtronicUIResponseType getResponseType() { + return this.responseType; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java new file mode 100644 index 00000000000..39c44167a22 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/MedtronicHistoryData.java @@ -0,0 +1,1569 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.LocalDateTime; +import org.joda.time.Minutes; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.db.DbObjectBase; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TDD; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.plugins.pump.common.bolusInfo.DetailedBolusInfoStorage; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.MedtronicPumpHistoryDecoder; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntryType; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryResult; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BasalProfile; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.BolusWizardDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.DailyTotalsDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.TempBasalProcessDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.plugins.treatments.Treatment; +import info.nightscout.androidaps.plugins.treatments.TreatmentService; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.SP; + + +/** + * Created by andy on 10/12/18. + */ + +// TODO: After release we need to refactor how data is retrieved from pump, each entry in history needs to be marked, and sorting +// needs to happen according those markings, not on time stamp (since AAPS can change time anytime it drifts away). This +// needs to include not returning any records if TZ goes into -x area. To fully support this AAPS would need to take note of +// all times that time changed (TZ, DST, etc.). Data needs to be returned in batches (time_changed batches, so that we can +// handle it. It would help to assign sort_ids to items (from oldest (1) to newest (x) + +// All things marked with "TODO: Fix db code" needs to be updated in new 2.5 database code + +public class MedtronicHistoryData { + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + private List allHistory = null; + private List newHistory = null; + + private Long lastHistoryRecordTime; + private boolean isInit = false; + + private Gson gson; + private Gson gsonCore; + + private DatabaseHelper databaseHelper = MainApp.getDbHelper(); + private ClockDTO pumpTime; + + private long lastIdUsed = 0; + + /** + * Double bolus debug. We seem to have small problem with double Boluses (or sometimes also missing boluses + * from history. This flag turns on debugging for that (default is off=false)... Debuging is pretty detailed, + * so log files will get bigger. + */ + public static boolean doubleBolusDebug = false; + + + public MedtronicHistoryData() { + this.allHistory = new ArrayList<>(); + this.gson = MedtronicUtil.gsonInstance; + this.gsonCore = MedtronicUtil.getGsonInstanceCore(); + + if (this.gson == null) { + this.gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + } + + if (this.gsonCore == null) { + this.gsonCore = new GsonBuilder().create(); + } + } + + + /** + * Add New History entries + * + * @param result PumpHistoryResult instance + */ + public void addNewHistory(PumpHistoryResult result) { + + List validEntries = result.getValidEntries(); + + List newEntries = new ArrayList<>(); + + for (PumpHistoryEntry validEntry : validEntries) { + + if (!this.allHistory.contains(validEntry)) { + newEntries.add(validEntry); + } + } + + this.newHistory = newEntries; + + showLogs("List of history (before filtering): [" + this.newHistory.size() + "]", gson.toJson(this.newHistory)); + } + + + private static void showLogs(String header, String data) { + + if (!isLogEnabled()) + return; + + if (header != null) { + LOG.debug(header); + } + + if (StringUtils.isNotBlank(data)) { + for (final String token : StringUtil.splitString(data, 3500)) { + LOG.debug("{}", token); + } + } else { + LOG.debug("No data."); + } + } + + + public List getAllHistory() { + return this.allHistory; + } + + + public void filterNewEntries() { + + List newHistory2 = new ArrayList<>(); + List TBRs = new ArrayList<>(); + List bolusEstimates = new ArrayList<>(); + long atechDate = DateTimeUtil.toATechDate(new GregorianCalendar()); + + //LOG.debug("Filter new entries: Before {}", newHistory); + + if (!isCollectionEmpty(newHistory)) { + + for (PumpHistoryEntry pumpHistoryEntry : newHistory) { + + if (!this.allHistory.contains(pumpHistoryEntry)) { + + PumpHistoryEntryType type = pumpHistoryEntry.getEntryType(); + + if (type == PumpHistoryEntryType.TempBasalRate || type == PumpHistoryEntryType.TempBasalDuration) { + TBRs.add(pumpHistoryEntry); + } else if (type == PumpHistoryEntryType.BolusWizard || type == PumpHistoryEntryType.BolusWizard512) { + bolusEstimates.add(pumpHistoryEntry); + newHistory2.add(pumpHistoryEntry); + } else { + + if (type == PumpHistoryEntryType.EndResultTotals) { + if (!DateTimeUtil.isSameDay(atechDate, pumpHistoryEntry.atechDateTime)) { + newHistory2.add(pumpHistoryEntry); + } + } else { + newHistory2.add(pumpHistoryEntry); + } + } + } + } + + TBRs = preProcessTBRs(TBRs); + + if (bolusEstimates.size() > 0) { + extendBolusRecords(bolusEstimates, newHistory2); + } + + newHistory2.addAll(TBRs); + + this.newHistory = newHistory2; + + sort(this.newHistory); + } + + if (isLogEnabled()) + LOG.debug("New History entries found: {}", this.newHistory.size()); + + showLogs("List of history (after filtering): [" + this.newHistory.size() + "]", gson.toJson(this.newHistory)); + + } + + private void extendBolusRecords(List bolusEstimates, List newHistory2) { + + List boluses = getFilteredItems(newHistory2, PumpHistoryEntryType.Bolus); + + for (PumpHistoryEntry bolusEstimate : bolusEstimates) { + for (PumpHistoryEntry bolus : boluses) { + if (bolusEstimate.atechDateTime.equals(bolus.atechDateTime)) { + bolus.addDecodedData("Estimate", bolusEstimate.getDecodedData().get("Object")); + } + } + } + } + + + public void finalizeNewHistoryRecords() { + + if ((newHistory == null) || (newHistory.size() == 0)) + return; + + PumpHistoryEntry pheLast = newHistory.get(0); + + // find last entry + for (PumpHistoryEntry pumpHistoryEntry : newHistory) { + if (pumpHistoryEntry.atechDateTime != null && pumpHistoryEntry.isAfter(pheLast.atechDateTime)) { + pheLast = pumpHistoryEntry; + } + } + + // add new entries + Collections.reverse(newHistory); + + for (PumpHistoryEntry pumpHistoryEntry : newHistory) { + + if (!this.allHistory.contains(pumpHistoryEntry)) { + lastIdUsed++; + pumpHistoryEntry.id = lastIdUsed; + this.allHistory.add(pumpHistoryEntry); + } + + } + + + if (pheLast == null) // if we don't have any valid record we don't do the filtering and setting + return; + + this.setLastHistoryRecordTime(pheLast.atechDateTime); + SP.putLong(MedtronicConst.Statistics.LastPumpHistoryEntry, pheLast.atechDateTime); + + LocalDateTime dt = null; + + try { + dt = DateTimeUtil.toLocalDateTime(pheLast.atechDateTime); + } catch (Exception ex) { + LOG.error("Problem decoding date from last record: {}" + pheLast); + } + + if (dt != null) { + + dt = dt.minusDays(1); // we keep 24 hours + + long dtRemove = DateTimeUtil.toATechDate(dt); + + List removeList = new ArrayList<>(); + + for (PumpHistoryEntry pumpHistoryEntry : allHistory) { + + if (!pumpHistoryEntry.isAfter(dtRemove)) { + removeList.add(pumpHistoryEntry); + } + } + + this.allHistory.removeAll(removeList); + + this.sort(this.allHistory); + + if (isLogEnabled()) + LOG.debug("All History records [afterFilterCount={}, removedItemsCount={}, newItemsCount={}]", + allHistory.size(), removeList.size(), newHistory.size()); + } else { + LOG.error("Since we couldn't determine date, we don't clean full history. This is just workaround."); + } + + this.newHistory.clear(); + } + + + public boolean hasRelevantConfigurationChanged() { + return getStateFromFilteredList( // + PumpHistoryEntryType.ChangeBasalPattern, // + PumpHistoryEntryType.ClearSettings, // + PumpHistoryEntryType.SaveSettings, // + PumpHistoryEntryType.ChangeMaxBolus, // + PumpHistoryEntryType.ChangeMaxBasal, // + PumpHistoryEntryType.ChangeTempBasalType); + } + + + private boolean isCollectionEmpty(List col) { + return (col == null || col.isEmpty()); + } + + private boolean isCollectionNotEmpty(List col) { + return (col != null && !col.isEmpty()); + } + + + public boolean isPumpSuspended() { + + List items = getDataForPumpSuspends(); + + showLogs("isPumpSuspended: ", MedtronicUtil.gsonInstance.toJson(items)); + + if (isCollectionNotEmpty(items)) { + + PumpHistoryEntryType pumpHistoryEntryType = items.get(0).getEntryType(); + + boolean isSuspended = !(pumpHistoryEntryType == PumpHistoryEntryType.TempBasalCombined || // + pumpHistoryEntryType == PumpHistoryEntryType.BasalProfileStart || // + pumpHistoryEntryType == PumpHistoryEntryType.Bolus || // + pumpHistoryEntryType == PumpHistoryEntryType.Resume || // + pumpHistoryEntryType == PumpHistoryEntryType.BatteryChange || // + pumpHistoryEntryType == PumpHistoryEntryType.Prime); + + if (isLogEnabled()) + LOG.debug("isPumpSuspended. Last entry type={}, isSuspended={}", pumpHistoryEntryType, isSuspended); + + return isSuspended; + } else + return false; + + } + + + private List getDataForPumpSuspends() { + + List newAndAll = new ArrayList<>(); + + if (isCollectionNotEmpty(this.allHistory)) { + newAndAll.addAll(this.allHistory); + } + + if (isCollectionNotEmpty(this.newHistory)) { + + for (PumpHistoryEntry pumpHistoryEntry : newHistory) { + if (!newAndAll.contains(pumpHistoryEntry)) { + newAndAll.add(pumpHistoryEntry); + } + } + } + + if (newAndAll.isEmpty()) + return newAndAll; + + this.sort(newAndAll); + + List newAndAll2 = getFilteredItems(newAndAll, // + PumpHistoryEntryType.Bolus, // + PumpHistoryEntryType.TempBasalCombined, // + PumpHistoryEntryType.Prime, // + PumpHistoryEntryType.Suspend, // + PumpHistoryEntryType.Resume, // + PumpHistoryEntryType.Rewind, // + PumpHistoryEntryType.NoDeliveryAlarm, // + PumpHistoryEntryType.BatteryChange, // + PumpHistoryEntryType.BasalProfileStart); + + newAndAll2 = filterPumpSuspend(newAndAll2, 10); + + return newAndAll2; + } + + + private List filterPumpSuspend(List newAndAll, int filterCount) { + + if (newAndAll.size() <= filterCount) { + return newAndAll; + } + + List newAndAllOut = new ArrayList<>(); + + for (int i = 0; i < filterCount; i++) { + newAndAllOut.add(newAndAll.get(i)); + } + + return newAndAllOut; + } + + + /** + * Process History Data: Boluses(Treatments), TDD, TBRs, Suspend-Resume (or other pump stops: battery, prime) + */ + public void processNewHistoryData() { + + // TODO: Fix db code + // Prime (for reseting autosense) + List primeRecords = getFilteredItems(PumpHistoryEntryType.Prime); + + if (isLogEnabled()) + LOG.debug("ProcessHistoryData: Prime [count={}, items={}]", primeRecords.size(), gson.toJson(primeRecords)); + + if (isCollectionNotEmpty(primeRecords)) { + try { + processPrime(primeRecords); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error processing Prime entries: " + ex.getMessage(), ex); + throw ex; + } + } + + // TDD + List tdds = getFilteredItems(PumpHistoryEntryType.EndResultTotals, getTDDType()); + + if (isLogEnabled()) + LOG.debug("ProcessHistoryData: TDD [count={}, items={}]", tdds.size(), gson.toJson(tdds)); + + if (isCollectionNotEmpty(tdds)) { + try { + processTDDs(tdds); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error processing TDD entries: " + ex.getMessage(), ex); + throw ex; + } + } + + pumpTime = MedtronicUtil.getPumpTime(); + + // Bolus + List treatments = getFilteredItems(PumpHistoryEntryType.Bolus); + + if (isLogEnabled()) + LOG.debug("ProcessHistoryData: Bolus [count={}, items={}]", treatments.size(), gson.toJson(treatments)); + + if (treatments.size() > 0) { + try { + processBolusEntries(treatments); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error processing Bolus entries: " + ex.getMessage(), ex); + throw ex; + } + } + + // TBR + List tbrs = getFilteredItems(PumpHistoryEntryType.TempBasalCombined); + + if (isLogEnabled()) + LOG.debug("ProcessHistoryData: TBRs Processed [count={}, items={}]", tbrs.size(), gson.toJson(tbrs)); + + if (tbrs.size() > 0) { + try { + processTBREntries(tbrs); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error processing TBR entries: " + ex.getMessage(), ex); + throw ex; + } + } + + // 'Delivery Suspend' + List suspends = null; + + try { + suspends = getSuspends(); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error getting Suspend entries: " + ex.getMessage(), ex); + throw ex; + } + + if (isLogEnabled()) + LOG.debug("ProcessHistoryData: 'Delivery Suspend' Processed [count={}, items={}]", suspends.size(), + gson.toJson(suspends)); + + if (isCollectionNotEmpty(suspends)) { + try { + processSuspends(suspends); + } catch (Exception ex) { + LOG.error("ProcessHistoryData: Error processing Suspends entries: " + ex.getMessage(), ex); + throw ex; + } + } + } + + + private void processPrime(List primeRecords) { + + long maxAllowedTimeInPast = DateTimeUtil.getATDWithAddedMinutes(new GregorianCalendar(), -30); + + long lastPrimeRecord = 0L; + + for (PumpHistoryEntry primeRecord : primeRecords) { + + if (primeRecord.atechDateTime > maxAllowedTimeInPast) { + if (lastPrimeRecord < primeRecord.atechDateTime) { + lastPrimeRecord = primeRecord.atechDateTime; + } + } + } + + if (lastPrimeRecord != 0L) { + long lastPrimeFromAAPS = SP.getLong(MedtronicConst.Statistics.LastPrime, 0L); + + if (lastPrimeRecord != lastPrimeFromAAPS) { + uploadCareportalEvent(DateTimeUtil.toMillisFromATD(lastPrimeRecord), CareportalEvent.SITECHANGE); + + SP.putLong(MedtronicConst.Statistics.LastPrime, lastPrimeRecord); + } + } + } + + + private void uploadCareportalEvent(long date, String event) { + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(date) != 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); + CareportalEvent careportalEvent = new CareportalEvent(); + careportalEvent.date = date; + careportalEvent.source = Source.USER; + careportalEvent.eventType = event; + careportalEvent.json = data.toString(); + MainApp.getDbHelper().createOrUpdate(careportalEvent); + NSUpload.uploadCareportalEntryToNS(data); + } catch (JSONException e) { + LOG.error("Unhandled exception", e); + } + } + + + private void processTDDs(List tddsIn) { + + List tdds = filterTDDs(tddsIn); + + if (isLogEnabled()) + LOG.debug(getLogPrefix() + "TDDs found: {}.\n{}", tdds.size(), gson.toJson(tdds)); + + List tddsDb = databaseHelper.getTDDsForLastXDays(3); + + for (PumpHistoryEntry tdd : tdds) { + + TDD tddDbEntry = findTDD(tdd.atechDateTime, tddsDb); + + DailyTotalsDTO totalsDTO = (DailyTotalsDTO) tdd.getDecodedData().get("Object"); + + //LOG.debug("DailyTotals: {}", totalsDTO); + + if (tddDbEntry == null) { + TDD tddNew = new TDD(); + totalsDTO.setTDD(tddNew); + + if (isLogEnabled()) + LOG.debug("TDD Add: {}", tddNew); + + databaseHelper.createOrUpdateTDD(tddNew); + + } else { + + if (!totalsDTO.doesEqual(tddDbEntry)) { + totalsDTO.setTDD(tddDbEntry); + + if (isLogEnabled()) + LOG.debug("TDD Edit: {}", tddDbEntry); + + databaseHelper.createOrUpdateTDD(tddDbEntry); + } + } + } + } + + + private enum ProcessHistoryRecord { + Bolus("Bolus"), + TBR("TBR"), + Suspend("Suspend"); + + private String description; + + ProcessHistoryRecord(String desc) { + this.description = desc; + } + + public String getDescription() { + return this.description; + } + + } + + + private void processBolusEntries(List entryList) { + + long oldestTimestamp = getOldestTimestamp(entryList); + + Gson gson = MedtronicUtil.getGsonInstance(); + + List entriesFromHistory = getDatabaseEntriesByLastTimestamp(oldestTimestamp, ProcessHistoryRecord.Bolus); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: List (before filter): {}, FromDb={}", gson.toJson(entryList), + gsonCore.toJson(entriesFromHistory)); + + filterOutAlreadyAddedEntries(entryList, entriesFromHistory); + + if (entryList.isEmpty()) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: EntryList was filtered out."); + return; + } + + filterOutNonInsulinEntries(entriesFromHistory); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: List (after filter): {}, FromDb={}", gson.toJson(entryList), + gsonCore.toJson(entriesFromHistory)); + + if (isCollectionEmpty(entriesFromHistory)) { + for (PumpHistoryEntry treatment : entryList) { + if (isLogEnabled()) + LOG.debug("Add Bolus (no db entry): " + treatment); + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: Add Bolus: FromDb=null, Treatment={}", treatment); + + addBolus(treatment, null); + } + } else { + for (PumpHistoryEntry treatment : entryList) { + DbObjectBase treatmentDb = findDbEntry(treatment, entriesFromHistory); + if (isLogEnabled()) + LOG.debug("Add Bolus {} - (entryFromDb={}) ", treatment, treatmentDb); + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: Add Bolus: FromDb={}, Treatment={}", treatmentDb, treatment); + + addBolus(treatment, (Treatment) treatmentDb); + } + } + } + + + private void filterOutNonInsulinEntries(List entriesFromHistory) { + // when we try to pair PumpHistory with AAPS treatments, we need to ignore all non-insulin entries + List removeList = new ArrayList<>(); + + for (DbObjectBase dbObjectBase : entriesFromHistory) { + + Treatment treatment = (Treatment)dbObjectBase; + + if (RileyLinkUtil.isSame(treatment.insulin, 0d)) { + removeList.add(dbObjectBase); + } + } + + entriesFromHistory.removeAll(removeList); + } + + + private void processTBREntries(List entryList) { + + Collections.reverse(entryList); + + TempBasalPair tbr = (TempBasalPair) entryList.get(0).getDecodedDataEntry("Object"); + + boolean readOldItem = false; + + if (tbr.isCancelTBR()) { + PumpHistoryEntry oneMoreEntryFromHistory = getOneMoreEntryFromHistory(PumpHistoryEntryType.TempBasalCombined); + + if (oneMoreEntryFromHistory != null) { + entryList.add(0, oneMoreEntryFromHistory); + readOldItem = true; + } else { + entryList.remove(0); + } + } + + long oldestTimestamp = getOldestTimestamp(entryList); + + List entriesFromHistory = getDatabaseEntriesByLastTimestamp(oldestTimestamp, ProcessHistoryRecord.TBR); + + if (isLogEnabled()) + LOG.debug(ProcessHistoryRecord.TBR.getDescription() + " List (before filter): {}, FromDb={}", gson.toJson(entryList), + gson.toJson(entriesFromHistory)); + + + TempBasalProcessDTO processDTO = null; + List processList = new ArrayList<>(); + + for (PumpHistoryEntry treatment : entryList) { + + TempBasalPair tbr2 = (TempBasalPair) treatment.getDecodedDataEntry("Object"); + + if (tbr2.isCancelTBR()) { + + if (processDTO != null) { + processDTO.itemTwo = treatment; + + if (readOldItem) { + processDTO.processOperation = TempBasalProcessDTO.Operation.Edit; + readOldItem = false; + } + } else { + LOG.error("processDTO was null - shouldn't happen. ItemTwo={}", treatment); + } + } else { + if (processDTO != null) { + processList.add(processDTO); + } + + processDTO = new TempBasalProcessDTO(); + processDTO.itemOne = treatment; + processDTO.processOperation = TempBasalProcessDTO.Operation.Add; + } + } + + if (processDTO != null) { + processList.add(processDTO); + } + + + if (isCollectionNotEmpty(processList)) { + + for (TempBasalProcessDTO tempBasalProcessDTO : processList) { + + if (tempBasalProcessDTO.processOperation == TempBasalProcessDTO.Operation.Edit) { + // edit + TemporaryBasal tempBasal = findTempBasalWithPumpId(tempBasalProcessDTO.itemOne.getPumpId(), entriesFromHistory); + + if (tempBasal != null) { + + tempBasal.durationInMinutes = tempBasalProcessDTO.getDuration(); + + databaseHelper.createOrUpdate(tempBasal); + + if (isLogEnabled()) + LOG.debug("Edit " + ProcessHistoryRecord.TBR.getDescription() + " - (entryFromDb={}) ", tempBasal); + } else { + LOG.error("TempBasal not found. Item: {}", tempBasalProcessDTO.itemOne); + } + + } else { + // add + + PumpHistoryEntry treatment = tempBasalProcessDTO.itemOne; + + TempBasalPair tbr2 = (TempBasalPair) treatment.getDecodedData().get("Object"); + tbr2.setDurationMinutes(tempBasalProcessDTO.getDuration()); + + TemporaryBasal tempBasal = findTempBasalWithPumpId(tempBasalProcessDTO.itemOne.getPumpId(), entriesFromHistory); + + if (tempBasal == null) { + DbObjectBase treatmentDb = findDbEntry(treatment, entriesFromHistory); + + if (isLogEnabled()) + LOG.debug("Add " + ProcessHistoryRecord.TBR.getDescription() + " {} - (entryFromDb={}) ", treatment, treatmentDb); + + addTBR(treatment, (TemporaryBasal) treatmentDb); + } else { + // this shouldn't happen + if (tempBasal.durationInMinutes != tempBasalProcessDTO.getDuration()) { + LOG.debug("Found entry with wrong duration (shouldn't happen)... updating"); + tempBasal.durationInMinutes = tempBasalProcessDTO.getDuration(); + } + + } + } // if + } // for + + } // collection + } + + + private TemporaryBasal findTempBasalWithPumpId(long pumpId, List entriesFromHistory) { + + for (DbObjectBase dbObjectBase : entriesFromHistory) { + TemporaryBasal tbr = (TemporaryBasal) dbObjectBase; + + if (tbr.pumpId == pumpId) { + return tbr; + } + } + + TemporaryBasal tempBasal = databaseHelper.findTempBasalByPumpId(pumpId); + return tempBasal; + } + + + /** + * findDbEntry - finds Db entries in database, while theoretically this should have same dateTime they + * don't. Entry on pump is few seconds before treatment in AAPS, and on manual boluses on pump there + * is no treatment at all. For now we look fro tratment that was from 0s - 1m59s within pump entry. + * + * @param treatment Pump Entry + * @param entriesFromHistory entries from history + * @return DbObject from AAPS (if found) + */ + private DbObjectBase findDbEntry(PumpHistoryEntry treatment, List entriesFromHistory) { + + long proposedTime = DateTimeUtil.toMillisFromATD(treatment.atechDateTime); + + //proposedTime += (this.pumpTime.timeDifference * 1000); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry Treatment={}, FromDb={}", treatment, gson.toJson(entriesFromHistory)); + + if (entriesFromHistory.size() == 0) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry Treatment={}, FromDb=null", treatment); + return null; + } else if (entriesFromHistory.size() == 1) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry Treatment={}, FromDb={}. Type=SingleEntry", treatment, entriesFromHistory.get(0)); + + // TODO: Fix db code + // if difference is bigger than 2 minutes we discard entry + long maxMillisAllowed = DateTimeUtil.getMillisFromATDWithAddedMinutes(treatment.atechDateTime, 2); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry maxMillisAllowed={}, AtechDateTime={} (add 2 minutes). ", maxMillisAllowed, treatment.atechDateTime); + + if (entriesFromHistory.get(0).getDate() > maxMillisAllowed) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry entry filtered out, returning null. "); + return null; + } + + return entriesFromHistory.get(0); + } + + for (int min = 0; min < 2; min += 1) { + + for (int sec = 0; sec <= 50; sec += 10) { + + if (min == 1 && sec == 50) { + sec = 59; + } + + int diff = (sec * 1000); + + List outList = new ArrayList<>(); + + for (DbObjectBase treatment1 : entriesFromHistory) { + + if ((treatment1.getDate() > proposedTime - diff) && (treatment1.getDate() < proposedTime + diff)) { + outList.add(treatment1); + } + } + + if (outList.size() == 1) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry Treatment={}, FromDb={}. Type=EntrySelected, AtTimeMin={}, AtTimeSec={}", treatment, entriesFromHistory.get(0), min, sec); + + return outList.get(0); + } + + if (min == 0 && sec == 10 && outList.size() > 1) { + if (isLogEnabled()) + LOG.error("Too many entries (with too small diff): (timeDiff=[min={},sec={}],count={},list={})", + min, sec, outList.size(), gson.toJson(outList)); + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: findDbEntry Error - Too many entries (with too small diff): (timeDiff=[min={},sec={}],count={},list={})", + min, sec, outList.size(), gson.toJson(outList)); + } + } + } + + return null; + } + + + private List getDatabaseEntriesByLastTimestamp(long startTimestamp, ProcessHistoryRecord processHistoryRecord) { + if (processHistoryRecord == ProcessHistoryRecord.Bolus) { + return TreatmentsPlugin.getPlugin().getTreatmentsFromHistoryAfterTimestamp(startTimestamp); + } else { + return databaseHelper.getTemporaryBasalsDataFromTime(startTimestamp, true); + } + } + + + private void filterOutAlreadyAddedEntries(List entryList, List treatmentsFromHistory) { + + if (isCollectionEmpty(treatmentsFromHistory)) + return; + + List removeTreatmentsFromHistory = new ArrayList<>(); + List removeTreatmentsFromPH = new ArrayList<>(); + + for (DbObjectBase treatment : treatmentsFromHistory) { + + if (treatment.getPumpId() != 0) { + + PumpHistoryEntry selectedBolus = null; + + for (PumpHistoryEntry bolus : entryList) { + if (bolus.getPumpId() == treatment.getPumpId()) { + selectedBolus = bolus; + break; + } + } + + if (selectedBolus != null) { + entryList.remove(selectedBolus); + + removeTreatmentsFromPH.add(selectedBolus); + removeTreatmentsFromHistory.add(treatment); + } + } + } + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: filterOutAlreadyAddedEntries: PumpHistory={}, Treatments={}", + gson.toJson(removeTreatmentsFromPH), + gsonCore.toJson(removeTreatmentsFromHistory)); + + treatmentsFromHistory.removeAll(removeTreatmentsFromHistory); + } + + + private void addBolus(PumpHistoryEntry bolus, Treatment treatment) { + + BolusDTO bolusDTO = (BolusDTO) bolus.getDecodedData().get("Object"); + + if (treatment == null) { + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(tretament==null): Bolus={}", bolusDTO); + + switch (bolusDTO.getBolusType()) { + case Normal: { + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + + detailedBolusInfo.date = tryToGetByLocalTime(bolus.atechDateTime); + detailedBolusInfo.source = Source.PUMP; + detailedBolusInfo.pumpId = bolus.getPumpId(); + detailedBolusInfo.insulin = bolusDTO.getDeliveredAmount(); + + addCarbsFromEstimate(detailedBolusInfo, bolus); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(tretament==null): DetailedBolusInfo={}", detailedBolusInfo); + + boolean newRecord = TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, false); + + bolus.setLinkedObject(detailedBolusInfo); + + if (isLogEnabled()) + LOG.debug("addBolus - [date={},pumpId={}, insulin={}, newRecord={}]", detailedBolusInfo.date, + detailedBolusInfo.pumpId, detailedBolusInfo.insulin, newRecord); + } + break; + + case Audio: + case Extended: { + ExtendedBolus extendedBolus = new ExtendedBolus(); + extendedBolus.date = tryToGetByLocalTime(bolus.atechDateTime); + extendedBolus.source = Source.PUMP; + extendedBolus.insulin = bolusDTO.getDeliveredAmount(); + extendedBolus.pumpId = bolus.getPumpId(); + extendedBolus.isValid = true; + extendedBolus.durationInMinutes = bolusDTO.getDuration(); + + bolus.setLinkedObject(extendedBolus); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(tretament==null): ExtendedBolus={}", extendedBolus); + + TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); + + if (isLogEnabled()) + LOG.debug("addBolus - Extended [date={},pumpId={}, insulin={}, duration={}]", extendedBolus.date, + extendedBolus.pumpId, extendedBolus.insulin, extendedBolus.durationInMinutes); + + } + break; + } + + } else { + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(OldTreatment={}): Bolus={}", treatment, bolusDTO); + + treatment.source = Source.PUMP; + treatment.pumpId = bolus.getPumpId(); + treatment.insulin = bolusDTO.getDeliveredAmount(); + + TreatmentService.UpdateReturn updateReturn = TreatmentsPlugin.getPlugin().getService().createOrUpdateMedtronic(treatment, false); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addBolus(tretament!=null): NewTreatment={}, UpdateReturn={}", treatment, updateReturn); + + if (isLogEnabled()) + LOG.debug("editBolus - [date={},pumpId={}, insulin={}, newRecord={}]", treatment.date, + treatment.pumpId, treatment.insulin, updateReturn.toString()); + + bolus.setLinkedObject(treatment); + + } + } + + + private void addCarbsFromEstimate(DetailedBolusInfo detailedBolusInfo, PumpHistoryEntry bolus) { + + if (bolus.containsDecodedData("Estimate")) { + + BolusWizardDTO bolusWizard = (BolusWizardDTO) bolus.getDecodedData().get("Estimate"); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: addCarbsFromEstimate: Bolus={}, BolusWizardDTO={}", bolus, bolusWizard); + + detailedBolusInfo.carbs = bolusWizard.carbs; + } + } + + + private void addTBR(PumpHistoryEntry treatment, TemporaryBasal temporaryBasalDbInput) { + + TempBasalPair tbr = (TempBasalPair) treatment.getDecodedData().get("Object"); + + TemporaryBasal temporaryBasalDb = temporaryBasalDbInput; + String operation = "editTBR"; + + if (temporaryBasalDb == null) { + temporaryBasalDb = new TemporaryBasal(); + temporaryBasalDb.date = tryToGetByLocalTime(treatment.atechDateTime); + + operation = "addTBR"; + } + + temporaryBasalDb.source = Source.PUMP; + temporaryBasalDb.pumpId = treatment.getPumpId(); + temporaryBasalDb.durationInMinutes = tbr.getDurationMinutes(); + temporaryBasalDb.absoluteRate = tbr.getInsulinRate(); + temporaryBasalDb.isAbsolute = !tbr.isPercent(); + + treatment.setLinkedObject(temporaryBasalDb); + + databaseHelper.createOrUpdate(temporaryBasalDb); + + if (isLogEnabled()) + LOG.debug(operation + " - [date={},pumpId={}, rate={} {}, duration={}]", // + temporaryBasalDb.date, // + temporaryBasalDb.pumpId, // + temporaryBasalDb.isAbsolute ? String.format(Locale.ENGLISH, "%.2f", temporaryBasalDb.absoluteRate) : + String.format(Locale.ENGLISH, "%d", temporaryBasalDb.percentRate), // + temporaryBasalDb.isAbsolute ? "U/h" : "%", // + temporaryBasalDb.durationInMinutes); + } + + + private void processSuspends(List tempBasalProcessList) { + + for (TempBasalProcessDTO tempBasalProcess : tempBasalProcessList) { + + TemporaryBasal tempBasal = databaseHelper.findTempBasalByPumpId(tempBasalProcess.itemOne.getPumpId()); + + if (tempBasal == null) { + // add + tempBasal = new TemporaryBasal(); + tempBasal.date = tryToGetByLocalTime(tempBasalProcess.itemOne.atechDateTime); + + tempBasal.source = Source.PUMP; + tempBasal.pumpId = tempBasalProcess.itemOne.getPumpId(); + tempBasal.durationInMinutes = tempBasalProcess.getDuration(); + tempBasal.absoluteRate = 0.0d; + tempBasal.isAbsolute = true; + + tempBasalProcess.itemOne.setLinkedObject(tempBasal); + tempBasalProcess.itemTwo.setLinkedObject(tempBasal); + + databaseHelper.createOrUpdate(tempBasal); + + } + } + + } + + + private List getSuspends() { + + List outList = new ArrayList<>(); + + // suspend/resume + outList.addAll(getSuspendResumeRecords()); + // no_delivery/prime & rewind/prime + outList.addAll(getNoDeliveryRewindPrimeRecords()); + + return outList; + } + + private List getSuspendResumeRecords() { + List filteredItems = getFilteredItems(this.newHistory, // + PumpHistoryEntryType.Suspend, // + PumpHistoryEntryType.Resume); + + List outList = new ArrayList<>(); + + if (filteredItems.size() > 0) { + + List filtered2Items = new ArrayList<>(); + + if ((filteredItems.size() % 2 == 0) && (filteredItems.get(0).getEntryType() == PumpHistoryEntryType.Resume)) { + // full resume suspends (S R S R) + filtered2Items.addAll(filteredItems); + } else if ((filteredItems.size() % 2 == 0) && (filteredItems.get(0).getEntryType() == PumpHistoryEntryType.Suspend)) { + // not full suspends, need to retrive one more record and discard first one (R S R S) -> ([S] R S R [xS]) + filteredItems.remove(0); + + PumpHistoryEntry oneMoreEntryFromHistory = getOneMoreEntryFromHistory(PumpHistoryEntryType.Suspend); + if (oneMoreEntryFromHistory != null) { + filteredItems.add(oneMoreEntryFromHistory); + } else { + filteredItems.remove(filteredItems.size() - 1); // remove last (unpaired R) + } + + filtered2Items.addAll(filteredItems); + } else { + if (filteredItems.get(0).getEntryType() == PumpHistoryEntryType.Resume) { + // get one more from history (R S R) -> ([S] R S R) + + PumpHistoryEntry oneMoreEntryFromHistory = getOneMoreEntryFromHistory(PumpHistoryEntryType.Suspend); + if (oneMoreEntryFromHistory != null) { + filteredItems.add(oneMoreEntryFromHistory); + } else { + filteredItems.remove(filteredItems.size() - 1); // remove last (unpaired R) + } + + filtered2Items.addAll(filteredItems); + } else { + // remove last and have paired items + filteredItems.remove(0); + filtered2Items.addAll(filteredItems); + } + } + + if (filtered2Items.size() > 0) { + sort(filtered2Items); + Collections.reverse(filtered2Items); + + for (int i = 0; i < filtered2Items.size(); i += 2) { + TempBasalProcessDTO dto = new TempBasalProcessDTO(); + + dto.itemOne = filtered2Items.get(i); + dto.itemTwo = filtered2Items.get(i + 1); + + dto.processOperation = TempBasalProcessDTO.Operation.Add; + + outList.add(dto); + } + } + } + + return outList; + } + + + private List getNoDeliveryRewindPrimeRecords() { + List primeItems = getFilteredItems(this.newHistory, // + PumpHistoryEntryType.Prime); + + List outList = new ArrayList<>(); + + if (primeItems.size() == 0) + return outList; + + List filteredItems = getFilteredItems(this.newHistory, // + PumpHistoryEntryType.Prime, + PumpHistoryEntryType.Rewind, + PumpHistoryEntryType.NoDeliveryAlarm, + PumpHistoryEntryType.Bolus, + PumpHistoryEntryType.TempBasalCombined + ); + + List tempData = new ArrayList<>(); + boolean startedItems = false; + boolean finishedItems = false; + + for (PumpHistoryEntry filteredItem : filteredItems) { + if (filteredItem.getEntryType() == PumpHistoryEntryType.Prime) { + startedItems = true; + } + + if (startedItems) { + if (filteredItem.getEntryType() == PumpHistoryEntryType.Bolus || + filteredItem.getEntryType() == PumpHistoryEntryType.TempBasalCombined) { + finishedItems = true; + break; + } + + tempData.add(filteredItem); + } + } + + + if (!finishedItems) { + + List filteredItemsOld = getFilteredItems(this.allHistory, // + PumpHistoryEntryType.Rewind, + PumpHistoryEntryType.NoDeliveryAlarm, + PumpHistoryEntryType.Bolus, + PumpHistoryEntryType.TempBasalCombined + ); + + for (PumpHistoryEntry filteredItem : filteredItemsOld) { + + if (filteredItem.getEntryType() == PumpHistoryEntryType.Bolus || + filteredItem.getEntryType() == PumpHistoryEntryType.TempBasalCombined) { + finishedItems = true; + break; + } + + tempData.add(filteredItem); + } + } + + + if (!finishedItems) { + showLogs("NoDeliveryRewindPrimeRecords: Not finished Items: ", gson.toJson(tempData)); + return outList; + } + + showLogs("NoDeliveryRewindPrimeRecords: Records to evaluate: ", gson.toJson(tempData)); + + List items = getFilteredItems(tempData, // + PumpHistoryEntryType.Prime + ); + + + TempBasalProcessDTO processDTO = new TempBasalProcessDTO(); + + processDTO.itemTwo = items.get(0); + + items = getFilteredItems(tempData, // + PumpHistoryEntryType.NoDeliveryAlarm + ); + + if (items.size() > 0) { + + processDTO.itemOne = items.get(items.size() - 1); + processDTO.processOperation = TempBasalProcessDTO.Operation.Add; + + outList.add(processDTO); + return outList; + } + + + items = getFilteredItems(tempData, // + PumpHistoryEntryType.Rewind + ); + + if (items.size() > 0) { + + processDTO.itemOne = items.get(0); + processDTO.processOperation = TempBasalProcessDTO.Operation.Add; + + outList.add(processDTO); + return outList; + } + + return outList; + } + + + private PumpHistoryEntry getOneMoreEntryFromHistory(PumpHistoryEntryType entryType) { + List filteredItems = getFilteredItems(this.allHistory, entryType); + + return filteredItems.size() == 0 ? null : filteredItems.get(0); + } + + + private List filterTDDs(List tdds) { + List tddsOut = new ArrayList<>(); + + for (PumpHistoryEntry tdd : tdds) { + if (tdd.getEntryType() != PumpHistoryEntryType.EndResultTotals) { + tddsOut.add(tdd); + } + } + + return tddsOut.size() == 0 ? tdds : tddsOut; + } + + + private TDD findTDD(long atechDateTime, List tddsDb) { + + for (TDD tdd : tddsDb) { + + if (DateTimeUtil.isSameDayATDAndMillis(atechDateTime, tdd.date)) { + return tdd; + } + } + + return null; + } + + private long tryToGetByLocalTime(long atechDateTime) { + return DateTimeUtil.toMillisFromATD(atechDateTime); + } + + + private int getOldestDateDifference(List treatments) { + + long dt = Long.MAX_VALUE; + PumpHistoryEntry currentTreatment = null; + + if (isCollectionEmpty(treatments)) { + return 8; // default return of 6 (5 for diif on history reading + 2 for max allowed difference) minutes + } + + for (PumpHistoryEntry treatment : treatments) { + + if (treatment.atechDateTime < dt) { + dt = treatment.atechDateTime; + currentTreatment = treatment; + } + } + + LocalDateTime oldestEntryTime = null; + + try { + + oldestEntryTime = DateTimeUtil.toLocalDateTime(dt); + oldestEntryTime = oldestEntryTime.minusMinutes(3); + +// if (this.pumpTime.timeDifference < 0) { +// oldestEntryTime = oldestEntryTime.plusSeconds(this.pumpTime.timeDifference); +// } + } catch (Exception ex) { + LOG.error("Problem decoding date from last record: {}" + currentTreatment); + return 8; // default return of 6 minutes + } + + LocalDateTime now = new LocalDateTime(); + + Minutes minutes = Minutes.minutesBetween(oldestEntryTime, now); + + // returns oldest time in history, with calculated time difference between pump and phone, minus 5 minutes + if (isLogEnabled()) + LOG.debug("Oldest entry: {}, pumpTimeDifference={}, newDt={}, currentTime={}, differenceMin={}", dt, + this.pumpTime.timeDifference, oldestEntryTime, now, minutes.getMinutes()); + + return minutes.getMinutes(); + } + + + private long getOldestTimestamp(List treatments) { + + long dt = Long.MAX_VALUE; + PumpHistoryEntry currentTreatment = null; + + for (PumpHistoryEntry treatment : treatments) { + + if (treatment.atechDateTime < dt) { + dt = treatment.atechDateTime; + currentTreatment = treatment; + } + } + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: getOldestTimestamp. Oldest entry found: time={}, object={}", dt, currentTreatment); + + try { + + GregorianCalendar oldestEntryTime = DateTimeUtil.toGregorianCalendar(dt); + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: getOldestTimestamp. oldestEntryTime: {}", DateTimeUtil.toString(oldestEntryTime)); + oldestEntryTime.add(Calendar.MINUTE, -2); + + if (doubleBolusDebug) + LOG.debug("DoubleBolusDebug: getOldestTimestamp. oldestEntryTime (-2m): {}, timeInMillis={}", DateTimeUtil.toString(oldestEntryTime), oldestEntryTime.getTimeInMillis()); + + return oldestEntryTime.getTimeInMillis(); + + } catch (Exception ex) { + LOG.error("Problem decoding date from last record: {}", currentTreatment); + return 8; // default return of 6 minutes + } + + } + + + private PumpHistoryEntryType getTDDType() { + + if (MedtronicUtil.getMedtronicPumpModel() == null) { + return PumpHistoryEntryType.EndResultTotals; + } + + switch (MedtronicUtil.getMedtronicPumpModel()) { + + case Medtronic_515: + case Medtronic_715: + return PumpHistoryEntryType.DailyTotals515; + + case Medtronic_522: + case Medtronic_722: + return PumpHistoryEntryType.DailyTotals522; + + case Medtronic_523_Revel: + case Medtronic_723_Revel: + case Medtronic_554_Veo: + case Medtronic_754_Veo: + return PumpHistoryEntryType.DailyTotals523; + + default: { + return PumpHistoryEntryType.EndResultTotals; + } + } + } + + + public boolean hasBasalProfileChanged() { + + List filteredItems = getFilteredItems(PumpHistoryEntryType.ChangeBasalProfile_NewProfile); + + if (isLogEnabled()) + LOG.debug("hasBasalProfileChanged. Items: " + gson.toJson(filteredItems)); + + return (filteredItems.size() > 0); + } + + + public void processLastBasalProfileChange(MedtronicPumpStatus mdtPumpStatus) { + + List filteredItems = getFilteredItems(PumpHistoryEntryType.ChangeBasalProfile_NewProfile); + + if (isLogEnabled()) + LOG.debug("processLastBasalProfileChange. Items: " + filteredItems); + + PumpHistoryEntry newProfile = null; + Long lastDate = null; + + if (filteredItems.size() == 1) { + newProfile = filteredItems.get(0); + } else if (filteredItems.size() > 1) { + + for (PumpHistoryEntry filteredItem : filteredItems) { + + if (lastDate == null || lastDate < filteredItem.atechDateTime) { + newProfile = filteredItem; + lastDate = newProfile.atechDateTime; + } + } + } + + if (newProfile != null) { + if (isLogEnabled()) + LOG.debug("processLastBasalProfileChange. item found, setting new basalProfileLocally: " + newProfile); + BasalProfile basalProfile = (BasalProfile) newProfile.getDecodedData().get("Object"); + + mdtPumpStatus.basalsByHour = basalProfile.getProfilesByHour(); + } + } + + + public boolean hasPumpTimeChanged() { + return getStateFromFilteredList(PumpHistoryEntryType.NewTimeSet, // + PumpHistoryEntryType.ChangeTime); + } + + + public void setLastHistoryRecordTime(Long lastHistoryRecordTime) { + + // this.previousLastHistoryRecordTime = this.lastHistoryRecordTime; + this.lastHistoryRecordTime = lastHistoryRecordTime; + } + + + public void setIsInInit(boolean init) { + this.isInit = init; + } + + + // HELPER METHODS + + private void sort(List list) { + Collections.sort(list, new PumpHistoryEntry.Comparator()); + } + + + private List preProcessTBRs(List TBRs_Input) { + List TBRs = new ArrayList<>(); + + Map map = new HashMap<>(); + + for (PumpHistoryEntry pumpHistoryEntry : TBRs_Input) { + if (map.containsKey(pumpHistoryEntry.DT)) { + MedtronicPumpHistoryDecoder.decodeTempBasal(map.get(pumpHistoryEntry.DT), pumpHistoryEntry); + pumpHistoryEntry.setEntryType(PumpHistoryEntryType.TempBasalCombined); + TBRs.add(pumpHistoryEntry); + map.remove(pumpHistoryEntry.DT); + } else { + map.put(pumpHistoryEntry.DT, pumpHistoryEntry); + } + } + + return TBRs; + } + + + private List getFilteredItems(PumpHistoryEntryType... entryTypes) { + return getFilteredItems(this.newHistory, entryTypes); + } + + + private boolean getStateFromFilteredList(PumpHistoryEntryType... entryTypes) { + if (isInit) { + return false; + } else { + List filteredItems = getFilteredItems(entryTypes); + + if (isLogEnabled()) + LOG.debug("Items: " + filteredItems); + + return filteredItems.size() > 0; + } + } + + + private List getFilteredItems(List inList, PumpHistoryEntryType... entryTypes) { + + // LOG.debug("InList: " + inList.size()); + List outList = new ArrayList<>(); + + if (inList != null && inList.size() > 0) { + for (PumpHistoryEntry pumpHistoryEntry : inList) { + + if (!isEmpty(entryTypes)) { + for (PumpHistoryEntryType pumpHistoryEntryType : entryTypes) { + + if (pumpHistoryEntry.getEntryType() == pumpHistoryEntryType) { + outList.add(pumpHistoryEntry); + break; + } + } + } else { + outList.add(pumpHistoryEntry); + } + } + } + + // LOG.debug("OutList: " + outList.size()); + + return outList; + } + + + private boolean isEmpty(PumpHistoryEntryType... entryTypes) { + return (entryTypes == null || (entryTypes.length == 1 && entryTypes[0] == null)); + } + + + private String getLogPrefix() { + return "MedtronicHistoryData::"; + } + + private static boolean isLogEnabled() { + return (L.isEnabled(L.PUMP)); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java new file mode 100644 index 00000000000..25facaf9e6c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfile.java @@ -0,0 +1,387 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import com.google.gson.annotations.Expose; + +import org.joda.time.Instant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by geoff on 6/1/15. + *

+ * There are three basal profiles stored on the pump. (722 only?) They are all parsed the same, the user just has 3 to + * choose from: Standard, A, and B + *

+ * The byte array is 48 times three byte entries long, plus a zero? If the profile is completely empty, it should have + * one entry: [0,0,0x3F]. The first entry of [0,0,0] marks the end of the used entries. + *

+ * Each entry is assumed to span from the specified start time to the start time of the next entry, or to midnight if + * there are no more entries. + *

+ * Individual entries are of the form [r,z,m] where r is the rate (in 0.025 U increments) z is zero (?) m is the start + * time-of-day for the basal rate period (in 30 minute increments) + */ +public class BasalProfile { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public static final int MAX_RAW_DATA_SIZE = (48 * 3) + 1; + private static final boolean DEBUG_BASALPROFILE = false; + @Expose + private byte[] mRawData; // store as byte array to make transport (via parcel) easier + private List listEntries; + + + public BasalProfile() { + init(); + } + + + public BasalProfile(byte[] data) { + setRawData(data); + } + + + // this asUINT8 should be combined with Record.asUINT8, and placed in a new util class. + protected static int readUnsignedByte(byte b) { + return (b < 0) ? b + 256 : b; + } + + + public void init() { + mRawData = new byte[MAX_RAW_DATA_SIZE]; + mRawData[0] = 0; + mRawData[1] = 0; + mRawData[2] = 0x3f; + } + + + public boolean setRawData(byte[] data) { + if (data == null) { + LOG.error("setRawData: buffer is null!"); + return false; + } + + // if we have just one entry through all day it looks like just length 1 + if (data.length == 1) { + data = MedtronicUtil.createByteArray(data[0], (byte) 0, (byte) 0); + } + + if (data.length == MAX_RAW_DATA_SIZE) { + mRawData = data; + } else { + int len = Math.min(MAX_RAW_DATA_SIZE, data.length); + mRawData = new byte[MAX_RAW_DATA_SIZE]; + System.arraycopy(data, 0, mRawData, 0, len); + } + + return true; + } + + + public boolean setRawDataFromHistory(byte[] data) { + if (data == null) { + LOG.error("setRawData: buffer is null!"); + return false; + } + + mRawData = new byte[MAX_RAW_DATA_SIZE]; + int item = 0; + + for (int i = 0; i < data.length - 2; i += 3) { + + if ((data[i] == 0) && (data[i + 1] == 0) && (data[i + 2] == 0)) { + mRawData[i] = 0; + mRawData[i + 1] = 0; + mRawData[i + 2] = 0; + } + + mRawData[i] = data[i + 1]; + mRawData[i + 1] = data[i + 2]; + mRawData[i + 2] = data[i]; + } + + return true; + } + + + public void dumpBasalProfile() { + LOG.debug("Basal Profile entries:"); + List entries = getEntries(); + for (int i = 0; i < entries.size(); i++) { + BasalProfileEntry entry = entries.get(i); + String startString = entry.startTime.toString("HH:mm"); + // this doesn't work + LOG.debug(String.format("Entry %d, rate=%.3f (0x%02X), start=%s (0x%02X)", i + 1, entry.rate, + entry.rate_raw, startString, entry.startTime_raw)); + + } + } + + + public String getBasalProfileAsString() { + StringBuffer sb = new StringBuffer("Basal Profile entries:\n"); + List entries = getEntries(); + for (int i = 0; i < entries.size(); i++) { + BasalProfileEntry entry = entries.get(i); + String startString = entry.startTime.toString("HH:mm"); + + sb.append(String.format("Entry %d, rate=%.3f, start=%s\n", i + 1, entry.rate, startString)); + } + + return sb.toString(); + } + + public String basalProfileToStringError() { + return "Basal Profile [rawData=" + ByteUtil.shortHexString(this.getRawData()) + "]"; + } + + + public String basalProfileToString() { + StringBuffer sb = new StringBuffer("Basal Profile ["); + List entries = getEntries(); + for (int i = 0; i < entries.size(); i++) { + BasalProfileEntry entry = entries.get(i); + String startString = entry.startTime.toString("HH:mm"); + + sb.append(String.format("%s=%.3f, ", startString, entry.rate)); + } + + sb.append("]"); + + return sb.toString(); + } + + + // TODO: this function must be expanded to include changes in which profile is in use. + // and changes to the profiles themselves. + public BasalProfileEntry getEntryForTime(Instant when) { + BasalProfileEntry rval = new BasalProfileEntry(); + List entries = getEntries(); + if (entries.size() == 0) { + LOG.warn(String.format("getEntryForTime(%s): table is empty", + when.toDateTime().toLocalTime().toString("HH:mm"))); + return rval; + } + // Log.w(TAG,"Assuming first entry"); + rval = entries.get(0); + if (entries.size() == 1) { + LOG.debug("getEntryForTime: Only one entry in profile"); + return rval; + } + + int localMillis = when.toDateTime().toLocalTime().getMillisOfDay(); + boolean done = false; + int i = 1; + while (!done) { + BasalProfileEntry entry = entries.get(i); + if (DEBUG_BASALPROFILE) { + LOG.debug(String.format("Comparing 'now'=%s to entry 'start time'=%s", when.toDateTime().toLocalTime() + .toString("HH:mm"), entry.startTime.toString("HH:mm"))); + } + if (localMillis >= entry.startTime.getMillisOfDay()) { + rval = entry; + if (DEBUG_BASALPROFILE) + LOG.debug("Accepted Entry"); + } else { + // entry at i has later start time, keep older entry + if (DEBUG_BASALPROFILE) + LOG.debug("Rejected Entry"); + done = true; + } + i++; + if (i >= entries.size()) { + done = true; + } + } + if (DEBUG_BASALPROFILE) { + LOG.debug(String.format("getEntryForTime(%s): Returning entry: rate=%.3f (%d), start=%s (%d)", when + .toDateTime().toLocalTime().toString("HH:mm"), rval.rate, rval.rate_raw, + rval.startTime.toString("HH:mm"), rval.startTime_raw)); + } + return rval; + } + + + public List getEntries() { + List entries = new ArrayList<>(); + + if (mRawData == null || mRawData[2] == 0x3f) { + LOG.warn("Raw Data is empty."); + return entries; // an empty list + } + boolean done = false; + int r, st; + + for (int i = 0; i < mRawData.length - 2; i += 3) { + + if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0)) + break; + + if ((mRawData[i] == 0) && (mRawData[i + 1] == 0) && (mRawData[i + 2] == 0x3f)) + break; + + r = MedtronicUtil.makeUnsignedShort(mRawData[i + 1], mRawData[i]); // readUnsignedByte(mRawData[i]); + st = readUnsignedByte(mRawData[i + 2]); + + try { + entries.add(new BasalProfileEntry(r, st)); + } catch (Exception ex) { + LOG.error("Error decoding basal profile from bytes: {}", ByteUtil.shortHexString(mRawData)); + throw ex; + } + + } + + return entries; + } + + + /** + * This is used to prepare new profile + * + * @param entry + */ + public void addEntry(BasalProfileEntry entry) { + if (listEntries == null) + listEntries = new ArrayList<>(); + + listEntries.add(entry); + } + + + public void generateRawDataFromEntries() { + + List outData = new ArrayList<>(); + + for (BasalProfileEntry profileEntry : listEntries) { + + byte[] strokes = MedtronicUtil.getBasalStrokes(profileEntry.rate, true); + + outData.add(profileEntry.rate_raw[0]); + outData.add(profileEntry.rate_raw[1]); + outData.add(profileEntry.startTime_raw); + } + + this.setRawData(MedtronicUtil.createByteArray(outData)); + + // return this.mRawData; + } + + + public Double[] getProfilesByHour() { + + List entries = null; + + try { + entries = getEntries(); + } catch (Exception ex) { + LOG.error("============================================================================="); + LOG.error(" Error generating entries. Ex.: " + ex, ex); + LOG.error(" rawBasalValues: " + ByteUtil.shortHexString(this.getRawData())); + LOG.error("============================================================================="); + + //FabricUtil.createEvent("MedtronicBasalProfileGetByHourError", null); + } + + if (entries == null || entries.size() == 0) { + Double[] basalByHour = new Double[24]; + + for (int i = 0; i < 24; i++) { + basalByHour[i] = 0.0d; + } + + return basalByHour; + } + + Double[] basalByHour = new Double[24]; + + PumpType pumpType = MedtronicUtil.getPumpStatus().pumpType; + + for (int i = 0; i < entries.size(); i++) { + BasalProfileEntry current = entries.get(i); + + int currentTime = (current.startTime_raw % 2 == 0) ? current.startTime_raw : current.startTime_raw - 1; + + currentTime = (currentTime * 30) / 60; + + int lastHour = 0; + if ((i + 1) == entries.size()) { + lastHour = 24; + } else { + BasalProfileEntry basalProfileEntry = entries.get(i + 1); + + int rawTime = (basalProfileEntry.startTime_raw % 2 == 0) ? basalProfileEntry.startTime_raw + : basalProfileEntry.startTime_raw - 1; + + lastHour = (rawTime * 30) / 60; + } + + // System.out.println("Current time: " + currentTime + " Next Time: " + lastHour); + + for (int j = currentTime; j < lastHour; j++) { + if (pumpType == null) + basalByHour[j] = current.rate; + else + basalByHour[j] = pumpType.determineCorrectBasalSize(current.rate); + } + } + + return basalByHour; + } + + + public static String getProfilesByHourToString(Double[] data) { + + StringBuilder stringBuilder = new StringBuilder(); + + for (Double value : data) { + stringBuilder.append(String.format("%.3f", value)); + stringBuilder.append(" "); + } + + return stringBuilder.toString(); + + } + + + public byte[] getRawData() { + return this.mRawData; + } + + + public String toString() { + return basalProfileToString(); + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + public boolean verify() { + + try { + getEntries(); + } catch (Exception ex) { + return false; + } + + Double[] profilesByHour = getProfilesByHour(); + + for (Double aDouble : profilesByHour) { + if (aDouble > 35.0d) + return false; + } + + return true; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java new file mode 100644 index 00000000000..9768ad2fc86 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BasalProfileEntry.java @@ -0,0 +1,89 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import org.joda.time.LocalTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by geoff on 6/1/15. + * This is a helper class for BasalProfile, only used for interpreting the contents of BasalProfile + * - fixed rate is not one bit but two + */ +public class BasalProfileEntry { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public byte[] rate_raw; + public double rate; + public byte startTime_raw; + public LocalTime startTime; // Just a "time of day" + + + public BasalProfileEntry() { + rate = -9.999E6; + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(0xFF, true); + startTime = new LocalTime(0); + startTime_raw = (byte)0xFF; + } + + + public BasalProfileEntry(double rate, int hour, int minutes) { + byte[] data = MedtronicUtil.getBasalStrokes(rate, true); + + rate_raw = new byte[2]; + rate_raw[0] = data[1]; + rate_raw[1] = data[0]; + + int interval = hour * 2; + + if (minutes == 30) { + interval++; + } + + startTime_raw = (byte)interval; + startTime = new LocalTime(hour, minutes == 30 ? 30 : 0); + } + + + public BasalProfileEntry(int rateStrokes, int startTimeInterval) { + // rateByte is insulin delivery rate, U/hr, in 0.025 U increments + // startTimeByte is time-of-day, in 30 minute increments + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateStrokes, true); + rate = rateStrokes * 0.025; + startTime_raw = (byte)startTimeInterval; + + try { + startTime = new LocalTime(startTimeInterval / 2, (startTimeInterval % 2) * 30); + } catch (Exception ex) { + LOG.error( + "Error creating BasalProfileEntry: startTimeInterval={}, startTime_raw={}, hours={}, rateStrokes={}", + startTimeInterval, startTime_raw, startTimeInterval / 2, rateStrokes); + throw ex; + } + + } + + + public BasalProfileEntry(byte rateByte, int startTimeByte) { + // rateByte is insulin delivery rate, U/hr, in 0.025 U increments + // startTimeByte is time-of-day, in 30 minute increments + rate_raw = MedtronicUtil.getByteArrayFromUnsignedShort(rateByte, true); + rate = rateByte * 0.025; + startTime_raw = (byte)startTimeByte; + startTime = new LocalTime(startTimeByte / 2, (startTimeByte % 2) * 30); + } + + + public void setStartTime(LocalTime localTime) { + this.startTime = localTime; + } + + + public void setRate(double rate) { + this.rate = rate; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BatteryStatusDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BatteryStatusDTO.java new file mode 100644 index 00000000000..93b5eb10cb7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BatteryStatusDTO.java @@ -0,0 +1,57 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import com.google.gson.annotations.Expose; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType; + +/** + * Created by andy on 6/14/18. + */ + +public class BatteryStatusDTO { + + @Expose + public BatteryStatusType batteryStatusType; + @Expose + public Double voltage; + + public boolean extendedDataReceived = false; + + + public int getCalculatedPercent(BatteryType batteryType) { + if (voltage == null || batteryType == BatteryType.None) { + return (batteryStatusType == BatteryStatusType.Low || batteryStatusType == BatteryStatusType.Unknown) ? 18 : 70; + } + + double percent = (voltage - batteryType.lowVoltage) / (batteryType.highVoltage - batteryType.lowVoltage); + + int percentInt = (int) (percent * 100.0d); + + if (percentInt<0) + percentInt = 1; + + if (percentInt > 100) + percentInt = 100; + + return percentInt; + } + + + public String toString() { + return String.format(Locale.ENGLISH, "BatteryStatusDTO [voltage=%.2f, alkaline=%d, lithium=%d, niZn={}]", + voltage == null ? 0.0f : voltage, + getCalculatedPercent(BatteryType.Alkaline), + getCalculatedPercent(BatteryType.Lithium), + getCalculatedPercent(BatteryType.NiZn)); + } + + + public enum BatteryStatusType { + Normal, + Low, + Unknown + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusDTO.java new file mode 100644 index 00000000000..8574bd831a7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusDTO.java @@ -0,0 +1,160 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import com.google.gson.annotations.Expose; + +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpBolusType; + +/** + * Application: GGC - GNU Gluco Control + * Plug-in: Pump Tool (support for Pump devices) + *

+ * See AUTHORS for copyright information. + *

+ * 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, or (at your option) any later + * version. + *

+ * 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 along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + *

+ * Filename: BolusDTO Description: Bolus DTO + *

+ * Author: Andy {andy@atech-software.com} + */ + +public class BolusDTO extends PumpTimeStampedRecord { + + @Expose + private Double requestedAmount; + @Expose + private Double deliveredAmount; + @Expose + private Double immediateAmount; // when Multiwave this is used + @Expose + private Integer duration; + @Expose + private PumpBolusType bolusType; + private Double insulinOnBoard; + + + public BolusDTO() { + // this.decimalPrecission = 2; + } + + + public Double getRequestedAmount() { + return requestedAmount; + } + + + public void setRequestedAmount(Double requestedAmount) { + this.requestedAmount = requestedAmount; + } + + + public Double getDeliveredAmount() { + return deliveredAmount; + } + + + public void setDeliveredAmount(Double deliveredAmount) { + this.deliveredAmount = deliveredAmount; + } + + + public Integer getDuration() { + return duration; + } + + + public void setDuration(Integer duration) { + this.duration = duration; + } + + + public PumpBolusType getBolusType() { + return bolusType; + } + + + public void setBolusType(PumpBolusType bolusType) { + this.bolusType = bolusType; + } + + + public Double getInsulinOnBoard() { + return insulinOnBoard; + } + + + public void setInsulinOnBoard(Double insulinOnBoard) { + this.insulinOnBoard = insulinOnBoard; + } + + + private String getDurationString() { + int minutes = this.duration; + + int h = minutes / 60; + + minutes -= (h * 60); + + return StringUtil.getLeadingZero(h, 2) + ":" + StringUtil.getLeadingZero(minutes, 2); + } + + + public String getValue() { + if ((bolusType == PumpBolusType.Normal) || (bolusType == PumpBolusType.Audio)) { + return getFormattedDecimal(this.deliveredAmount); + } else if (bolusType == PumpBolusType.Extended) { + return String.format("AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(this.deliveredAmount), + getDurationString()); + } else { + return String.format("AMOUNT=%s;AMOUNT_SQUARE=%s;DURATION=%s", getFormattedDecimal(this.immediateAmount), + getFormattedDecimal(this.deliveredAmount), getDurationString()); + } + } + + + public String getDisplayableValue() { + String value = getValue(); + + value = value.replace("AMOUNT_SQUARE=", "Amount Square: "); + value = value.replace("AMOUNT=", "Amount: "); + value = value.replace("DURATION=", "Duration: "); + + return value; + } + + + public Double getImmediateAmount() { + return immediateAmount; + } + + + public void setImmediateAmount(Double immediateAmount) { + this.immediateAmount = immediateAmount; + } + + + public String getFormattedDecimal(double value) { + return StringUtil.getFormatedValueUS(value, 2); + } + + + public String getBolusKey() { + return "Bolus_" + this.bolusType.name(); + + } + + + @Override + public String toString() { + return "BolusDTO [type=" + bolusType.name() + ", " + getValue() + "]"; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusWizardDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusWizardDTO.java new file mode 100644 index 00000000000..5d9710a5c50 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/BolusWizardDTO.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; + +/** + * Created by andy on 18.05.15. + */ +public class BolusWizardDTO extends PumpTimeStampedRecord { + + // bloodGlucose and bgTarets are in mg/dL + public Integer bloodGlucose = 0; // mg/dL + public Integer carbs = 0; + public String chUnit = "g"; + + public Float carbRatio = 0.0f; + public Float insulinSensitivity = 0.0f; + public Integer bgTargetLow = 0; + public Integer bgTargetHigh = 0; + public Float bolusTotal = 0.0f; + public Float correctionEstimate = 0.0f; + public Float foodEstimate = 0.0f; + public Float unabsorbedInsulin = 0.0f; + + + // public LocalDateTime localDateTime; + // public long atechDateTime; + + public String getValue() { + return String.format("BG=%d;CH=%d;CH_UNIT=%s;CH_INS_RATIO=%5.3f;BG_INS_RATIO=%5.3f;" + + "BG_TARGET_LOW=%d;BG_TARGET_HIGH=%d;BOLUS_TOTAL=%5.3f;" + + "BOLUS_CORRECTION=%5.3f;BOLUS_FOOD=%5.3f;UNABSORBED_INSULIN=%5.3f", // + bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, // + bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin); + } + + public String getDisplayableValue() { + return String.format("Bg=%d, CH=%d %s, Ch/Ins Ratio=%5.3f, Bg/Ins Ratio=%5.3f;" + + "Bg Target(L/H)=%d/%d, Bolus: Total=%5.3f, " + + "Correction=%5.3f, Food=%5.3f, IOB=%5.3f", // + bloodGlucose, carbs, chUnit, carbRatio, insulinSensitivity, bgTargetLow, // + bgTargetHigh, bolusTotal, correctionEstimate, foodEstimate, unabsorbedInsulin); + } + + + public String toString() { + return "BolusWizardDTO [dateTime=" + DateTimeUtil.toString(atechDateTime) + ", " + getValue() + "]"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/ClockDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/ClockDTO.java new file mode 100644 index 00000000000..1623e03ff56 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/ClockDTO.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import org.joda.time.LocalDateTime; + +/** + * Created by andy on 2/27/19. + */ + +public class ClockDTO { + + public LocalDateTime localDeviceTime; + + public LocalDateTime pumpTime; + + public int timeDifference; // s (pump -> local) +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/DailyTotalsDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/DailyTotalsDTO.java new file mode 100644 index 00000000000..495bce8a312 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/DailyTotalsDTO.java @@ -0,0 +1,257 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import com.google.common.base.MoreObjects; +import com.google.gson.annotations.Expose; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.db.TDD; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; + +/** + * Created by andy on 11/3/18. + */ + +/** + * NOTE: Decoding is only done for insulin part, everything else is pretty must left undecoded. + */ + +public class DailyTotalsDTO { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + // bg avg, bg low hi, number Bgs, + // Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0, + // Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs, + // Bolus=1.7, Fodd, Corr, Manual=1.7, + // Num bOlus=1, food/corr, Food+corr, manual bolus=1 + private Double bgAvg; + private Double bgLow; + private Double bgHigh; + private Integer bgCount; + + private Double sensorAvg; + private Double sensorMin; + private Double sensorMax; + private Integer sensorCalcCount; + private Integer sensorDataCount; + + @Expose + private Double insulinTotal = 0.0d; + @Expose + private Double insulinBasal = 0.0d; + @Expose + private Double insulinBolus = 0.0d; + private Double insulinCarbs; + + private Double bolusTotal; + private Double bolusFood; + private Double bolusFoodAndCorr; + private Double bolusCorrection; + private Double bolusManual; + + private Integer bolusCount; + private Integer bolusCountFoodOrCorr; + // Integer bolusCountCorr; + Integer bolusCountFoodAndCorr; + Integer bolusCountManual; + private Integer bolusCountFood; + private Integer bolusCountCorr; + + PumpHistoryEntry entry; + + + public DailyTotalsDTO(PumpHistoryEntry entry) { + this.entry = entry; + + switch (entry.getEntryType()) { + case EndResultTotals: + decodeEndResultsTotals(entry); + break; + + case DailyTotals515: + decodeDailyTotals515(entry.getBody()); + break; + + case DailyTotals522: + decodeDailyTotals522(entry.getBody()); + break; + + case DailyTotals523: + decodeDailyTotals523(entry.getBody()); + break; + + default: + break; + } + + setDisplayable(); + } + + + private void setDisplayable() { + + if (this.insulinBasal == null) { + this.entry.setDisplayableValue("Total Insulin: " + StringUtil.getFormatedValueUS(this.insulinTotal, 2)); + } else { + this.entry.setDisplayableValue("Basal Insulin: " + StringUtil.getFormatedValueUS(this.insulinBasal, 2) + + ", Total Insulin: " + StringUtil.getFormatedValueUS(this.insulinTotal, 2)); + } + + } + + + private void decodeEndResultsTotals(PumpHistoryEntry entry) { + double totals = ByteUtil.toInt((int) entry.getHead()[0], (int) entry.getHead()[1], (int) entry.getHead()[2], + (int) entry.getHead()[3], ByteUtil.BitConversion.BIG_ENDIAN) * 0.025d; + + this.insulinTotal = totals; + + entry.addDecodedData("Totals", totals); + } + + + private void testDecode(byte[] data) { + + // Daily + + byte[] body = data; // entry.getBody(); + //System.out.println("Totals 522"); + + for (int i = 0; i < body.length - 2; i++) { + + int j = ByteUtil.toInt(body[i], body[i + 1]); + int k = ByteUtil.toInt(body[i], body[i + 1], body[i + 2]); + + int j1 = ByteUtil.toInt(body[i + 1], body[i]); + int k1 = ByteUtil.toInt(body[i + 2], body[i + 1], body[i]); + + System.out.println(String.format( + "index: %d, number=%d, del/40=%.3f, del/10=%.3f, singular=%d, sing_hex=%s", i, j, j / 40.0d, j / 10.0d, + body[i], ByteUtil.shortHexString(body[i]))); + + System.out.println(String.format(" number[k,j1,k1]=%d / %d /%d, del/40=%.3f, del/40=%.3f, del/40=%.3f", + k, j1, k1, k / 40.0d, j1 / 40.0d, k1 / 40.0d)); + + } + } + + + private void decodeDailyTotals515(byte[] data) { + // LOG.debug("Can't decode DailyTotals515: Body={}", ByteUtil.getHex(data)); + + this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d; + this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d; + this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d; + + // Delivery Stats: BG AVG: Bg Low/Hi=none,Number BGs=0 + // Delivery Stats: INSULIN: Basal 22.30, Bolus=4.20, Catbs = 0g (26.5) + // Delivery Stats: BOLUS: Food=0.00, Corr=0.00, Manual=4.20 + // Delivery Stats: NUM BOLUS: Food/Corr=0,Food+Corr=0, Manual=3 + + //LOG.debug("515: {}", toString()); + } + + + private void decodeDailyTotals522(byte[] data) { + + this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d; + this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d; + this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d; + + this.bolusTotal = ByteUtil.toInt(data[17], data[18], data[19]) / 40.0d; + this.bolusFood = ByteUtil.toInt(data[21], data[22]) / 40.0d; + this.bolusCorrection = ByteUtil.toInt(data[23], data[24], data[25]) / 40.0d; + this.bolusManual = ByteUtil.toInt(data[26], data[27], data[28]) / 40.0d; + + bolusCount = ByteUtil.asUINT8(data[30]); + bolusCountFoodOrCorr = ByteUtil.asUINT8(data[31]); + bolusCountFoodAndCorr = ByteUtil.asUINT8(data[32]); + bolusCountManual = ByteUtil.asUINT8(data[33]); + + // bg avg, bg low hi, number Bgs, + // Sen Avg, Sen Lo/Hi, Sens Cal/Data = 0/0, + // Insulin=19.8[8,9], Basal[10,11], Bolus[13,14], Carbs, + // Bolus=1.7[18,19], Fodd, Corr, Manual=1.7[27,28], + // Num bOlus=1, food/corr, Food+corr, manual bolus=1 + + //LOG.debug("522: {}", toString()); + } + + + private void decodeDailyTotals523(byte[] data) { + + this.insulinTotal = ByteUtil.toInt(data[8], data[9]) / 40.0d; + this.insulinBasal = ByteUtil.toInt(data[10], data[11]) / 40.0d; + this.insulinBolus = ByteUtil.toInt(data[13], data[14]) / 40.0d; + this.insulinCarbs = ByteUtil.toInt(data[16], data[17]) * 1.0d; + + this.bolusFood = ByteUtil.toInt(data[18], data[19]) / 40.0d; + this.bolusCorrection = ByteUtil.toInt(data[20], data[21]) / 40.0d; + this.bolusFoodAndCorr = ByteUtil.toInt(data[22], data[23]) / 40.0d; + this.bolusManual = ByteUtil.toInt(data[24], data[25]) / 40.0d; + + this.bolusCountFood = ByteUtil.asUINT8(data[26]); + this.bolusCountCorr = ByteUtil.asUINT8(data[27]); + this.bolusCountFoodAndCorr = ByteUtil.asUINT8(data[28]); + this.bolusCountManual = ByteUtil.asUINT8(data[29]); // + + + // Delivery Stats: Carbs=11, Total Insulin=3.850, Basal=2.000 + // Delivery Stats: Basal 52,Bolus 1.850, Bolus=48%o + // Delivery Stats: Food only=0.9, Food only#=1, Corr only = 0.0 + // Delivery Stats: #Corr_only=0,Food+Corr=0.000, #Food+Corr=0 + // Delivery Stats: Manual = 0.95, #Manual=5 + + //LOG.debug("523: {}", toString()); + } + + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) // + .add("bgAvg", bgAvg) // + .add("bgLow", bgLow) // + .add("bgHigh", bgHigh) // + .add("bgCount", bgCount) // + .add("sensorAvg", sensorAvg) // + .add("sensorMin", sensorMin) // + .add("sensorMax", sensorMax) // + .add("sensorCalcCount", sensorCalcCount) // + .add("sensorDataCount", sensorDataCount) // + .add("insulinTotal", insulinTotal) // + .add("insulinBasal", insulinBasal) // + .add("insulinBolus", insulinBolus) // + .add("insulinCarbs", insulinCarbs) // + .add("bolusTotal", bolusTotal) // + .add("bolusFood", bolusFood) // + .add("bolusCorrection", bolusCorrection) // + .add("bolusManual", bolusManual) // + .add("bolusCount", bolusCount) // + .add("bolusCountFoodOrCorr", bolusCountFoodOrCorr) // + .add("bolusCountFoodAndCorr", bolusCountFoodAndCorr) // + .add("bolusCountFood", bolusCountFood) // + .add("bolusCountCorr", bolusCountCorr) // + .add("bolusCountManual", bolusCountManual) // + .omitNullValues() // + .toString(); + } + + + public void setTDD(TDD tdd) { + tdd.date = DateTimeUtil.toMillisFromATD(this.entry.atechDateTime); + tdd.basal = insulinBasal; + tdd.bolus = insulinBolus; + tdd.total = insulinTotal; + } + + + public boolean doesEqual(TDD tdd) { + return tdd.total == this.insulinTotal && tdd.bolus == this.insulinBolus && tdd.basal == this.insulinBasal; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpSettingDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpSettingDTO.java new file mode 100644 index 00000000000..1868064d8b1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpSettingDTO.java @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpConfigurationGroup; + +/** + * Created by andy on 6/6/18. + */ + +public class PumpSettingDTO { + + public String key; + public String value; + + PumpConfigurationGroup configurationGroup; + + + public PumpSettingDTO(String key, String value, PumpConfigurationGroup configurationGroup) { + this.key = key; + this.value = value; + this.configurationGroup = configurationGroup; + } + + + @Override + public String toString() { + return "PumpSettingDTO [key=" + key + ",value=" + value + ",group=" + configurationGroup.name() + "]"; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpTimeStampedRecord.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpTimeStampedRecord.java new file mode 100644 index 00000000000..84ad0914b33 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/PumpTimeStampedRecord.java @@ -0,0 +1,28 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; + +/** + * Created by andy on 6/2/18. + */ +public class PumpTimeStampedRecord { + + protected int decimalPrecission = 2; + public long atechDateTime; + + + public long getAtechDateTime() { + return this.atechDateTime; + } + + + public void setAtechDateTime(long atechDateTime) { + this.atechDateTime = atechDateTime; + } + + + public String getFormattedDecimal(double value) { + return StringUtil.getFormatedValueUS(value, this.decimalPrecission); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java new file mode 100644 index 00000000000..bd9df92642a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalPair.java @@ -0,0 +1,129 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by geoff on 5/29/15. + *

+ * Just need a class to keep the pair together, for parcel transport. + */ +public class TempBasalPair extends info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public TempBasalPair() { + } + + + /** + * This constructor is for use with PumpHistoryDecoder + * + * @param rateByte + * @param startTimeByte + * @param isPercent + */ + public TempBasalPair(byte rateByte, int startTimeByte, boolean isPercent) { + super(); + + int rateInt = ByteUtil.asUINT8(rateByte); + + if (isPercent) + this.insulinRate = rateByte; + else + this.insulinRate = rateInt * 0.025; + this.durationMinutes = startTimeByte * 30; + this.isPercent = isPercent; + } + + + public TempBasalPair(byte[] response) { + + if (L.isEnabled(L.PUMPCOMM)) + LOG.debug("Received TempBasal response: " + ByteUtil.getHex(response)); + + isPercent = response[0] == 1; + + if (isPercent) { + insulinRate = response[1]; + } else { + int strokes = MedtronicUtil.makeUnsignedShort(response[2], response[3]); + + insulinRate = strokes / 40.0d; + } + + if (response.length<6) { + durationMinutes = ByteUtil.asUINT8(response[4]); + } else { + durationMinutes = MedtronicUtil.makeUnsignedShort(response[4], response[5]); + } + + LOG.warn("TempBasalPair (with {} byte response): {}", response.length, toString()); + + } + + + public TempBasalPair(double insulinRate, boolean isPercent, int durationMinutes) { + super(insulinRate, isPercent, durationMinutes); + } + + + public byte[] getAsRawData() { + + List list = new ArrayList(); + + list.add((byte) 5); + + byte[] insulinRate = MedtronicUtil.getBasalStrokes(this.insulinRate, true); + byte timeMin = (byte) MedtronicUtil.getIntervalFromMinutes(durationMinutes); + + // list.add((byte) 0); // ? + + // list.add((byte) 0); // is_absolute + + if (insulinRate.length == 1) + list.add((byte) 0x00); + else + list.add(insulinRate[0]); + + list.add(insulinRate[1]); + // list.add((byte) 0); // percent amount + + list.add(timeMin); // 3 (time) - OK + + if (insulinRate.length == 1) + list.add((byte) 0x00); + else + list.add(insulinRate[0]); + + list.add(insulinRate[1]); + + return MedtronicUtil.createByteArray(list); + } + + public boolean isCancelTBR() { + return (MedtronicUtil.isSame(insulinRate, 0.0d) && durationMinutes == 0); + } + + + public String getDescription() { + if (isCancelTBR()) { + return "Cancel TBR"; + } + + if (isPercent) { + return String.format(Locale.ENGLISH, "Rate: %.0f%%, Duration: %d min", insulinRate, durationMinutes); + } else { + return String.format(Locale.ENGLISH, "Rate: %.3f U, Duration: %d min", insulinRate, durationMinutes); + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.java new file mode 100644 index 00000000000..86cba28184f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/data/dto/TempBasalProcessDTO.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.data.dto; + +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; + +public class TempBasalProcessDTO { + + public PumpHistoryEntry itemOne; + public PumpHistoryEntry itemTwo; + + public Operation processOperation = Operation.None; + + public int getDuration() { + if (itemTwo == null) { + TempBasalPair tbr = (TempBasalPair) itemOne.getDecodedDataEntry("Object"); + return tbr.getDurationMinutes(); + } else { + int difference = DateTimeUtil.getATechDateDiferenceAsMinutes(itemOne.atechDateTime, itemTwo.atechDateTime); + return difference; + } + } + + + public enum Operation { + None, + Add, + Edit + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BasalProfileStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BasalProfileStatus.java new file mode 100644 index 00000000000..0710e6ca4c6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BasalProfileStatus.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +/** + * Created by andy on 1/20/19. + */ + +public enum BasalProfileStatus { + + NotInitialized, // + ProfileOK, // + ProfileChanged, // + ; + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BatteryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BatteryType.java new file mode 100644 index 00000000000..5c7bf245a2e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/BatteryType.java @@ -0,0 +1,47 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +/** + * Created by andy on 6/4/18. + */ + +public enum BatteryType { + + None(R.string.key_medtronic_pump_battery_no, 0, 0), + Alkaline(R.string.key_medtronic_pump_battery_alkaline, 1.20d, 1.47d), // + Lithium(R.string.key_medtronic_pump_battery_lithium, 1.22d, 1.64d), // + NiZn(R.string.key_medtronic_pump_battery_nizn, 1.40d, 1.70d) // + ; + + private final String description; + public double lowVoltage; + public double highVoltage; + + static Map mapByDescription; + + static { + mapByDescription = new HashMap<>(); + + for (BatteryType value : values()) { + mapByDescription.put(value.description, value); + } + } + + BatteryType(int resId, double lowVoltage, double highVoltage) { + this.description = MainApp.gs(resId); + this.lowVoltage = lowVoltage; + this.highVoltage = highVoltage; + } + + public static BatteryType getByDescription(String batteryTypeStr) { + if (mapByDescription.containsKey(batteryTypeStr)) { + return mapByDescription.get(batteryTypeStr); + } + return BatteryType.None; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/CommandValueDefinitionMDTType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/CommandValueDefinitionMDTType.java new file mode 100644 index 00000000000..f061b860cf2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/CommandValueDefinitionMDTType.java @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.CommandValueDefinitionType; + +/** + * Created by andy on 4/5/19. + */ + +public enum CommandValueDefinitionMDTType implements CommandValueDefinitionType { + GetModel, // + TuneUp, // + GetProfile, // + GetTBR, // + ; + + @Override + public String getName() { + return this.name(); + } + + + @Override + public String getDescription() { + return null; + } + + + @Override + public String commandAction() { + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java new file mode 100755 index 00000000000..516085269ad --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCommandType.java @@ -0,0 +1,453 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.MessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.PumpAckMessageBody; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.message.UnknownMessageBody; + +/** + * Taken from GNU Gluco Control diabetes management software (ggc.sourceforge.net) + *

+ * Description: Medtronic Commands (Pump and CGMS) for all 512 and later models (just 5xx) + *

+ * Link to original/unmodified file: + * https://sourceforge.net/p/ggc/code/HEAD/tree/trunk/ggc-plugins/ggc-plugins-base/src/ + * main/java/ggc/plugin/device/impl/minimed/enums/MinimedCommandType.java + *

+ * A lot of stuff has been removed because it is not needed anymore (historical stuff from CareLink + * and Carelink USB communication. + *

+ * Author: Andy {andy@atech-software.com} + */ +public enum MedtronicCommandType implements Serializable // , MinimedCommandTypeInterface +{ + InvalidCommand(0, "Invalid Command", null, null), // + + // Pump Responses (9) + CommandACK(0x06, "ACK - Acknowledge", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // + CommandNAK(0x15, "NAK - Not Acknowledged", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // + + // All (8) + PushAck(91, "Push ACK", MedtronicDeviceType.All, MinimedCommandParameterType.FixedParameters, getByteArray(2)), // + + PushEsc(91, "Push Esc", MedtronicDeviceType.All, MinimedCommandParameterType.FixedParameters, getByteArray(1)), // + + PushButton(0x5b, "Push Button", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // 91 + + RFPowerOn(93, "RF Power On", MedtronicDeviceType.All, MinimedCommandParameterType.FixedParameters, getByteArray( + 1, 10)), // + + RFPowerOff(93, "RF Power Off", MedtronicDeviceType.All, MinimedCommandParameterType.FixedParameters, getByteArray( + 0, 0)), // + + // SetSuspend(77, "Set Suspend", MinimedTargetType.InitCommand, MedtronicDeviceType.All, + // MinimedCommandParameterType.FixedParameters, getByteArray(1)), // + + // CancelSuspend(77, "Cancel Suspend", MinimedTargetType.InitCommand, MedtronicDeviceType.All, + // MinimedCommandParameterType.FixedParameters, getByteArray(0)), // + + PumpState(131, "Pump State", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // + + ReadPumpErrorStatus(117, "Pump Error Status", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // + + // 511 (InitCommand = 2, Config 7, Data = 1(+3) +// DetectBolus(75, "Detect Bolus", MedtronicDeviceType.Medtronic_511, MinimedCommandParameterType.FixedParameters, getByteArray( +// 0, 0, 0)), // + + // RemoteControlIds(118, "Remote Control Ids", MinimedTargetType.PumpConfiguration_NA, MedtronicDeviceType.All, + // MinimedCommandParameterType.NoParameters), // + + // FirmwareVersion(116, "Firmware Version", MinimedTargetType.InitCommand, MedtronicDeviceType.All, + // MinimedCommandParameterType.NoParameters), // + + // PumpId(113, "Pump Id", MinimedTargetType.PumpConfiguration, MedtronicDeviceType.All, + // MinimedCommandParameterType.NoParameters), // init + + SetRealTimeClock(0x40, "Set Pump Time", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters, // + 0), // + + GetRealTimeClock(112, "Get Pump Time", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters, // + 7, R.string.medtronic_cmd_desc_get_time), // 0x70 + + GetBatteryStatus(0x72, "Get Battery Status", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters), // + // GetBattery((byte) 0x72), // + + GetRemainingInsulin(0x73, "Read Remaining Insulin", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters, 2), // 115 + + SetBolus(0x42, "Set Bolus", MedtronicDeviceType.All, MinimedCommandParameterType.NoParameters, // + 0, R.string.medtronic_cmd_desc_set_bolus), // 66 + + // 512 + ReadTemporaryBasal(0x98, "Read Temporary Basal", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 5, R.string.medtronic_cmd_desc_get_tbr), // 152 + + SetTemporaryBasal(76, "Set Temporay Basal", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 0, R.string.medtronic_cmd_desc_set_tbr), + + // 512 Config + PumpModel(141, "Pump Model", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 5, R.string.medtronic_cmd_desc_get_model), // 0x8D + + // BGTargets_512(140, "BG Targets", MinimedTargetType.PumpConfiguration, MedtronicDeviceType.Medtronic_512_712, + // MinimedCommandParameterType.NoParameters), // + + // BGUnits(137, "BG Units", MinimedTargetType.PumpConfiguration, MedtronicDeviceType.Medtronic_512andHigher, + // MinimedCommandParameterType.NoParameters), // + + // Language(134, "Language", MinimedTargetType.PumpConfiguration, MedtronicDeviceType.Medtronic_512andHigher, + // MinimedCommandParameterType.NoParameters), // + + Settings_512(145, "Configuration", MedtronicDeviceType.Medtronic_512_712, MinimedCommandParameterType.NoParameters, // + 64, 1, 18, R.string.medtronic_cmd_desc_get_settings), // + + // BGAlarmClocks(142, "BG Alarm Clocks", MinimedTargetType.PumpConfiguration, + // MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters), // + + // BGAlarmEnable(151, "BG Alarm Enable", MinimedTargetType.PumpConfiguration, + // MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters), // + + // BGReminderEnable(144, "BG Reminder Enable", MinimedTargetType.PumpConfiguration, + // MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters), // + + // ReadInsulinSensitivities(0x8b, "Read Insulin Sensitivities", MinimedTargetType.PumpConfiguration, + // MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters), // 139 + + // 512 Data + GetHistoryData(128, "Get History", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.SubCommands, // + 1024, 16, 1024, R.string.medtronic_cmd_desc_get_history), // 0x80 + + GetBasalProfileSTD(146, "Get Profile Standard", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_get_basal_profile), // 146 + + GetBasalProfileA(147, "Get Profile A", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_get_basal_profile), + + GetBasalProfileB(148, "Get Profile B", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_get_basal_profile), // 148 + + SetBasalProfileSTD(0x6f, "Set Profile Standard", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_set_basal_profile), // 111 + + SetBasalProfileA(0x30, "Set Profile A", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_set_basal_profile), // 48 + + SetBasalProfileB(0x31, "Set Profile B", MedtronicDeviceType.Medtronic_512andHigher, MinimedCommandParameterType.NoParameters, // + 64, 3, 192, R.string.medtronic_cmd_desc_set_basal_profile), // 49 + + // 515 + PumpStatus(206, "Pump Status", MedtronicDeviceType.Medtronic_515andHigher, MinimedCommandParameterType.NoParameters), // PumpConfiguration + + Settings(192, "Configuration", MedtronicDeviceType.Medtronic_515andHigher, MinimedCommandParameterType.NoParameters, // + 64, 1, 21, R.string.medtronic_cmd_desc_get_settings), // + + // 522 + SensorSettings_522(153, "Sensor Configuration", MedtronicDeviceType.Medtronic_522andHigher, MinimedCommandParameterType.NoParameters), // + + GlucoseHistory(154, "Glucose History", MedtronicDeviceType.Medtronic_522andHigher, MinimedCommandParameterType.SubCommands, 1024, 32, 0, null), // + + // 523 + SensorSettings(207, "Sensor Configuration", MedtronicDeviceType.Medtronic_523andHigher, MinimedCommandParameterType.NoParameters), // + + // 553 + // 554 + + // var MESSAGES = { + // READ_TIME : 0x70, + // READ_BATTERY_STATUS: 0x72, + // READ_HISTORY : 0x80, + // READ_CARB_RATIOS : 0x8A, + // READ_INSULIN_SENSITIVITIES: 0x8B, + // READ_MODEL : 0x8D, + // READ_PROFILE_STD : 0x92, + // READ_PROFILE_A : 0x93, + // READ_PROFILE_B : 0x94, + // READ_CBG_HISTORY: 0x9A, + // READ_ISIG_HISTORY: 0x9B, + // READ_CURRENT_PAGE : 0x9D, + // READ_BG_TARGETS : 0x9F, + // READ_SETTINGS : 0xC0, 192 + // READ_CURRENT_CBG_PAGE : 0xCD + // }; + + // Fake Commands + + CancelTBR(), + ; + + static Map mapByCode; + + static { + MedtronicCommandType.RFPowerOn.maxAllowedTime = 17000; + MedtronicCommandType.RFPowerOn.allowedRetries = 0; + MedtronicCommandType.RFPowerOn.recordLength = 0; + MedtronicCommandType.RFPowerOn.minimalBufferSizeToStartReading = 1; + + mapByCode = new HashMap<>(); + + for (MedtronicCommandType medtronicCommandType : values()) { + mapByCode.put(medtronicCommandType.getCommandCode(), medtronicCommandType); + } + } + + public byte commandCode = 0; + public String commandDescription = ""; + public byte[] commandParameters = null; + public int commandParametersCount = 0; + public int maxRecords = 1; + private Integer resourceId; + public int command_type = 0; + public int allowedRetries = 2; + public int maxAllowedTime = 2000; + public MinimedCommandParameterType parameterType; + public int minimalBufferSizeToStartReading = 14; + public int expectedLength = 0; + //MinimedTargetType targetType; + MedtronicDeviceType devices; + private int recordLength = 64; + + + MedtronicCommandType() { + // this is for "fake" commands needed by AAPS MedtronicUITask + } + + + // MedtronicCommandType(int code, String description, MedtronicDeviceType devices, +// MinimedCommandParameterType parameterType) { +// this(code, description, devices, parameterType, 64, 1, 0, 0, 0, 0); +// } +// +// +// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, +// MinimedCommandParameterType parameterType, int expectedLength) { +// this(code, description, devices, parameterType, 64, 1, 0, 0, 0, expectedLength); +// } +// +// +// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, +// MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType) { +// this(code, description, devices, parameterType, recordLength, maxRecords, 0, 0, commandType, 0); +// } +// +// +// MedtronicCommandType(int code, String description, MedtronicDeviceType devices, +// MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType, +// int expectedLength) { +// this(code, description, devices, parameterType, recordLength, maxRecords, 0, 0, commandType, +// expectedLength); +// } +// +// + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, + MinimedCommandParameterType parameterType, byte[] cmd_params) { + this(code, description, devices, parameterType, 0, 1, 0, 0, 11, 0); + + this.commandParameters = cmd_params; + this.commandParametersCount = cmd_params.length; + } + + + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // + MinimedCommandParameterType parameterType) { + + this(code, description, devices, parameterType, 64, 1, 0, null); + } + + + // NEW + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, + MinimedCommandParameterType parameterType, int recordLength, int maxRecords, int commandType) { + this(code, description, devices, parameterType, recordLength, maxRecords, 0, null); + } + + + // NEW + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // + MinimedCommandParameterType parameterType, int expectedLength) { + this(code, description, devices, parameterType, 64, 1, expectedLength, null); + } + + + // NEW + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // + MinimedCommandParameterType parameterType, int expectedLength, int resourceId) { + this(code, description, devices, parameterType, 64, 1, expectedLength, resourceId); + } + + + // NEW + MedtronicCommandType(int code, String description, + MedtronicDeviceType devices, // + MinimedCommandParameterType parameterType, int recordLength, int max_recs, int expectedLength, + Integer resourceId) { + this.commandCode = (byte) code; + this.commandDescription = description; + this.devices = devices; + this.recordLength = recordLength; + this.maxRecords = max_recs; + this.resourceId = resourceId; + + this.commandParametersCount = 0; + this.allowedRetries = 2; + this.parameterType = parameterType; + this.expectedLength = expectedLength; + + if (this.parameterType == MinimedCommandParameterType.SubCommands) { + this.minimalBufferSizeToStartReading = 200; + } + } + + + @Deprecated + MedtronicCommandType(int code, String description, MedtronicDeviceType devices, // + MinimedCommandParameterType parameterType, int recordLength, int max_recs, int addy, // + int addy_len, int cmd_type, int expectedLength) { + this.commandCode = (byte) code; + this.commandDescription = description; + //this.targetType = targetType; + this.devices = devices; + this.recordLength = recordLength; + this.maxRecords = max_recs; + + this.command_type = cmd_type; + this.commandParametersCount = 0; + this.allowedRetries = 2; + this.parameterType = parameterType; + this.expectedLength = expectedLength; + + if (this.parameterType == MinimedCommandParameterType.SubCommands) { + this.minimalBufferSizeToStartReading = 200; + } + + } + + + private static HashMap getDeviceTypesArray(MedtronicDeviceType... types) { + HashMap hashMap = new HashMap(); + + for (MedtronicDeviceType type : types) { + hashMap.put(type, null); + } + + return hashMap; + } + + + private static byte[] getByteArray(int... data) { + byte[] array = new byte[data.length]; + + for (int i = 0; i < data.length; i++) { + array[i] = (byte) data[i]; + } + + return array; + } + + + private static int[] getIntArray(int... data) { + return data; + } + + + public static MedtronicCommandType getByCode(byte code) { + if (mapByCode.containsKey(code)) { + return mapByCode.get(code); + } else { + return MedtronicCommandType.InvalidCommand; + } + } + + + public static MessageBody constructMessageBody(MedtronicCommandType messageType, byte[] bodyData) { + switch (messageType) { + case CommandACK: + return new PumpAckMessageBody(bodyData); + default: + return new UnknownMessageBody(bodyData); + } + } + + + public static MedtronicCommandType getSettings(MedtronicDeviceType medtronicPumpModel) { + if (MedtronicDeviceType.isSameDevice(medtronicPumpModel, MedtronicDeviceType.Medtronic_512_712)) + return MedtronicCommandType.Settings_512; + else + return MedtronicCommandType.Settings; + } + + + /** + * Get Full Command Description + * + * @return command description + */ + public String getFullCommandDescription() { + return "Command [name=" + this.name() + ", id=" + this.commandCode + ",description=" + this.commandDescription + + "] "; + } + + + public boolean canReturnData() { + System.out.println("CanReturnData: ]id=" + this.name() + "max=" + this.maxRecords + "recLen=" + recordLength); + return (this.maxRecords * this.recordLength) > 0; + } + + + public int getRecordLength() { + return recordLength; + } + + + public int getMaxRecords() { + return maxRecords; + } + + + public byte getCommandCode() { + return commandCode; + } + + + public int getCommandParametersCount() { + if (this.commandParameters == null) { + return 0; + } else { + return this.commandParameters.length; + } + } + + + public byte[] getCommandParameters() { + return commandParameters; + } + + + public boolean hasCommandParameters() { + return (getCommandParametersCount() > 0); + } + + + public String toString() { + return name(); + } + + + public String getCommandDescription() { + return this.commandDescription; + } + + + public Integer getResourceId() { + return resourceId; + } + + public enum MinimedCommandParameterType { + NoParameters, // + FixedParameters, // + SubCommands // + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCustomActionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCustomActionType.java new file mode 100644 index 00000000000..af0be33cfa2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicCustomActionType.java @@ -0,0 +1,20 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; + +/** + * Created by andy on 11/3/18. + */ + +public enum MedtronicCustomActionType implements CustomActionType { + + WakeUpAndTune(), // + ClearBolusBlock(), // + ResetRileyLinkConfiguration(), // + ; + + @Override + public String getKey() { + return this.name(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicDeviceType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicDeviceType.java new file mode 100644 index 00000000000..f13974999b0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicDeviceType.java @@ -0,0 +1,140 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import java.util.HashMap; +import java.util.Map; + +/** + * Taken from GNU Gluco Control diabetes management software (ggc.sourceforge.net) + *

+ * Author: Andy {andy@atech-software.com} + */ + +public enum MedtronicDeviceType { + Unknown_Device, // + + // Pump + Medtronic_511("511"), // + + Medtronic_512("512"), // + Medtronic_712("712"), // + Medtronic_512_712(Medtronic_512, Medtronic_712), // + + Medtronic_515("515"), // + Medtronic_715("715"), // + Medtronic_515_715(Medtronic_515, Medtronic_715), // + + Medtronic_522("522"), // + Medtronic_722("722"), // + Medtronic_522_722(Medtronic_522, Medtronic_722), // + + Medtronic_523_Revel("523"), // + Medtronic_723_Revel("723"), // + + Medtronic_554_Veo("554"), // + Medtronic_754_Veo("754"), // + + Medtronic_512andHigher(Medtronic_512, Medtronic_712, Medtronic_515, Medtronic_715, Medtronic_522, Medtronic_722, // + Medtronic_523_Revel, Medtronic_723_Revel, Medtronic_554_Veo, Medtronic_754_Veo), // + + Medtronic_515andHigher(Medtronic_515, Medtronic_715, Medtronic_522, Medtronic_722, Medtronic_523_Revel, Medtronic_723_Revel, // + Medtronic_554_Veo, Medtronic_754_Veo), // + Medtronic_522andHigher(Medtronic_522, Medtronic_722, Medtronic_523_Revel, Medtronic_723_Revel, // + Medtronic_554_Veo, Medtronic_754_Veo), // + Medtronic_523andHigher(Medtronic_523_Revel, Medtronic_723_Revel, Medtronic_554_Veo, // + Medtronic_754_Veo), // + + Medtronic_554andHigher(Medtronic_554_Veo, Medtronic_754_Veo), // + + + // + All; + + static Map mapByDescription; + + static { + + mapByDescription = new HashMap<>(); + + for (MedtronicDeviceType minimedDeviceType : values()) { + + if (!minimedDeviceType.isFamily) { + mapByDescription.put(minimedDeviceType.pumpModel, minimedDeviceType); + } + } + + } + + private String pumpModel; + + private boolean isFamily; + private MedtronicDeviceType[] familyMembers = null; + + + MedtronicDeviceType(String pumpModel) { + this.isFamily = false; + this.pumpModel = pumpModel; + } + + + MedtronicDeviceType(MedtronicDeviceType... familyMembers) { + this.familyMembers = familyMembers; + this.isFamily = true; + } + + + public static boolean isSameDevice(MedtronicDeviceType deviceWeCheck, MedtronicDeviceType deviceSources) { + if (deviceSources.isFamily) { + for (MedtronicDeviceType mdt : deviceSources.familyMembers) { + if (mdt == deviceWeCheck) + return true; + } + } else { + return (deviceWeCheck == deviceSources); + } + + return false; + } + + + public static MedtronicDeviceType getByDescription(String desc) { + if (mapByDescription.containsKey(desc)) { + return mapByDescription.get(desc); + } else { + return MedtronicDeviceType.Unknown_Device; + } + } + + +// public static boolean isLargerFormat(MedtronicDeviceType model) { +// return isSameDevice(model, Medtronic_523andHigher); +// } + + + public boolean isFamily() { + return isFamily; + } + + + public MedtronicDeviceType[] getFamilyMembers() { + return familyMembers; + } + + +// public boolean isLargerFormat() { +// return isSameDevice(this, Medtronic_523andHigher); +// } + + public boolean isMedtronic_523orHigher() { + return isSameDevice(this, Medtronic_523andHigher); + } + + + public int getBolusStrokes() { + return (isMedtronic_523orHigher()) ? 40 : 10; + } + + + public String getPumpModel() { + return pumpModel; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicNotificationType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicNotificationType.java new file mode 100644 index 00000000000..d11d6ad64c4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicNotificationType.java @@ -0,0 +1,65 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; + +/** + * Created by andy on 10/15/18. + */ + +public enum MedtronicNotificationType { + + PumpUnreachable(Notification.RILEYLINK_CONNECTION, R.string.medtronic_pump_status_pump_unreachable, Notification.NORMAL), // + PumpTypeNotSame(R.string.medtronic_error_pump_type_set_differs_from_detected, Notification.NORMAL), // + PumpBasalProfilesNotEnabled(R.string.medtronic_error_pump_basal_profiles_not_enabled, Notification.URGENT), // + PumpIncorrectBasalProfileSelected(R.string.medtronic_error_pump_incorrect_basal_profile_selected, Notification.URGENT), // + PumpWrongTBRTypeSet(R.string.medtronic_error_pump_wrong_tbr_type_set, Notification.URGENT), // + PumpWrongMaxBolusSet(R.string.medtronic_error_pump_wrong_max_bolus_set, Notification.NORMAL), // + PumpWrongMaxBasalSet(R.string.medtronic_error_pump_wrong_max_basal_set, Notification.NORMAL), // + PumpWrongTimeUrgent(R.string.combo_notification_check_time_date, Notification.URGENT), + PumpWrongTimeNormal(R.string.combo_notification_check_time_date, Notification.NORMAL), + TimeChangeOver24h(Notification.OVER_24H_TIME_CHANGE_REQUESTED, R.string.medtronic_error_pump_24h_time_change_requested, Notification.URGENT), + // + ; + + private int notificationType; + private int resourceId; + private int notificationUrgency; + + + MedtronicNotificationType(int resourceId, int notificationUrgency) { + this(Notification.MEDTRONIC_PUMP_ALARM, resourceId, notificationUrgency); + } + + + MedtronicNotificationType(int notificationType, int resourceId, int notificationUrgency) { + this.notificationType = notificationType; + this.resourceId = resourceId; + this.notificationUrgency = notificationUrgency; + } + + + public int getNotificationType() { + return notificationType; + } + + + public void setNotificationType(int notificationType) { + this.notificationType = notificationType; + } + + + public int getResourceId() { + + return resourceId; + } + + + public int getNotificationUrgency() { + + return notificationUrgency; + } + + // Notification.MEDTRONIC_PUMP_ALARM R.string.medtronic_pump_status_pump_unreachable, Notification.NORMAL + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicStatusRefreshType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicStatusRefreshType.java new file mode 100644 index 00000000000..55dc9b1a49e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicStatusRefreshType.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; + +/** + * Created by andy on 6/28/18. + */ + +public enum MedtronicStatusRefreshType { + + PumpHistory(5, null), // + Configuration(0, null), // + RemainingInsulin(-1, MedtronicCommandType.GetRemainingInsulin), // + BatteryStatus(55, MedtronicCommandType.GetBatteryStatus), // + PumpTime(60, MedtronicCommandType.GetRealTimeClock) // + ; + + private int refreshTime; + private MedtronicCommandType commandType; + + + MedtronicStatusRefreshType(int refreshTime, MedtronicCommandType commandType) { + this.refreshTime = refreshTime; + this.commandType = commandType; + } + + + public int getRefreshTime() { + return refreshTime; + } + + + public MedtronicCommandType getCommandType() { + if (this == Configuration) { + return MedtronicCommandType.getSettings(MedtronicUtil.getMedtronicPumpModel()); + } else + return commandType; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicUIResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicUIResponseType.java new file mode 100644 index 00000000000..8ab1fc08719 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/MedtronicUIResponseType.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +/** + * Created by andy on 10/18/18. + */ + +public enum MedtronicUIResponseType { + + Data, + Error, + Invalid + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpBolusType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpBolusType.java new file mode 100644 index 00000000000..820bff8a5b6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpBolusType.java @@ -0,0 +1,129 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import java.util.HashMap; + +/** + * Application: GGC - GNU Gluco Control + * Plug-in: Pump Tool (support for Pump devices) + *

+ * See AUTHORS for copyright information. + *

+ * 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, or (at your option) any later + * version. + *

+ * 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 along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + *

+ * Filename: PumpBolusType Description: Pump Bolus Types + *

+ * Author: Andy {andy@atech-software.com} + */ + +public enum PumpBolusType // implements CodeEnumWithTranslation +{ + None(0, "NONE"), // + Normal(1, "BOLUS_STANDARD"), // + Audio(2, "BOLUS_AUDIO"), // + Extended(3, "BOLUS_SQUARE", "AMOUNT_SQUARE=%s;DURATION=%s"), // + Multiwave(4, "BOLUS_MULTIWAVE", "AMOUNT=%s;AMOUNT_SQUARE=%s;DURATION=%s"); + + static String[] descriptions; + // static HashMap translationMapping = new HashMap(); + static HashMap codeMapping = new HashMap(); + private static boolean translated; + + static { + for (PumpBolusType pbt : values()) { + codeMapping.put(pbt.code, pbt); + } + } + + // public static void translateKeywords(I18nControlAbstract ic) + // { + // if (translated) + // return; + // + // for (PumpBolusType pbt : values()) + // { + // pbt.setTranslation(ic.getMessage(pbt.i18nKey)); + // translationMapping.put(pbt.getTranslation(), pbt); + // } + // + // String[] bolusDescriptions = { ic.getMessage("SELECT_BOLUS_TYPE"), // + // ic.getMessage("BOLUS_STANDARD"), // + // ic.getMessage("BOLUS_AUDIO"), // + // ic.getMessage("BOLUS_SQUARE"), // + // ic.getMessage("BOLUS_MULTIWAVE"), }; + // + // descriptions = bolusDescriptions; + // + // translated = true; + // } + + int code; + String i18nKey; + String translation; + String valueTemplate; + + + PumpBolusType(int code, String i18nKey) { + this.code = code; + this.i18nKey = i18nKey; + } + + + PumpBolusType(int code, String i18nKey, String valueTemplate) { + this.code = code; + this.i18nKey = i18nKey; + this.valueTemplate = valueTemplate; + } + + + public static PumpBolusType getByCode(int code) { + if (codeMapping.containsKey(code)) { + return codeMapping.get(code); + } else { + return PumpBolusType.None; + } + } + + + /** + * Get Descriptions (array) + * + * @return array of strings with description + */ + public static String[] getDescriptions() { + return descriptions; + } + + + public String getTranslation() { + return translation; + } + + + public void setTranslation(String translation) { + this.translation = translation; + } + + + public int getCode() { + return code; + } + + + public String getI18nKey() { + return i18nKey; + } + + + public String getName() { + return this.name(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpConfigurationGroup.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpConfigurationGroup.java new file mode 100644 index 00000000000..1c69641c98b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpConfigurationGroup.java @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +/** + * Created by andy on 27.02.15. + */ +public enum PumpConfigurationGroup { + General(1, "GROUP_GENERAL"), // + Device(2, "GROUP_DEVICE"), // + + Insulin(3, "GROUP_INSULIN"), // + + Basal(4, "GROUP_BASAL"), // + Bolus(5, "GROUP_BOLUS"), // + Sound(6, "GROUP_SOUND"), // + + Other(20, "GROUP_OTHER"), // + + UnknownGroup(21, "GROUP_UNKNOWN"), // + + ; // + + static boolean translated; + int code; + String i18nKey; + String translation; + + + PumpConfigurationGroup(int code, String i18nKey) { + this.code = code; + this.i18nKey = i18nKey; + } + + + public String getTranslation() { + return translation; + } + + + public void setTranslation(String translation) { + this.translation = translation; + } + + + public int getCode() { + return code; + } + + + public String getI18nKey() { + return i18nKey; + } + + + public String getName() { + return this.name(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpDeviceState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpDeviceState.java new file mode 100644 index 00000000000..ba869638695 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/defs/PumpDeviceState.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 6/11/18. + */ + +public enum PumpDeviceState { + + NeverContacted(R.string.medtronic_pump_status_never_contacted), // + Sleeping(R.string.medtronic_pump_status_sleeping), // + WakingUp(R.string.medtronic_pump_status_waking_up), // + Active(R.string.medtronic_pump_status_active), // + ErrorWhenCommunicating(R.string.medtronic_pump_status_error_comm), // + TimeoutWhenCommunicating(R.string.medtronic_pump_status_timeout_comm), // + // ProblemContacting(R.string.medtronic_pump_status_problem_contacting), // + PumpUnreachable(R.string.medtronic_pump_status_pump_unreachable), // + InvalidConfiguration(R.string.medtronic_pump_status_invalid_config); + + Integer resourceId; + + PumpDeviceState(int resourceId) { + this.resourceId = resourceId; + } + + public Integer getResourceId() { + return resourceId; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/MedtronicHistoryActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/MedtronicHistoryActivity.java new file mode 100644 index 00000000000..830efb95dfe --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/MedtronicHistoryActivity.java @@ -0,0 +1,254 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.dialog; + +import android.os.Bundle; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.history.pump.PumpHistoryEntry; + +public class MedtronicHistoryActivity extends NoSplashAppCompatActivity { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + Spinner historyTypeSpinner; + TextView statusView; + RecyclerView recyclerView; + LinearLayoutManager llm; + + static TypeList showingType = null; + static PumpHistoryEntryGroup selectedGroup = PumpHistoryEntryGroup.All; + List filteredHistoryList = new ArrayList<>(); + + RecyclerViewAdapter recyclerViewAdapter; + boolean manualChange = false; + + List typeListFull; + + + public MedtronicHistoryActivity() { + super(); + } + + + private void filterHistory(PumpHistoryEntryGroup group) { + + this.filteredHistoryList.clear(); + + List list = new ArrayList<>(); + list.addAll(MedtronicPumpPlugin.getPlugin().getMedtronicHistoryData().getAllHistory()); + + //LOG.debug("Items on full list: {}", list.size()); + + if (group == PumpHistoryEntryGroup.All) { + this.filteredHistoryList.addAll(list); + } else { + for (PumpHistoryEntry pumpHistoryEntry : list) { + if (pumpHistoryEntry.getEntryType().getGroup() == group) { + this.filteredHistoryList.add(pumpHistoryEntry); + } + } + } + + if (this.recyclerViewAdapter != null) { + this.recyclerViewAdapter.setHistoryList(this.filteredHistoryList); + this.recyclerViewAdapter.notifyDataSetChanged(); + } + + //LOG.debug("Items on filtered list: {}", filteredHistoryList.size()); + } + + + @Override + protected void onResume() { + super.onResume(); + filterHistory(selectedGroup); + setHistoryTypeSpinner(); + } + + + private void setHistoryTypeSpinner() { + this.manualChange = true; + + for (int i = 0; i < typeListFull.size(); i++) { + if (typeListFull.get(i).entryGroup == selectedGroup) { + historyTypeSpinner.setSelection(i); + break; + } + } + + SystemClock.sleep(200); + this.manualChange = false; + } + + + @Override + protected void onPause() { + super.onPause(); + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.medtronic_history_activity); + + historyTypeSpinner = (Spinner) findViewById(R.id.medtronic_historytype); + statusView = (TextView) findViewById(R.id.medtronic_historystatus); + recyclerView = (RecyclerView) findViewById(R.id.medtronic_history_recyclerview); + + recyclerView.setHasFixedSize(true); + llm = new LinearLayoutManager(this); + recyclerView.setLayoutManager(llm); + + recyclerViewAdapter = new RecyclerViewAdapter(filteredHistoryList); + recyclerView.setAdapter(recyclerViewAdapter); + + statusView.setVisibility(View.GONE); + + typeListFull = getTypeList(PumpHistoryEntryGroup.getList()); + + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(this, R.layout.spinner_centered, typeListFull); + historyTypeSpinner.setAdapter(spinnerAdapter); + + historyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (manualChange) + return; + TypeList selected = (TypeList) historyTypeSpinner.getSelectedItem(); + showingType = selected; + selectedGroup = selected.entryGroup; + filterHistory(selectedGroup); + } + + + @Override + public void onNothingSelected(AdapterView parent) { + if (manualChange) + return; + filterHistory(PumpHistoryEntryGroup.All); + } + }); + + } + + + private List getTypeList(List list) { + + ArrayList typeList = new ArrayList<>(); + + for (PumpHistoryEntryGroup pumpHistoryEntryGroup : list) { + typeList.add(new TypeList(pumpHistoryEntryGroup)); + } + + return typeList; + } + + public static class TypeList { + + PumpHistoryEntryGroup entryGroup; + String name; + + + TypeList(PumpHistoryEntryGroup entryGroup) { + this.entryGroup = entryGroup; + this.name = entryGroup.getTranslated(); + } + + + @Override + public String toString() { + return name; + } + } + + public static class RecyclerViewAdapter extends RecyclerView.Adapter { + + List historyList; + + + RecyclerViewAdapter(List historyList) { + this.historyList = historyList; + } + + + public void setHistoryList(List historyList) { + // this.historyList.clear(); + // this.historyList.addAll(historyList); + + this.historyList = historyList; + + // this.notifyDataSetChanged(); + } + + + @Override + public HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.medtronic_history_item, // + viewGroup, false); + return new HistoryViewHolder(v); + } + + + @Override + public void onBindViewHolder(HistoryViewHolder holder, int position) { + PumpHistoryEntry record = historyList.get(position); + + if (record != null) { + holder.timeView.setText(record.getDateTimeString()); + holder.typeView.setText(record.getEntryType().getDescription()); + holder.valueView.setText(record.getDisplayableValue()); + } + } + + + @Override + public int getItemCount() { + return historyList.size(); + } + + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + } + + static class HistoryViewHolder extends RecyclerView.ViewHolder { + + TextView timeView; + TextView typeView; + TextView valueView; + + + HistoryViewHolder(View itemView) { + super(itemView); + // cv = (CardView)itemView.findViewById(R.id.rileylink_history_item); + timeView = (TextView) itemView.findViewById(R.id.medtronic_history_time); + typeView = (TextView) itemView.findViewById(R.id.medtronic_history_source); + valueView = (TextView) itemView.findViewById(R.id.medtronic_history_description); + } + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/RileyLinkStatusDeviceMedtronic.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/RileyLinkStatusDeviceMedtronic.java new file mode 100644 index 00000000000..2d7252cb7ca --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/dialog/RileyLinkStatusDeviceMedtronic.java @@ -0,0 +1,159 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.dialog; + +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.common.dialog.RefreshableInterface; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.common.utils.StringUtil; + +/** + * Created by andy on 5/19/18. + *

+ * This is for 3rd tab, called Medtronic (in RileyLink stats), that should work similarly as the one in Loop. + *

+ * Showing currently selected RL, speed of RL, ability to issue simple commands (getModel, tuneUp, gerProfile) + */ + +// TODO needs to be implemented +public class RileyLinkStatusDeviceMedtronic extends Fragment implements RefreshableInterface { + + // @BindView(R.id.rileylink_history_list) + ListView listView; + + RileyLinkCommandListAdapter adapter; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.rileylink_status_device, container, false); + + adapter = new RileyLinkCommandListAdapter(); + + return rootView; + } + + + @Override + public void onStart() { + super.onStart(); + + this.listView = (ListView) getActivity().findViewById(R.id.rileylink_history_list); + + listView.setAdapter(adapter); + + refreshData(); + } + + + @Override + public void refreshData() { + // adapter.addItemsAndClean(RileyLinkUtil.getRileyLinkHistory()); + } + + static class ViewHolder { + + TextView itemTime; + TextView itemSource; + TextView itemDescription; + } + + private class RileyLinkCommandListAdapter extends BaseAdapter { + + private List historyItemList; + private LayoutInflater mInflator; + + + public RileyLinkCommandListAdapter() { + super(); + historyItemList = new ArrayList<>(); + mInflator = RileyLinkStatusDeviceMedtronic.this.getLayoutInflater(); + } + + + public void addItem(RLHistoryItem item) { + if (!historyItemList.contains(item)) { + historyItemList.add(item); + notifyDataSetChanged(); + } + } + + + public RLHistoryItem getHistoryItem(int position) { + return historyItemList.get(position); + } + + + public void addItemsAndClean(List items) { + this.historyItemList.clear(); + + for (RLHistoryItem item : items) { + + if (!historyItemList.contains(item)) { + historyItemList.add(item); + } + } + + notifyDataSetChanged(); + } + + + public void clear() { + historyItemList.clear(); + notifyDataSetChanged(); + } + + + @Override + public int getCount() { + return historyItemList.size(); + } + + + @Override + public Object getItem(int i) { + return historyItemList.get(i); + } + + + @Override + public long getItemId(int i) { + return i; + } + + + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + RileyLinkStatusDeviceMedtronic.ViewHolder viewHolder; + // General ListView optimization code. + if (view == null) { + view = mInflator.inflate(R.layout.rileylink_status_device_item, null); + viewHolder = new RileyLinkStatusDeviceMedtronic.ViewHolder(); + viewHolder.itemTime = (TextView) view.findViewById(R.id.rileylink_history_time); + viewHolder.itemSource = (TextView) view.findViewById(R.id.rileylink_history_source); + viewHolder.itemDescription = (TextView) view.findViewById(R.id.rileylink_history_description); + view.setTag(viewHolder); + } else { + viewHolder = (RileyLinkStatusDeviceMedtronic.ViewHolder) view.getTag(); + } + + RLHistoryItem item = historyItemList.get(i); + viewHolder.itemTime.setText(StringUtil.toDateTimeString(item.getDateTime())); + viewHolder.itemSource.setText("Riley Link"); // for now + viewHolder.itemDescription.setText(item.getDescription()); + + return view; + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.java new file mode 100644 index 00000000000..fc1208a340a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/driver/MedtronicPumpStatus.java @@ -0,0 +1,390 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.driver; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkTargetFrequency; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BasalProfileStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4/28/18. + */ + +public class MedtronicPumpStatus extends PumpStatus { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public String errorDescription = null; + public String serialNumber; + public String pumpFrequency = null; + public String rileyLinkAddress = null; + public Double maxBolus; + public Double maxBasal; + public boolean inPreInit = true; + + // statuses + public RileyLinkServiceState rileyLinkServiceState = RileyLinkServiceState.NotStarted; + public RileyLinkError rileyLinkError; + public PumpDeviceState pumpDeviceState = PumpDeviceState.NeverContacted; + public MedtronicDeviceType medtronicDeviceType = null; + public double currentBasal = 0; + public int tempBasalInProgress = 0; + public int tempBasalRatio = 0; + public int tempBasalRemainMin = 0; + public Date tempBasalStart; + public Double tempBasalAmount = 0.0d; + + // fixme + public Integer tempBasalLength = 0; + + private String regexMac = "([\\da-fA-F]{1,2}(?:\\:|$)){6}"; + private String regexSN = "[0-9]{6}"; + + private boolean serialChanged = false; + private boolean rileyLinkAddressChanged = false; + private boolean encodingChanged = false; + private boolean targetFrequencyChanged = false; + + private RileyLinkEncodingType encodingType; + private String[] frequencies; + private boolean isFrequencyUS = false; + private Map medtronicPumpMap = null; + private Map medtronicDeviceTypeMap = null; + private RileyLinkTargetFrequency targetFrequency; + public BasalProfileStatus basalProfileStatus = BasalProfileStatus.NotInitialized; + public BatteryType batteryType = BatteryType.None; + + + public MedtronicPumpStatus(PumpDescription pumpDescription) { + super(pumpDescription); + } + + + @Override + public void initSettings() { + + this.activeProfileName = "STD"; + this.reservoirRemainingUnits = 75d; + this.batteryRemaining = 75; + + if (this.medtronicPumpMap == null) + createMedtronicPumpMap(); + + if (this.medtronicDeviceTypeMap == null) + createMedtronicDeviceTypeMap(); + + this.lastConnection = SP.getLong(MedtronicConst.Statistics.LastGoodPumpCommunicationTime, 0L); + this.lastDataTime = new LocalDateTime(this.lastConnection); + } + + + private void createMedtronicDeviceTypeMap() { + medtronicDeviceTypeMap = new HashMap<>(); + medtronicDeviceTypeMap.put("512", MedtronicDeviceType.Medtronic_512); + medtronicDeviceTypeMap.put("712", MedtronicDeviceType.Medtronic_712); + medtronicDeviceTypeMap.put("515", MedtronicDeviceType.Medtronic_515); + medtronicDeviceTypeMap.put("715", MedtronicDeviceType.Medtronic_715); + + medtronicDeviceTypeMap.put("522", MedtronicDeviceType.Medtronic_522); + medtronicDeviceTypeMap.put("722", MedtronicDeviceType.Medtronic_722); + medtronicDeviceTypeMap.put("523", MedtronicDeviceType.Medtronic_523_Revel); + medtronicDeviceTypeMap.put("723", MedtronicDeviceType.Medtronic_723_Revel); + medtronicDeviceTypeMap.put("554", MedtronicDeviceType.Medtronic_554_Veo); + medtronicDeviceTypeMap.put("754", MedtronicDeviceType.Medtronic_754_Veo); + } + + + private void createMedtronicPumpMap() { + + medtronicPumpMap = new HashMap<>(); + medtronicPumpMap.put("512", PumpType.Medtronic_512_712); + medtronicPumpMap.put("712", PumpType.Medtronic_512_712); + medtronicPumpMap.put("515", PumpType.Medtronic_515_715); + medtronicPumpMap.put("715", PumpType.Medtronic_515_715); + + medtronicPumpMap.put("522", PumpType.Medtronic_522_722); + medtronicPumpMap.put("722", PumpType.Medtronic_522_722); + medtronicPumpMap.put("523", PumpType.Medtronic_523_723_Revel); + medtronicPumpMap.put("723", PumpType.Medtronic_523_723_Revel); + medtronicPumpMap.put("554", PumpType.Medtronic_554_754_Veo); + medtronicPumpMap.put("754", PumpType.Medtronic_554_754_Veo); + + frequencies = new String[2]; + frequencies[0] = MainApp.gs(R.string.key_medtronic_pump_frequency_us_ca); + frequencies[1] = MainApp.gs(R.string.key_medtronic_pump_frequency_worldwide); + } + + + public boolean verifyConfiguration() { + try { + + // FIXME don't reload information several times + if (this.medtronicPumpMap == null) + createMedtronicPumpMap(); + + if (this.medtronicDeviceTypeMap == null) + createMedtronicDeviceTypeMap(); + + this.errorDescription = "-"; + + String serialNr = SP.getString(MedtronicConst.Prefs.PumpSerial, null); + + if (serialNr == null) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_serial_not_set); + return false; + } else { + if (!serialNr.matches(regexSN)) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_serial_invalid); + return false; + } else { + if (!serialNr.equals(this.serialNumber)) { + this.serialNumber = serialNr; + serialChanged = true; + } + } + } + + String pumpType = SP.getString(MedtronicConst.Prefs.PumpType, null); + + if (pumpType == null) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_pump_type_not_set); + return false; + } else { + String pumpTypePart = pumpType.substring(0, 3); + + if (!pumpTypePart.matches("[0-9]{3}")) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_pump_type_invalid); + return false; + } else { + this.pumpType = medtronicPumpMap.get(pumpTypePart); + this.medtronicDeviceType = medtronicDeviceTypeMap.get(pumpTypePart); + this.pumpDescription.setPumpDescription(this.pumpType); + + if (pumpTypePart.startsWith("7")) + this.reservoirFullUnits = 300; + else + this.reservoirFullUnits = 176; + } + } + + String pumpFrequency = SP.getString(MedtronicConst.Prefs.PumpFrequency, null); + + if (pumpFrequency == null) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_pump_frequency_not_set); + return false; + } else { + if (!pumpFrequency.equals(frequencies[0]) && !pumpFrequency.equals(frequencies[1])) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_pump_frequency_invalid); + return false; + } else { + this.pumpFrequency = pumpFrequency; + this.isFrequencyUS = pumpFrequency.equals(frequencies[0]); + + RileyLinkTargetFrequency newTargetFrequency = this.isFrequencyUS ? // + RileyLinkTargetFrequency.Medtronic_US + : RileyLinkTargetFrequency.Medtronic_WorldWide; + + if (targetFrequency != newTargetFrequency) { + RileyLinkUtil.setRileyLinkTargetFrequency(newTargetFrequency); + targetFrequency = newTargetFrequency; + targetFrequencyChanged = true; + } + + } + } + + String rileyLinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, null); + + if (rileyLinkAddress == null) { + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: null"); + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + return false; + } else { + if (!rileyLinkAddress.matches(regexMac)) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: {}", rileyLinkAddress); + } else { + if (!rileyLinkAddress.equals(this.rileyLinkAddress)) { + this.rileyLinkAddress = rileyLinkAddress; + rileyLinkAddressChanged = true; + } + } + } + + double maxBolusLcl = checkParameterValue(MedtronicConst.Prefs.MaxBolus, "25.0", 25.0d); + + if (maxBolus == null || !maxBolus.equals(maxBolusLcl)) { + maxBolus = maxBolusLcl; + + //LOG.debug("Max Bolus from AAPS settings is " + maxBolus); + } + + double maxBasalLcl = checkParameterValue(MedtronicConst.Prefs.MaxBasal, "35.0", 35.0d); + + if (maxBasal == null || !maxBasal.equals(maxBasalLcl)) { + maxBasal = maxBasalLcl; + + //LOG.debug("Max Basal from AAPS settings is " + maxBasal); + } + + + String encodingTypeStr = SP.getString(MedtronicConst.Prefs.Encoding, null); + + if (encodingTypeStr == null) { + return false; + } + + RileyLinkEncodingType newEncodingType = RileyLinkEncodingType.getByDescription(encodingTypeStr); + + if (this.encodingType == null) { + this.encodingType = newEncodingType; + } else if (this.encodingType != newEncodingType) { + this.encodingType = newEncodingType; + this.encodingChanged = true; + } + + String batteryTypeStr = SP.getString(MedtronicConst.Prefs.BatteryType, null); + + if (batteryTypeStr == null) + return false; + + BatteryType batteryType = BatteryType.getByDescription(batteryTypeStr); + + if (this.batteryType != batteryType) { + this.batteryType = batteryType; + MedtronicUtil.setBatteryType(this.batteryType); + } + + String bolusDebugEnabled = SP.getString(MedtronicConst.Prefs.BolusDebugEnabled, null); + + boolean bolusDebug = bolusDebugEnabled != null && bolusDebugEnabled.equals(MainApp.gs(R.string.common_on)); + + MedtronicHistoryData.doubleBolusDebug = bolusDebug; + + reconfigureService(); + + return true; + + } catch (Exception ex) { + this.errorDescription = ex.getMessage(); + LOG.error("Error on Verification: " + ex.getMessage(), ex); + return false; + } + } + + + private boolean reconfigureService() { + + if (!inPreInit && MedtronicUtil.getMedtronicService() != null) { + + if (serialChanged) { + MedtronicUtil.getMedtronicService().setPumpIDString(this.serialNumber); // short operation + serialChanged = false; + } + + if (rileyLinkAddressChanged) { + MedtronicUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkNewAddressSet); + rileyLinkAddressChanged = false; + } + + if (encodingChanged) { + RileyLinkUtil.getRileyLinkService().changeRileyLinkEncoding(encodingType); + encodingChanged = false; + } + } + + + // if (targetFrequencyChanged && !inPreInit && MedtronicUtil.getMedtronicService() != null) { + // RileyLinkUtil.setRileyLinkTargetFrequency(targetFrequency); + // // RileyLinkUtil.getRileyLinkCommunicationManager().refreshRileyLinkTargetFrequency(); + // targetFrequencyChanged = false; + // } + + return (!rileyLinkAddressChanged && !serialChanged && !encodingChanged); // && !targetFrequencyChanged); + } + + + private double checkParameterValue(int key, String defaultValue, double defaultValueDouble) { + double val = 0.0d; + + String value = SP.getString(key, defaultValue); + + try { + val = Double.parseDouble(value); + } catch (Exception ex) { + LOG.error("Error parsing setting: {}, value found {}", key, value); + val = defaultValueDouble; + } + + if (val > defaultValueDouble) { + SP.putString(key, defaultValue); + val = defaultValueDouble; + } + + return val; + } + + + public String getErrorInfo() { + verifyConfiguration(); + + return (this.errorDescription == null) ? "-" : this.errorDescription; + } + + + @Override + public void refreshConfiguration() { + verifyConfiguration(); + } + + + public boolean setNotInPreInit() { + this.inPreInit = false; + + return reconfigureService(); + } + + + public double getBasalProfileForHour() { + if (basalsByHour != null) { + GregorianCalendar c = new GregorianCalendar(); + int hour = c.get(Calendar.HOUR_OF_DAY); + + return basalsByHour[hour]; + } + + return 0; + } + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicDeviceStatusChange.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicDeviceStatusChange.kt new file mode 100644 index 00000000000..37e6649252f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicDeviceStatusChange.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.events + +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState + +class EventMedtronicDeviceStatusChange : Event { + + private var rileyLinkServiceState: RileyLinkServiceState? = null + private var rileyLinkError: RileyLinkError? = null + + private var pumpDeviceState: PumpDeviceState? = null + private var errorDescription: String? = null + + + constructor(rileyLinkServiceState: RileyLinkServiceState?, rileyLinkError: RileyLinkError?) { + this.rileyLinkServiceState = rileyLinkServiceState + this.rileyLinkError = rileyLinkError + } + + constructor(pumpDeviceState: PumpDeviceState?) { + this.pumpDeviceState = pumpDeviceState + } + + constructor(pumpDeviceState: PumpDeviceState?, errorDescription: String?) { + this.pumpDeviceState = pumpDeviceState + this.errorDescription = errorDescription + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpConfigurationChanged.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpConfigurationChanged.kt new file mode 100644 index 00000000000..458c0554543 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpConfigurationChanged.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.events + +import info.nightscout.androidaps.events.Event + +class EventMedtronicPumpConfigurationChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpValuesChanged.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpValuesChanged.kt new file mode 100644 index 00000000000..b314b9db19f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventMedtronicPumpValuesChanged.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.events + +import info.nightscout.androidaps.events.Event + +class EventMedtronicPumpValuesChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventRefreshButtonState.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventRefreshButtonState.kt new file mode 100644 index 00000000000..81b9af4ff34 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/events/EventRefreshButtonState.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.events + +import info.nightscout.androidaps.events.Event + +class EventRefreshButtonState (val newState : Boolean): Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java new file mode 100644 index 00000000000..49d49ee06ca --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java @@ -0,0 +1,205 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.service; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.os.Binder; +import android.os.IBinder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTask; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicConst; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * RileyLinkMedtronicService is intended to stay running when the gui-app is closed. + */ +public class RileyLinkMedtronicService extends RileyLinkService { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static RileyLinkMedtronicService instance; + private static ServiceTask currentTask = null; + + // cache of most recently received set of pump history pages. Probably shouldn't be here. + public MedtronicCommunicationManager medtronicCommunicationManager; + MedtronicPumpStatus pumpStatus = null; + private IBinder mBinder = new LocalBinder(); + + + public RileyLinkMedtronicService() { + super(MainApp.instance().getApplicationContext()); + instance = this; + if (isLogEnabled()) + LOG.debug("RileyLinkMedtronicService newly constructed"); + MedtronicUtil.setMedtronicService(this); + pumpStatus = (MedtronicPumpStatus) MedtronicPumpPlugin.getPlugin().getPumpStatusData(); + } + + + public static RileyLinkMedtronicService getInstance() { + return instance; + } + + + public static MedtronicCommunicationManager getCommunicationManager() { + return instance.medtronicCommunicationManager; + } + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (isLogEnabled()) + LOG.warn("onConfigurationChanged"); + super.onConfigurationChanged(newConfig); + } + + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + + @Override + public RileyLinkEncodingType getEncoding() { + return RileyLinkEncodingType.FourByteSixByteLocal; + } + + + /** + * If you have customized RileyLinkServiceData you need to override this + */ + public void initRileyLinkServiceData() { + + rileyLinkServiceData = new RileyLinkServiceData(RileyLinkTargetDevice.MedtronicPump); + + RileyLinkUtil.setRileyLinkServiceData(rileyLinkServiceData); + RileyLinkUtil.setTargetDevice(RileyLinkTargetDevice.MedtronicPump); + + setPumpIDString(SP.getString(MedtronicConst.Prefs.PumpSerial, "000000")); + + // get most recently used RileyLink address + rileyLinkServiceData.rileylinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, ""); + + rileyLinkBLE = new RileyLinkBLE(this.context); // or this + rfspy = new RFSpy(rileyLinkBLE); + rfspy.startReader(); + + RileyLinkUtil.setRileyLinkBLE(rileyLinkBLE); + + // init rileyLinkCommunicationManager + medtronicCommunicationManager = new MedtronicCommunicationManager(rfspy); + } + + + public void resetRileyLinkConfiguration() { + rfspy.resetRileyLinkConfiguration(); + } + + + @Override + public RileyLinkCommunicationManager getDeviceCommunicationManager() { + return this.medtronicCommunicationManager; + } + + + public void setPumpIDString(String pumpID) { + if (pumpID.length() != 6) { + LOG.error("setPumpIDString: invalid pump id string: " + pumpID); + return; + } + + byte[] pumpIDBytes = ByteUtil.fromHexString(pumpID); + + if (pumpIDBytes == null) { + LOG.error("Invalid pump ID? - PumpID is null."); + + rileyLinkServiceData.setPumpID("000000", new byte[]{0, 0, 0}); + + } else if (pumpIDBytes.length != 3) { + LOG.error("Invalid pump ID? " + ByteUtil.shortHexString(pumpIDBytes)); + + rileyLinkServiceData.setPumpID("000000", new byte[]{0, 0, 0}); + + } else if (pumpID.equals("000000")) { + LOG.error("Using pump ID " + pumpID); + + rileyLinkServiceData.setPumpID(pumpID, new byte[]{0, 0, 0}); + + } else { + LOG.info("Using pump ID " + pumpID); + + String oldId = rileyLinkServiceData.pumpID; + + rileyLinkServiceData.setPumpID(pumpID, pumpIDBytes); + + if (oldId != null && !oldId.equals(pumpID)) { + MedtronicUtil.setMedtronicPumpModel(null); // if we change pumpId, model probably changed too + } + + return; + } + + MedtronicUtil.setPumpDeviceState(PumpDeviceState.InvalidConfiguration); + + // LOG.info("setPumpIDString: saved pumpID " + idString); + } + + public class LocalBinder extends Binder { + + public RileyLinkMedtronicService getServiceInstance() { + return RileyLinkMedtronicService.this; + } + } + + + /* private functions */ + + // PumpInterface - REMOVE + + public boolean isInitialized() { + return RileyLinkServiceState.isReady(RileyLinkUtil.getRileyLinkServiceData().serviceState); + } + + + @Override + public String getDeviceSpecificBroadcastsIdentifierPrefix() { + return null; + } + + + public boolean handleDeviceSpecificBroadcasts(Intent intent) { + return false; + } + + + @Override + public void registerDeviceSpecificBroadcasts(IntentFilter intentFilter) { + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicConst.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicConst.java new file mode 100644 index 00000000000..fef95087de8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicConst.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.util; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 5/12/18. + */ + +public class MedtronicConst { + + static final String Prefix = "AAPS.Medtronic."; + + public class Prefs { + public static final int PumpSerial = R.string.key_medtronic_serial; + public static final int PumpType = R.string.key_medtronic_pump_type; + public static final int PumpFrequency = R.string.key_medtronic_frequency; + public static final int MaxBolus = R.string.key_medtronic_max_bolus; + public static final int MaxBasal = R.string.key_medtronic_max_basal; + public static final int BolusDelay = R.string.key_medtronic_bolus_delay; + public static final int Encoding = R.string.key_medtronic_encoding; + public static final int BatteryType = R.string.key_medtronic_battery_type; + public static final int BolusDebugEnabled = R.string.key_medtronic_bolus_debug; + } + + public class Statistics { + + public static final String StatsPrefix = "medtronic_"; + public static final String FirstPumpStart = Prefix + "first_pump_use"; + public static final String LastGoodPumpCommunicationTime = Prefix + "lastGoodPumpCommunicationTime"; + public static final String LastGoodPumpFrequency = Prefix + "LastGoodPumpFrequency"; + public static final String TBRsSet = StatsPrefix + "tbrs_set"; + public static final String StandardBoluses = StatsPrefix + "std_boluses_delivered"; + public static final String SMBBoluses = StatsPrefix + "smb_boluses_delivered"; + public static final String LastPumpHistoryEntry = StatsPrefix + "pump_history_entry"; + public static final String LastPrime = StatsPrefix + "last_sent_prime"; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java new file mode 100644 index 00000000000..2c7ee69c2f0 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java @@ -0,0 +1,537 @@ +package info.nightscout.androidaps.plugins.pump.medtronic.util; + +import android.content.Context; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import org.joda.time.LocalTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.comm.MedtronicCommunicationManager; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.ClockDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.data.dto.PumpSettingDTO; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.BatteryType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicCommandType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicDeviceType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.MedtronicNotificationType; +import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; +import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; +import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange; +import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; +import info.nightscout.androidaps.utils.OKDialog; + +/** + * Created by andy on 5/9/18. + */ + +public class MedtronicUtil extends RileyLinkUtil { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + static int ENVELOPE_SIZE = 4; // 0xA7 S1 S2 S3 CMD PARAM_COUNT [PARAMS] + static int CRC_SIZE = 1; + private static boolean lowLevelDebug = true; + private static PumpDeviceState pumpDeviceState; + private static MedtronicDeviceType medtronicPumpModel; + private static RileyLinkMedtronicService medtronicService; + private static MedtronicPumpStatus medtronicPumpStatus; + private static MedtronicCommandType currentCommand; + private static Map settings; + private static int BIG_FRAME_LENGTH = 65; + private static int doneBit = 1 << 7; + private static ClockDTO pumpTime; + public static Gson gsonInstance = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + public static Gson gsonInstanceCore = new GsonBuilder().create(); + private static BatteryType batteryType = BatteryType.None; + + + public static Gson getGsonInstance() { + return gsonInstance; + } + + + public static Gson getGsonInstanceCore() { + return gsonInstanceCore; + } + + + public static LocalTime getTimeFrom30MinInterval(int interval) { + if (interval % 2 == 0) { + return new LocalTime(interval / 2, 0); + } else { + return new LocalTime((interval - 1) / 2, 30); + } + } + + + public static int getIntervalFromMinutes(int minutes) { + return minutes / 30; + } + + + public static int makeUnsignedShort(int b2, int b1) { + int k = (b2 & 0xff) << 8 | b1 & 0xff; + return k; + } + + public static boolean isMedtronicPump() { + return MedtronicPumpPlugin.getPlugin().isEnabled(PluginType.PUMP); + //return ConfigBuilderPlugin.getPlugin().getActivePump().deviceID().equals("Medtronic"); + } + + + public static byte[] getByteArrayFromUnsignedShort(int shortValue, boolean returnFixedSize) { + byte highByte = (byte) (shortValue >> 8 & 0xFF); + byte lowByte = (byte) (shortValue & 0xFF); + + if (highByte > 0) { + return createByteArray(highByte, lowByte); + } else { + return returnFixedSize ? createByteArray(highByte, lowByte) : createByteArray(lowByte); + } + + } + + + public static byte[] createByteArray(byte... data) { + return data; + } + + + public static byte[] createByteArray(List data) { + + byte[] array = new byte[data.size()]; + + for (int i = 0; i < data.size(); i++) { + array[i] = data.get(i); + } + + return array; + } + + + public static double decodeBasalInsulin(int i, int j) { + return decodeBasalInsulin(makeUnsignedShort(i, j)); + } + + + public static double decodeBasalInsulin(int i) { + return (double) i / 40.0d; + } + + + public static byte[] getBasalStrokes(double amount) { + return getBasalStrokes(amount, false); + } + + + public static byte[] getBasalStrokes(double amount, boolean returnFixedSize) { + return getStrokes(amount, 40, returnFixedSize); + } + + + public static int getBasalStrokesInt(double amount) { + return getStrokesInt(amount, 40); + } + + + public static byte[] getBolusStrokes(double amount) { + + int strokesPerUnit = medtronicPumpModel.getBolusStrokes(); + + int length; + int scrollRate; + + if (strokesPerUnit >= 40) { + length = 2; + + // 40-stroke pumps scroll faster for higher unit values + + if (amount > 10) + scrollRate = 4; + else if (amount > 1) + scrollRate = 2; + else + scrollRate = 1; + + } else { + length = 1; + scrollRate = 1; + } + + int strokes = (int) (amount * ((strokesPerUnit * 1.0d) / (scrollRate * 1.0d))) * scrollRate; + + byte[] body = ByteUtil.fromHexString(String.format("%02x%0" + (2 * length) + "x", length, strokes)); + + return body; + } + + + public static byte[] createCommandBody(byte[] input) { + + return ByteUtil.concat((byte) input.length, input); + } + + + public static byte[] getStrokes(double amount, int strokesPerUnit, boolean returnFixedSize) { + + int strokes = getStrokesInt(amount, strokesPerUnit); + + return getByteArrayFromUnsignedShort(strokes, returnFixedSize); + + } + + + public static int getStrokesInt(double amount, int strokesPerUnit) { + + int length = 1; + int scrollRate = 1; + + if (strokesPerUnit >= 40) { + length = 2; + + // 40-stroke pumps scroll faster for higher unit values + if (amount > 10) + scrollRate = 4; + else if (amount > 1) + scrollRate = 2; + } + + int strokes = (int) (amount * (strokesPerUnit / (scrollRate * 1.0d))); + + strokes *= scrollRate; + + return strokes; + + } + + + public static void sendNotification(MedtronicNotificationType notificationType) { + Notification notification = new Notification( // + notificationType.getNotificationType(), // + MainApp.gs(notificationType.getResourceId()), // + notificationType.getNotificationUrgency()); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } + + + public static void sendNotification(MedtronicNotificationType notificationType, Object... parameters) { + Notification notification = new Notification( // + notificationType.getNotificationType(), // + MainApp.gs(notificationType.getResourceId(), parameters), // + notificationType.getNotificationUrgency()); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } + + + public static void dismissNotification(MedtronicNotificationType notificationType) { + RxBus.INSTANCE.send(new EventDismissNotification(notificationType.getNotificationType())); + } + + +// public static byte[] buildCommandPayload(MessageType commandType, byte[] parameters) { +// return buildCommandPayload(commandType.getValue(), parameters); +// } + + + public static byte[] buildCommandPayload(MedtronicCommandType commandType, byte[] parameters) { + return buildCommandPayload((byte) commandType.commandCode, parameters); + } + + + public static byte[] buildCommandPayload(byte commandType, byte[] parameters) { + // A7 31 65 51 C0 00 52 + + byte commandLength = (byte) (parameters == null ? 2 : 2 + parameters.length); + + ByteBuffer sendPayloadBuffer = ByteBuffer.allocate(ENVELOPE_SIZE + commandLength); // + CRC_SIZE + sendPayloadBuffer.order(ByteOrder.BIG_ENDIAN); + + byte[] serialNumberBCD = RileyLinkUtil.getRileyLinkServiceData().pumpIDBytes; + + sendPayloadBuffer.put((byte) 0xA7); + sendPayloadBuffer.put(serialNumberBCD[0]); + sendPayloadBuffer.put(serialNumberBCD[1]); + sendPayloadBuffer.put(serialNumberBCD[2]); + + sendPayloadBuffer.put(commandType); + + if (parameters == null) { + sendPayloadBuffer.put((byte) 0x00); + } else { + sendPayloadBuffer.put((byte) parameters.length); // size + + for (byte val : parameters) { + sendPayloadBuffer.put(val); + } + } + + byte[] payload = sendPayloadBuffer.array(); + + if (L.isEnabled(L.PUMPCOMM)) + LOG.debug("buildCommandPayload [{}]", ByteUtil.shortHexString(payload)); + + // int crc = computeCRC8WithPolynomial(payload, 0, payload.length - 1); + + // LOG.info("crc: " + crc); + + // sendPayloadBuffer.put((byte) crc); + + return sendPayloadBuffer.array(); + } + + + // Note: at the moment supported only for 24 items, if you will use it for more than + // that you will need to add + public static List> getBasalProfileFrames(byte[] data) { + + boolean done = false; + int start = 0; + int frame = 1; + + List> frames = new ArrayList<>(); + boolean lastFrame = false; + + do { + int frameLength = BIG_FRAME_LENGTH - 1; + + if (start + frameLength > data.length) { + frameLength = data.length - start; + } + + // System.out.println("Framelength: " + frameLength); + + byte[] substring = ByteUtil.substring(data, start, frameLength); + + // System.out.println("Subarray: " + ByteUtil.getCompactString(substring)); + // System.out.println("Subarray Lenths: " + substring.length); + + List frameData = ByteUtil.getListFromByteArray(substring); + + if (isEmptyFrame(frameData)) { + byte b = (byte) frame; + // b |= 0x80; + b |= 0b1000_0000; + // b |= doneBit; + + frameData.add(0, b); + + checkAndAppenLastFrame(frameData); + + lastFrame = true; + + done = true; + } else { + frameData.add(0, (byte) frame); + } + + // System.out.println("Subarray: " + ByteUtil.getCompactString(substring)); + + frames.add(frameData); + + frame++; + start += (BIG_FRAME_LENGTH - 1); + + if (start == data.length) { + done = true; + } + + } while (!done); + + if (!lastFrame) { + List frameData = new ArrayList<>(); + + byte b = (byte) frame; + b |= 0b1000_0000; + // b |= doneBit; + + frameData.add(b); + + checkAndAppenLastFrame(frameData); + } + + return frames; + + } + + + private static void checkAndAppenLastFrame(List frameData) { + + if (frameData.size() == BIG_FRAME_LENGTH) + return; + + int missing = BIG_FRAME_LENGTH - frameData.size(); + + for (int i = 0; i < missing; i++) { + frameData.add((byte) 0x00); + } + } + + + private static boolean isEmptyFrame(List frameData) { + + for (Byte frameDateEntry : frameData) { + if (frameDateEntry != 0x00) { + return false; + } + } + + return true; + } + + + public static boolean isLowLevelDebug() { + return lowLevelDebug; + } + + + public static void setLowLevelDebug(boolean lowLevelDebug) { + MedtronicUtil.lowLevelDebug = lowLevelDebug; + } + + + public static PumpDeviceState getPumpDeviceState() { + return pumpDeviceState; + } + + + public static void setPumpDeviceState(PumpDeviceState pumpDeviceState) { + MedtronicUtil.pumpDeviceState = pumpDeviceState; + + historyRileyLink.add(new RLHistoryItem(pumpDeviceState, RileyLinkTargetDevice.MedtronicPump)); + + RxBus.INSTANCE.send(new EventMedtronicDeviceStatusChange(pumpDeviceState)); + } + + + public static boolean isModelSet() { + return MedtronicUtil.medtronicPumpModel != null; + } + + + public static MedtronicDeviceType getMedtronicPumpModel() { + return MedtronicUtil.medtronicPumpModel; + } + + + public static void setMedtronicPumpModel(MedtronicDeviceType medtronicPumpModel) { + MedtronicUtil.medtronicPumpModel = medtronicPumpModel; + } + + + public static MedtronicCommunicationManager getMedtronicCommunicationManager() { + return (MedtronicCommunicationManager) RileyLinkUtil.rileyLinkCommunicationManager; + } + + + public static RileyLinkMedtronicService getMedtronicService() { + return MedtronicUtil.medtronicService; + } + + + public static void setMedtronicService(RileyLinkMedtronicService medtronicService) { + MedtronicUtil.medtronicService = medtronicService; + } + + + public static MedtronicPumpStatus getPumpStatus() { + return MedtronicUtil.medtronicPumpStatus; + } + + + public static void setPumpStatus(MedtronicPumpStatus medtronicPumpStatus) { + MedtronicUtil.medtronicPumpStatus = medtronicPumpStatus; + } + + + public static MedtronicCommandType getCurrentCommand() { + return MedtronicUtil.currentCommand; + } + + + public static void setCurrentCommand(MedtronicCommandType currentCommand) { + MedtronicUtil.currentCommand = currentCommand; + + if (currentCommand != null) + historyRileyLink.add(new RLHistoryItem(currentCommand)); + + } + + public static int pageNumber; + public static Integer frameNumber; + + + public static void setCurrentCommand(MedtronicCommandType currentCommand, int pageNumber_, Integer frameNumber_) { + pageNumber = pageNumber_; + frameNumber = frameNumber_; + + if (MedtronicUtil.currentCommand != currentCommand) { + setCurrentCommand(currentCommand); + } + + RxBus.INSTANCE.send(new EventMedtronicDeviceStatusChange(pumpDeviceState)); + } + + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + + + public static Map getSettings() { + return settings; + } + + + public static void setSettings(Map settings) { + MedtronicUtil.settings = settings; + } + + + public static void setPumpTime(ClockDTO pumpTime) { + MedtronicUtil.pumpTime = pumpTime; + } + + + public static ClockDTO getPumpTime() { + return MedtronicUtil.pumpTime; + } + + public static void setBatteryType(BatteryType batteryType) { + MedtronicUtil.batteryType = batteryType; + } + + + public static BatteryType getBatteryType() { + return MedtronicUtil.batteryType; + } + + + public static void displayNotConfiguredDialog(Context context) { + OKDialog.show(context, MainApp.gs(R.string.combo_warning), + MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt new file mode 100644 index 00000000000..e64eb9f7879 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodFragment.kt @@ -0,0 +1,443 @@ +package info.nightscout.androidaps.plugins.pump.omnipod + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.os.Handler +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventPreferenceChange +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyLinkStatusActivity +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodStatusRequest +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.PodManagementActivity +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodAcknowledgeAlertsChanged +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodRefreshButtonState +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import info.nightscout.androidaps.utils.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.omnipod_fragment.* +import org.slf4j.LoggerFactory + + +class OmnipodFragment : Fragment() { + private val LOG = LoggerFactory.getLogger(L.PUMP) + private var disposable: CompositeDisposable = CompositeDisposable() + //private var podAvailable = false + + operator fun CompositeDisposable.plusAssign(disposable: Disposable) { + add(disposable) + } + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + init { + refreshLoop = Runnable { + activity?.runOnUiThread { updateGUI() } + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.omnipod_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + omnipod_rl_status.text = MainApp.gs(RileyLinkServiceState.NotStarted.getResourceId(RileyLinkTargetDevice.Omnipod)) + + omnipod_pod_status.setTextColor(Color.WHITE) + omnipod_pod_status.text = "{fa-bed}" + + omnipod_pod_mgmt.setOnClickListener { + if (OmnipodUtil.getPumpStatus().verifyConfiguration()) { + startActivity(Intent(context, PodManagementActivity::class.java)) + } else { + OmnipodUtil.displayNotConfiguredDialog(context) + } + } + + omnipod_refresh.setOnClickListener { + if (!OmnipodUtil.getPumpStatus().verifyConfiguration()) { + OmnipodUtil.displayNotConfiguredDialog(context) + } else { + omnipod_refresh.isEnabled = false + OmnipodUtil.getPlugin().addPodStatusRequest(OmnipodStatusRequest.GetPodState); + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("Clicked Refresh", object : Callback() { + override fun run() { + activity?.runOnUiThread { omnipod_refresh.isEnabled = true } + } + }) + } + } + + omnipod_stats.setOnClickListener { + if (OmnipodUtil.getPumpStatus().verifyConfiguration()) { + startActivity(Intent(context, RileyLinkStatusActivity::class.java)) + } else { + OmnipodUtil.displayNotConfiguredDialog(context) + } + } + + omnipod_pod_active_alerts_ack.setOnClickListener { + if (!OmnipodUtil.getPumpStatus().verifyConfiguration()) { + OmnipodUtil.displayNotConfiguredDialog(context) + } else { + omnipod_pod_active_alerts_ack.isEnabled = false + OmnipodUtil.getPlugin().addPodStatusRequest(OmnipodStatusRequest.AcknowledgeAlerts); + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("Clicked Alert Ack", null) + } + } + + omnipod_pod_debug.setOnClickListener { + if (!OmnipodUtil.getPumpStatus().verifyConfiguration()) { + OmnipodUtil.displayNotConfiguredDialog(context) + } else { +// val readPulseLog = AapsOmnipodManager.getInstance().readPulseLog() +// +// OKDialog.show(MainApp.instance().applicationContext, MainApp.gs(R.string.action), +// "Pulse Log:\n" + readPulseLog.toString(), null) +// + + omnipod_pod_debug.isEnabled = false + OmnipodUtil.getPlugin().addPodStatusRequest(OmnipodStatusRequest.GetPodPulseLog); + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("Clicked Refresh", object : Callback() { + override fun run() { + activity?.runOnUiThread { omnipod_pod_debug.isEnabled = true } + } + }) + + } + } + + omnipod_lastconnection.setTextColor(Color.WHITE) + + setVisibilityOfPodDebugButton() + + updateGUI() + } + + override fun onResume() { + super.onResume() + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + disposable += RxBus + .toObservable(EventOmnipodRefreshButtonState::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ omnipod_refresh.isEnabled = it.newState }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventOmnipodDeviceStatusChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + if (L.isEnabled(L.PUMP)) + LOG.info("onStatusEvent(EventOmnipodDeviceStatusChange): {}", it) + setDeviceStatus() + }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventOmnipodPumpValuesChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventOmnipodAcknowledgeAlertsChanged::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateAcknowledgeAlerts(OmnipodUtil.getPumpStatus()) }, { FabricPrivacy.logException(it) }) + disposable += RxBus + .toObservable(EventPreferenceChange::class.java) + .observeOn(Schedulers.io()) + .subscribe({ event -> + setVisibilityOfPodDebugButton() + }, { FabricPrivacy.logException(it) }) + + } + + + fun setVisibilityOfPodDebugButton() { + val isEnabled = SP.getBoolean(OmnipodConst.Prefs.PodDebuggingOptionsEnabled, false) + + if (isEnabled) + omnipod_pod_debug.visibility = View.VISIBLE + else + omnipod_pod_debug.visibility = View.GONE + } + + + override fun onPause() { + super.onPause() + disposable.clear() + loopHandler.removeCallbacks(refreshLoop) + } + + + @Synchronized + private fun setDeviceStatus() { + val pumpStatus: OmnipodPumpStatus = OmnipodUtil.getPumpStatus() + pumpStatus.rileyLinkServiceState = checkStatusSet(pumpStatus.rileyLinkServiceState, + RileyLinkUtil.getServiceState()) as RileyLinkServiceState? + + LOG.info("setDeviceStatus: [pumpStatus={}]", pumpStatus) + + val resourceId = pumpStatus.rileyLinkServiceState.getResourceId(RileyLinkTargetDevice.Omnipod) + val rileyLinkError = RileyLinkUtil.getError() + omnipod_rl_status.text = + when { + pumpStatus.rileyLinkServiceState == RileyLinkServiceState.NotStarted -> MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isConnecting -> "{fa-bluetooth-b spin} " + MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isError && rileyLinkError == null -> "{fa-bluetooth-b} " + MainApp.gs(resourceId) + pumpStatus.rileyLinkServiceState.isError && rileyLinkError != null -> "{fa-bluetooth-b} " + MainApp.gs(rileyLinkError.getResourceId(RileyLinkTargetDevice.MedtronicPump)) + else -> "{fa-bluetooth-b} " + MainApp.gs(resourceId) + } + omnipod_rl_status.setTextColor(if (rileyLinkError != null) Color.RED else Color.WHITE) + + pumpStatus.rileyLinkError = checkStatusSet(pumpStatus.rileyLinkError, RileyLinkUtil.getError()) as RileyLinkError? + + omnipod_errors.text = + pumpStatus.rileyLinkError?.let { + MainApp.gs(it.getResourceId(RileyLinkTargetDevice.Omnipod)) + } ?: "-" + + val driverState = OmnipodUtil.getDriverState(); + + LOG.info("getDriverState: [driverState={}]", driverState) + + if (driverState == OmnipodDriverState.NotInitalized) { + omnipod_pod_address.text = MainApp.gs(R.string.omnipod_pod_name_no_info) + omnipod_pod_expiry.text = "-" + omnipod_pod_status.text = MainApp.gs(R.string.omnipod_pod_not_initalized) + pumpStatus.podAvailable = false + pumpStatus.podNumber == null + } else if (driverState == OmnipodDriverState.Initalized_NoPod) { + omnipod_pod_address.text = MainApp.gs(R.string.omnipod_pod_name_no_info) + omnipod_pod_expiry.text = "-" + omnipod_pod_status.text = MainApp.gs(R.string.omnipod_pod_no_pod_connected) + pumpStatus.podAvailable = false + pumpStatus.podNumber == null + } else { + pumpStatus.podLotNumber = "" + pumpStatus.podSessionState.lot + pumpStatus.podAvailable = true + omnipod_pod_address.text = pumpStatus.podSessionState.address.toString() + omnipod_pod_expiry.text = pumpStatus.podSessionState.expiryDateAsString + pumpStatus.podNumber = pumpStatus.podSessionState.address.toString() + + //pumpStatus.podSessionState = checkStatusSet(pumpStatus.podSessionState, + // OmnipodUtil.getPodSessionState()) as PodSessionState? + + var podDeviceState = pumpStatus.podDeviceState + + when (podDeviceState) { + null, + PodDeviceState.Sleeping -> omnipod_pod_status.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name()); + PodDeviceState.NeverContacted, + PodDeviceState.WakingUp, + PodDeviceState.PumpUnreachable, + PodDeviceState.ErrorWhenCommunicating, + PodDeviceState.TimeoutWhenCommunicating, + PodDeviceState.InvalidConfiguration -> omnipod_pod_status.text = " " + MainApp.gs(podDeviceState.resourceId) + PodDeviceState.Active -> { + + omnipod_pod_status.text = "Active"; +// val cmd = OmnipodUtil.getCurrentCommand() +// if (cmd == null) +// omnipod_pod_status.text = " " + MainApp.gs(pumpStatus.pumpDeviceState.resourceId) +// else { +// log.debug("Command: " + cmd) +// val cmdResourceId = cmd.resourceId +// if (cmd == MedtronicCommandType.GetHistoryData) { +// omnipod_pod_status.text = OmnipodUtil.frameNumber?.let { +// MainApp.gs(cmdResourceId, OmnipodUtil.pageNumber, OmnipodUtil.frameNumber) +// } +// ?: MainApp.gs(R.string.medtronic_cmd_desc_get_history_request, OmnipodUtil.pageNumber) +// } else { +// omnipod_pod_status.text = " " + (cmdResourceId?.let { MainApp.gs(it) } +// ?: cmd.getCommandDescription()) +// } +// } + } + else -> LOG.warn("Unknown pump state: " + pumpStatus.podDeviceState) + } + + + } + + +// pumpStatus.pumpDeviceState = checkStatusSet(pumpStatus.pumpDeviceState, +// OmnipodUtil.getPumpDeviceState()) as PumpDeviceState? +// +// when (pumpStatus.pumpDeviceState) { +// null, +// PumpDeviceState.Sleeping -> omnipod_pod_status.text = "{fa-bed} " // + pumpStatus.pumpDeviceState.name()); +// PumpDeviceState.NeverContacted, +// PumpDeviceState.WakingUp, +// PumpDeviceState.PumpUnreachable, +// PumpDeviceState.ErrorWhenCommunicating, +// PumpDeviceState.TimeoutWhenCommunicating, +// PumpDeviceState.InvalidConfiguration -> omnipod_pod_status.text = " " + MainApp.gs(pumpStatus.pumpDeviceState.resourceId) +// PumpDeviceState.Active -> { +// val cmd = OmnipodUtil.getCurrentCommand() +// if (cmd == null) +// omnipod_pod_status.text = " " + MainApp.gs(pumpStatus.pumpDeviceState.resourceId) +// else { +// log.debug("Command: " + cmd) +// val cmdResourceId = cmd.resourceId +// if (cmd == MedtronicCommandType.GetHistoryData) { +// omnipod_pod_status.text = OmnipodUtil.frameNumber?.let { +// MainApp.gs(cmdResourceId, OmnipodUtil.pageNumber, OmnipodUtil.frameNumber) +// } +// ?: MainApp.gs(R.string.medtronic_cmd_desc_get_history_request, OmnipodUtil.pageNumber) +// } else { +// omnipod_pod_status.text = " " + (cmdResourceId?.let { MainApp.gs(it) } +// ?: cmd.getCommandDescription()) +// } +// } +// } +// else -> log.warn("Unknown pump state: " + pumpStatus.pumpDeviceState) +// } + + val status = ConfigBuilderPlugin.getPlugin().commandQueue.spannedStatus() + if (status.toString() == "") { + omnipod_queue.visibility = View.GONE + } else { + omnipod_queue.visibility = View.VISIBLE + omnipod_queue.text = status + } + } + + + private fun checkStatusSet(object1: Any?, object2: Any?): Any? { + return if (object1 == null) { + object2 + } else { + if (object1 != object2) { + object2 + } else + object1 + } + } + + // GUI functions + fun updateGUI() { + val plugin = OmnipodPumpPlugin.getPlugin() + val pumpStatus = OmnipodUtil.getPumpStatus() + var pumpType = OmnipodUtil.getPumpType() + + if (pumpType==null) { + LOG.warn("PumpType was not set, reseting to Omnipod.") + pumpType = PumpType.Insulet_Omnipod; + } + + setDeviceStatus() + + if (pumpStatus.podAvailable) { + // last connection + if (pumpStatus.lastConnection != 0L) { + //val minAgo = DateUtil.minAgo(pumpStatus.lastConnection) + val min = (System.currentTimeMillis() - pumpStatus.lastConnection) / 1000 / 60 + if (pumpStatus.lastConnection + 60 * 1000 > System.currentTimeMillis()) { + omnipod_lastconnection.setText(R.string.combo_pump_connected_now) + //omnipod_lastconnection.setTextColor(Color.WHITE) + } else { //if (pumpStatus.lastConnection + 30 * 60 * 1000 < System.currentTimeMillis()) { + + if (min < 60) { + omnipod_lastconnection.text = MainApp.gs(R.string.minago, min) + } else if (min < 1440) { + val h = (min / 60).toInt() + omnipod_lastconnection.text = (MainApp.gq(R.plurals.objective_hours, h, h) + " " + + MainApp.gs(R.string.ago)) + } else { + val h = (min / 60).toInt() + val d = h / 24 + // h = h - (d * 24); + omnipod_lastconnection.text = (MainApp.gq(R.plurals.objective_days, d, d) + " " + + MainApp.gs(R.string.ago)) + } + //omnipod_lastconnection.setTextColor(Color.RED) + } +// } else { +// omnipod_lastconnection.text = minAgo +// //omnipod_lastconnection.setTextColor(Color.WHITE) +// } + } + + // last bolus + val bolus = pumpStatus.lastBolusAmount + val bolusTime = pumpStatus.lastBolusTime + if (bolus != null && bolusTime != null && pumpStatus.podAvailable) { + val agoMsc = System.currentTimeMillis() - pumpStatus.lastBolusTime.time + val bolusMinAgo = agoMsc.toDouble() / 60.0 / 1000.0 + val unit = MainApp.gs(R.string.insulin_unit_shortname) + val ago: String + if (agoMsc < 60 * 1000) { + ago = MainApp.gs(R.string.combo_pump_connected_now) + } else if (bolusMinAgo < 60) { + ago = DateUtil.minAgo(pumpStatus.lastBolusTime.time) + } else { + ago = DateUtil.hourAgo(pumpStatus.lastBolusTime.time) + } + omnipod_lastbolus.text = MainApp.gs(R.string.omnipod_last_bolus, pumpType.determineCorrectBolusSize(bolus), unit, ago) + } else { + omnipod_lastbolus.text = "" + } + + // base basal rate + omnipod_basabasalrate.text = MainApp.gs(R.string.pump_basebasalrate, pumpType.determineCorrectBasalSize(plugin.baseBasalRate)) + + omnipod_tempbasal.text = TreatmentsPlugin.getPlugin() + .getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() ?: "" + + // reservoir + if (RileyLinkUtil.isSame(pumpStatus.reservoirRemainingUnits, 75.0)) { + omnipod_reservoir.text = MainApp.gs(R.string.omnipod_reservoir_over50) + } else { + omnipod_reservoir.text = MainApp.gs(R.string.omnipod_reservoir_left, pumpStatus.reservoirRemainingUnits) + } + SetWarnColor.setColorInverse(omnipod_reservoir, pumpStatus.reservoirRemainingUnits, 50.0, 20.0) + + } else { + omnipod_basabasalrate.text = "" + omnipod_reservoir.text = "" + omnipod_tempbasal.text = "" + omnipod_lastbolus.text = "" + omnipod_lastconnection.text = "" + omnipod_lastconnection.setTextColor(Color.WHITE) + } + + omnipod_errors.text = pumpStatus.errorInfo + + updateAcknowledgeAlerts(pumpStatus) + + omnipod_refresh.isEnabled = pumpStatus.podAvailable + + } + + + private fun updateAcknowledgeAlerts(pumpStatus: OmnipodPumpStatus) { + if (pumpStatus != null) { + omnipod_pod_active_alerts_ack.isEnabled = pumpStatus.ackAlertsAvailable + omnipod_pod_active_alerts.text = pumpStatus.ackAlertsText + } + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java new file mode 100644 index 00000000000..1bb491694e9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java @@ -0,0 +1,980 @@ +package info.nightscout.androidaps.plugins.pump.omnipod; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.ErrorHelperActivity; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.EventPreferenceChange; +import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ResetRileyLinkConfigurationTask; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.tasks.ServiceTaskExecutor; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCustomActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPodType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPumpPluginInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodStatusRequest; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsOmnipodManager; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.ui.OmnipodUIComm; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.ui.OmnipodUITask; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodRefreshButtonState; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.TimeChangeType; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +/** + * Created by andy on 23.04.18. + * + * @author Andy Rozman (andy.rozman@gmail.com) + */ +public class OmnipodPumpPlugin extends PumpPluginAbstract implements OmnipodPumpPluginInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + private static OmnipodPumpPlugin plugin = null; + private RileyLinkOmnipodService omnipodService; + protected OmnipodPumpStatus pumpStatusLocal = null; + protected OmnipodUIComm omnipodUIComm; + private CompositeDisposable disposable = new CompositeDisposable(); + + // variables for handling statuses and history + protected boolean firstRun = true; + protected boolean isRefresh = false; + private boolean isBasalProfileInvalid = false; + private boolean basalProfileChanged = false; + private boolean isInitialized = false; + protected OmnipodCommunicationManagerInterface omnipodCommunicationManager; + + public static boolean isBusy = false; + protected List busyTimestamps = new ArrayList<>(); + protected boolean sentIdToFirebase = false; + protected boolean hasTimeDateOrTimeZoneChanged = false; + private int timeChangeRetries = 0; + + private Profile currentProfile; + + boolean omnipodServiceRunning = false; + + private long nextPodCheck = 0L; + //OmnipodDriverState driverState = OmnipodDriverState.NotInitalized; + + private OmnipodPumpPlugin() { + + super(new PluginDescription() // + .mainType(PluginType.PUMP) // + .fragmentClass(OmnipodFragment.class.getName()) // + .pluginName(R.string.omnipod_name) // + .shortName(R.string.omnipod_name_short) // + .preferencesId(R.xml.pref_omnipod) // + .description(R.string.description_pump_omnipod), // + PumpType.Insulet_Omnipod + ); + + displayConnectionMessages = false; + + //OmnipodUtil.setDriverState(); + +// TODO loop +// if (OmnipodUtil.isOmnipodEros()) { +// OmnipodUtil.setPlugin(this); +// OmnipodUtil.setOmnipodPodType(OmnipodPodType.Eros); +// OmnipodUtil.setPumpType(PumpType.Insulet_Omnipod); +// } + +// // TODO ccc + + + + serviceConnection = new ServiceConnection() { + + @Override + public void onServiceDisconnected(ComponentName name) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkOmnipodService is disconnected"); + omnipodService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (isLoggingEnabled()) + LOG.debug("RileyLinkOmnipodService is connected"); + RileyLinkOmnipodService.LocalBinder mLocalBinder = (RileyLinkOmnipodService.LocalBinder) service; + omnipodService = mLocalBinder.getServiceInstance(); + + new Thread(() -> { + + for (int i = 0; i < 20; i++) { + + if (pumpStatusLocal != null) { + if (isLoggingEnabled()) + LOG.debug("Starting OmniPod-RileyLink service"); + if (OmnipodUtil.getPumpStatus().setNotInPreInit()) { + if (omnipodCommunicationManager == null) { + omnipodCommunicationManager = AapsOmnipodManager.getInstance(); + omnipodCommunicationManager.setPumpStatus(pumpStatusLocal); + omnipodServiceRunning = true; + } else { + omnipodCommunicationManager.setPumpStatus(pumpStatusLocal); + } + + OmnipodUtil.setOmnipodPodType(OmnipodPodType.Eros); + OmnipodUtil.setPlugin(OmnipodPumpPlugin.this); + + omnipodUIComm = new OmnipodUIComm(omnipodCommunicationManager, plugin, pumpStatusLocal); + break; + } + } + + SystemClock.sleep(5000); + } + }).start(); + } + }; + } + + protected OmnipodPumpPlugin(PluginDescription pluginDescription, PumpType pumpType) { + super(pluginDescription, pumpType); + } + + public static OmnipodPumpPlugin getPlugin() { + if (plugin == null) + plugin = new OmnipodPumpPlugin(); + return plugin; + } + + + @Override + protected void onStart() { + super.onStart(); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if ((event.isChanged(R.string.key_omnipod_beep_basal_enabled)) || + (event.isChanged(R.string.key_omnipod_beep_bolus_enabled)) || + (event.isChanged(R.string.key_omnipod_beep_tbr_enabled)) || + (event.isChanged(R.string.key_omnipod_pod_debugging_options_enabled)) || + (event.isChanged(R.string.key_omnipod_beep_smb_enabled)) || + (event.isChanged(R.string.key_omnipod_timechange_enabled))) + refreshConfiguration(); + }, FabricPrivacy::logException) + ); + refreshConfiguration(); + } + +// @Override +// protected void onResume() { +// +// } + + private void refreshConfiguration() { + if (pumpStatusLocal != null) { + pumpStatusLocal.refreshConfiguration(); + } + } + + @Override + protected void onStop() { + disposable.clear(); + super.onStop(); + } + + private String getLogPrefix() { + return "OmnipodPlugin::"; + } + + + @Override + public void initPumpStatusData() { + + this.pumpStatusLocal = new OmnipodPumpStatus(pumpDescription); + if (omnipodCommunicationManager != null) { + omnipodCommunicationManager.setPumpStatus(pumpStatusLocal); + } + + OmnipodUtil.setPumpStatus(pumpStatusLocal); + + pumpStatusLocal.lastConnection = SP.getLong(RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + pumpStatusLocal.lastDataTime = new LocalDateTime(pumpStatusLocal.lastConnection); + pumpStatusLocal.previousConnection = pumpStatusLocal.lastConnection; + + pumpStatusLocal.refreshConfiguration(); + + if (isLoggingEnabled()) + LOG.debug("initPumpStatusData: {}", this.pumpStatusLocal); + + this.pumpStatus = pumpStatusLocal; + + // set first Omnipod Start + if (!SP.contains(OmnipodConst.Statistics.FirstPumpStart)) { + SP.putLong(OmnipodConst.Statistics.FirstPumpStart, System.currentTimeMillis()); + } + + } + + + @Override + public void onStartCustomActions() { + + // check status every minute (if any status needs refresh we send readStatus command) + new Thread(() -> { + + do { + SystemClock.sleep(60000); + + if (this.isInitialized) { + clearBusyQueue(); + } + + if (!this.omnipodStatusRequestList.isEmpty() || this.hasTimeDateOrTimeZoneChanged) { + if (!ConfigBuilderPlugin.getPlugin().getCommandQueue().statusInQueue()) { + ConfigBuilderPlugin.getPlugin().getCommandQueue() + .readStatus("Status Refresh Requested", null); + } + } + + doPodCheck(); + + } while (serviceRunning); + + }).start(); + } + + private void doPodCheck() { + + if (System.currentTimeMillis() > this.nextPodCheck) { + if (OmnipodUtil.getDriverState() == OmnipodDriverState.Initalized_NoPod) { + Notification notification = new Notification(Notification.OMNIPOD_POD_NOT_ATTACHED, MainApp.gs(R.string.omnipod_error_pod_not_attached), Notification.NORMAL); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else { + RxBus.INSTANCE.send(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED)); + } + + this.nextPodCheck = DateTimeUtil.getTimeInFutureFromMinutes(15); + } + } + + + @Override + public Class getServiceClass() { + return RileyLinkOmnipodService.class; + } + + + @Override + public String deviceID() { + return "Omnipod"; + } + + + // Pump Plugin + + private boolean isServiceSet() { + return omnipodService != null; + } + + + @Override + public boolean isInitialized() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isInitialized"); + return isServiceSet() && isInitialized; + } + + + @Override + public boolean isBusy() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isBusy"); + + if (isServiceSet()) { + + if (isBusy || !pumpStatusLocal.podAvailable) + return true; + + if (busyTimestamps.size() > 0) { + + clearBusyQueue(); + + return (busyTimestamps.size() > 0); + } + } + + return false; + } + + + private synchronized void clearBusyQueue() { + + if (busyTimestamps.size() == 0) { + return; + } + + Set deleteFromQueue = new HashSet<>(); + + for (Long busyTimestamp : busyTimestamps) { + + if (System.currentTimeMillis() > busyTimestamp) { + deleteFromQueue.add(busyTimestamp); + } + } + + if (deleteFromQueue.size() == busyTimestamps.size()) { + busyTimestamps.clear(); + //setEnableCustomAction(MedtronicCustomActionType.ClearBolusBlock, false); + } + + if (deleteFromQueue.size() > 0) { + busyTimestamps.removeAll(deleteFromQueue); + } + + } + + + @Override + public boolean isConnected() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnected"); + return isServiceSet() && omnipodService.isInitialized(); + } + + + @Override + public boolean isConnecting() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnecting"); + return !isServiceSet() || !omnipodService.isInitialized(); + } + + + @Override + public boolean isSuspended() { + + return (OmnipodUtil.getDriverState() == OmnipodDriverState.Initalized_NoPod) || + (OmnipodUtil.getPodSessionState() != null && OmnipodUtil.getPodSessionState().isSuspended()); + +// return (pumpStatusLocal != null && !pumpStatusLocal.podAvailable) || +// (OmnipodUtil.getPodSessionState() != null && OmnipodUtil.getPodSessionState().isSuspended()); +// +// TODO ddd +// return (OmnipodUtil.getDriverState() == OmnipodDriverState.Initalized_NoPod) || +// (OmnipodUtil.getPodSessionState() != null && OmnipodUtil.getPodSessionState().isSuspended()); +// +// return (pumpStatusLocal != null && !pumpStatusLocal.podAvailable) || +// (OmnipodUtil.getPodSessionState() != null && OmnipodUtil.getPodSessionState().isSuspended()); + } + + @Override + public void getPumpStatus() { + + if (firstRun) { + initializePump(!isRefresh); + triggerUIChange(); + } else if (!omnipodStatusRequestList.isEmpty()) { + + List removeList = new ArrayList<>(); + + for (OmnipodStatusRequest omnipodStatusRequest : omnipodStatusRequestList) { + if (omnipodStatusRequest == OmnipodStatusRequest.GetPodPulseLog) { + OmnipodUITask omnipodUITask = omnipodUIComm.executeCommand(omnipodStatusRequest.getCommandType()); + + PodInfoRecentPulseLog result = (PodInfoRecentPulseLog) omnipodUITask.returnDataObject; + + if (result == null) { + LOG.warn("Result was null."); + } else { + LOG.warn("Result was NOT null."); + + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", 0); + i.putExtra("status", "Pulse Log (copied to clipboard):\n" + result.toString()); + i.putExtra("title", MainApp.gs(R.string.combo_warning)); + i.putExtra("clipboardContent", result.toString()); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + +// OKDialog.show(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.action), +// "Pulse Log:\n" + result.toString(), null); + } + + } else { + omnipodUIComm.executeCommand(omnipodStatusRequest.getCommandType()); + } + removeList.add(omnipodStatusRequest); + } + + omnipodStatusRequestList.removeAll(removeList); + + } else if (this.hasTimeDateOrTimeZoneChanged) { + OmnipodUITask omnipodUITask = omnipodUIComm.executeCommand(OmnipodCommandType.SetTime); + + if (omnipodUITask.wasCommandSuccessful()) { + this.hasTimeDateOrTimeZoneChanged = false; + timeChangeRetries = 0; + + Notification notification = new Notification( + Notification.TIME_OR_TIMEZONE_CHANGE, + MainApp.gs(R.string.time_or_timezone_change), + Notification.INFO, 60); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + + } else { + timeChangeRetries++; + + if (timeChangeRetries > 3) { + this.hasTimeDateOrTimeZoneChanged = false; + timeChangeRetries = 0; + } + } + } + } + + + public void setIsBusy(boolean isBusy_) { + isBusy = isBusy_; + } + + + private void getPodPumpStatus() { + // TODO read pod status + LOG.error("getPodPumpStatus() NOT IMPLEMENTED"); + + //getPodPumpStatusObject().driverState = OmnipodDriverState.Initalized_PodAvailable; + //driverState = OmnipodDriverState.Initalized_PodAvailable; + OmnipodUtil.setDriverState(OmnipodDriverState.Initalized_PodAvailable); + // we would probably need to read Basal Profile here too + } + + + List omnipodStatusRequestList = new ArrayList<>(); + + public void addPodStatusRequest(OmnipodStatusRequest pumpStatusRequest) { + if (pumpStatusRequest == OmnipodStatusRequest.ResetState) { + resetStatusState(); + } else { + omnipodStatusRequestList.add(pumpStatusRequest); + } + } + + @Override + public void setDriverState(OmnipodDriverState state) { + //this.driverState = state; + } + + + public void resetStatusState() { + firstRun = true; + isRefresh = true; + } + + + private void setRefreshButtonEnabled(boolean enabled) { + RxBus.INSTANCE.send(new EventOmnipodRefreshButtonState(enabled)); + } + + + private void initializePump(boolean realInit) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "initializePump - start"); + + // TODO ccc + //OmnipodPumpStatus podPumpStatus = getPodPumpStatusObject(); + + setRefreshButtonEnabled(false); + + PodSessionState podSessionState = null; + + if (OmnipodUtil.getPodSessionState() != null) { + podSessionState = OmnipodUtil.getPodSessionState(); + } else { + String podState = SP.getString(OmnipodConst.Prefs.PodState, null); + + LOG.info("PodSessionState-SP: loaded from SharedPreferences: " + podState); + + if (podState != null) { + podSessionState = OmnipodUtil.getGsonInstance().fromJson(podState, PodSessionState.class); + OmnipodUtil.setPodSessionState(podSessionState); + } + } + + + if (podSessionState != null) { + LOG.debug("PodSessionState (saved): " + podSessionState); + + if (!isRefresh) { + pumpState = PumpDriverState.Initialized; + } + + // TODO handle if session state too old + getPodPumpStatus(); + + } else { + LOG.debug("No PodSessionState found. Pod probably not running."); + //podPumpStatus.driverState = OmnipodDriverState.Initalized_NoPod; + OmnipodUtil.setDriverState(OmnipodDriverState.Initalized_NoPod); + } + + finishAction("Omnipod Pump"); + + if (!sentIdToFirebase) { + Bundle params = new Bundle(); + params.putString("version", BuildConfig.VERSION); + MainApp.getFirebaseAnalytics().logEvent("OmnipodPumpInit", params); + + sentIdToFirebase = true; + } + + isInitialized = true; + + this.firstRun = false; + } + + + @Override + public boolean isThisProfileSet(Profile profile) { + + // TODO status was not yet read from pod + // TODO maybe not possible, need to see how we will handle that + if (currentProfile == null) { + this.currentProfile = profile; + return true; + } + + return (currentProfile.isProfileTheSame(profile)); + } + + + @Override + public long lastDataTime() { + getPodPumpStatusObject(); + + if (pumpStatusLocal.lastConnection != 0) { + return pumpStatusLocal.lastConnection; + } + + return System.currentTimeMillis(); + } + + + @Override + public double getBaseBasalRate() { + + if (currentProfile != null) { + int hour = (new GregorianCalendar()).get(Calendar.HOUR_OF_DAY); + return currentProfile.getBasalTimeFromMidnight(DateTimeUtil.getTimeInS(hour * 60)); + } else { + return 0.0d; + } + } + + + @Override + public double getReservoirLevel() { + return getPodPumpStatusObject().reservoirRemainingUnits; + } + + + @Override + public int getBatteryLevel() { + return 75; + } + + + protected OmnipodPumpStatus getPodPumpStatusObject() { + if (pumpStatusLocal == null) { + // FIXME I don't know why this happens + if (isLoggingEnabled()) + LOG.warn("!!!! Reset Pump Status Local"); + pumpStatusLocal = OmnipodUtil.getPumpStatus(); + if (omnipodCommunicationManager != null) { + omnipodCommunicationManager.setPumpStatus(pumpStatusLocal); + } + } + + return pumpStatusLocal; + } + + + @Override + protected void triggerUIChange() { + RxBus.INSTANCE.send(new EventOmnipodPumpValuesChanged()); + } + + + @Override + public boolean isFakingTempsByExtendedBoluses() { + return false; + } + + + @Override + @NonNull + protected PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { + + LOG.info(getLogPrefix() + "deliverBolus - {}", detailedBolusInfo); + + setRefreshButtonEnabled(false); + + try { + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetBolus, + detailedBolusInfo); + + PumpEnactResult result = responseTask.getResult(); + + setRefreshButtonEnabled(true); + + if (result.success) { + + // we subtract insulin, exact amount will be visible with next remainingInsulin update. +// if (getPodPumpStatusObject().reservoirRemainingUnits != 0 && +// getPodPumpStatusObject().reservoirRemainingUnits != 75 ) { +// getPodPumpStatusObject().reservoirRemainingUnits -= detailedBolusInfo.insulin; +// } + + incrementStatistics(detailedBolusInfo.isSMB ? OmnipodConst.Statistics.SMBBoluses + : OmnipodConst.Statistics.StandardBoluses); + + result.carbsDelivered(detailedBolusInfo.carbs); + } + + return result; + } finally { + finishAction("Bolus"); + } + } + + @Override + public void stopBolusDelivering() { + LOG.info(getLogPrefix() + "stopBolusDelivering"); + + setRefreshButtonEnabled(false); + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.CancelBolus); + + PumpEnactResult result = responseTask.getResult(); + + //setRefreshButtonEnabled(true); + + LOG.info(getLogPrefix() + "stopBolusDelivering - wasSuccess={}", result.success); + + //finishAction("Bolus"); + } + + + private void incrementStatistics(String statsKey) { + long currentCount = SP.getLong(statsKey, 0L); + currentCount++; + SP.putLong(statsKey, currentCount); + } + + + // if enforceNew===true current temp basal is canceled and new TBR set (duration is prolonged), + // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed + @Override + public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, Profile profile, + boolean enforceNew) { + + getPodPumpStatusObject(); + + setRefreshButtonEnabled(false); + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: rate: {}, duration={}", absoluteRate, durationInMinutes); + + // read current TBR + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent != null) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute: Current Basal: duration: {} min, rate={}", + tbrCurrent.getDurationMinutes(), tbrCurrent.getInsulinRate()); + } + + if (tbrCurrent != null && !enforceNew) { + if (OmnipodUtil.isSame(tbrCurrent.getInsulinRate(), absoluteRate)) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - No enforceNew and same rate. Exiting."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + } + } + + // if TBR is running we will cancel it. +// if (tbrCurrent != null) { +// if (isLoggingEnabled()) +// LOG.info(getLogPrefix() + "setTempBasalAbsolute - TBR running - so canceling it."); +// +// // CANCEL +// OmnipodUITask responseTask2 = omnipodUIComm.executeCommand(OmnipodCommandType.CancelTemporaryBasal); +// +// PumpEnactResult result = responseTask2.getResult(); +// +// if (result.success) { +// if (isLoggingEnabled()) +// LOG.info(getLogPrefix() + "setTempBasalAbsolute - Current TBR cancelled."); +// } else { +// if (isLoggingEnabled()) +// LOG.error(getLogPrefix() + "setTempBasalAbsolute - Cancel TBR failed."); +// +// finishAction("TBR"); +// +// return result; +// } +// } + + // now start new TBR + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetTemporaryBasal, + absoluteRate, durationInMinutes); + + PumpEnactResult result = responseTask.getResult(); + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setTempBasalAbsolute - setTBR. Response: " + result.success); + + if (result.success) { + incrementStatistics(OmnipodConst.Statistics.TBRsSet); + } + + finishAction("TBR"); + return result; + } + + protected TempBasalPair readTBR() { + // TODO we can do it like this or read status from pod ?? + if (pumpStatusLocal.tempBasalEnd < System.currentTimeMillis()) { + // TBR done + pumpStatusLocal.clearTemporaryBasal(); + + return null; + } + + return pumpStatusLocal.getTemporaryBasal(); + } + + + protected void finishAction(String overviewKey) { + if (overviewKey != null) + RxBus.INSTANCE.send(new EventRefreshOverview(overviewKey)); + + triggerUIChange(); + + setRefreshButtonEnabled(true); + } + + + @Override + public PumpEnactResult cancelTempBasal(boolean enforceNew) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - started"); + + setRefreshButtonEnabled(false); + + TempBasalPair tbrCurrent = readTBR(); + + if (tbrCurrent == null) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - TBR already canceled."); + finishAction("TBR"); + return new PumpEnactResult().success(true).enacted(false); + + } + + OmnipodUITask responseTask2 = omnipodUIComm.executeCommand(OmnipodCommandType.CancelTemporaryBasal); + + PumpEnactResult result = responseTask2.getResult(); + + finishAction("TBR"); + + if (result.success) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR successful."); + + TemporaryBasal tempBasal = new TemporaryBasal() // + .date(System.currentTimeMillis()) // + .duration(0) // + .source(Source.USER); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); + } else { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "cancelTempBasal - Cancel TBR failed."); + } + + return result; + } + + @Override + public String serialNumber() { + return getPodPumpStatusObject().podNumber; + } + + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "setNewBasalProfile"); + + // this shouldn't be needed, but let's do check if profile setting we are setting is same as current one + if (this.currentProfile != null && this.currentProfile.isProfileTheSame(profile)) { + return new PumpEnactResult() // + .success(true) // + .enacted(false) // + .comment(MainApp.gs(R.string.medtronic_cmd_basal_profile_not_set_is_same)); + } + + setRefreshButtonEnabled(false); + + OmnipodUITask responseTask = omnipodUIComm.executeCommand(OmnipodCommandType.SetBasalProfile, + profile); + + PumpEnactResult result = responseTask.getResult(); + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "Basal Profile was set: " + result.success); + + if (result.success) { + this.currentProfile = profile; + + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else { + Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.gs(R.string.failedupdatebasalprofile), Notification.URGENT); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } + + return result; + } + + + // OPERATIONS not supported by Pump or Plugin + + protected List customActions = null; + + private CustomAction customActionResetRLConfig = new CustomAction( + R.string.medtronic_custom_action_reset_rileylink, OmnipodCustomActionType.ResetRileyLinkConfiguration, true); + + + @Override + public List getCustomActions() { + + if (customActions == null) { + this.customActions = Arrays.asList( + customActionResetRLConfig //, + ); + } + + return this.customActions; + + } + + + @Override + public void executeCustomAction(CustomActionType customActionType) { + + OmnipodCustomActionType mcat = (OmnipodCustomActionType) customActionType; + + switch (mcat) { + + case ResetRileyLinkConfiguration: { + ServiceTaskExecutor.startTask(new ResetRileyLinkConfigurationTask()); + } + break; + + default: + break; + } + + } + + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + if (isLoggingEnabled()) + LOG.warn(getLogPrefix() + "Time, Date and/or TimeZone changed. [changeType={}, eventHandlingEnabled={}]", timeChangeType.name(), pumpStatusLocal.timeChangeEventEnabled); + + if (OmnipodUtil.getDriverState() == OmnipodDriverState.Initalized_PodAvailable) { + if (pumpStatusLocal.timeChangeEventEnabled) { + LOG.info(getLogPrefix() + "Time,and/or TimeZone changed event received and will be consumed by driver."); + this.hasTimeDateOrTimeZoneChanged = true; + } + } + } + + @Override + public boolean isUnreachableAlertTimeoutExceeded(long unreachableTimeoutMilliseconds) { + getPodPumpStatusObject(); + + if (pumpStatusLocal.lastConnection != 0 || pumpStatusLocal.lastErrorConnection != 0) { + if (pumpStatusLocal.lastConnection + unreachableTimeoutMilliseconds < System.currentTimeMillis()) { + if (pumpStatusLocal.lastErrorConnection > pumpStatusLocal.lastConnection) { + // We exceeded the alert threshold, and our last connection failed + // We should show an alert + return true; + } + + // Don't trigger an alert when we exceeded the thresholds, but the last communication was successful + // This happens when we simply didn't need to send any commands to the pump + return false; + } + + } + + return false; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/BolusProgressIndicationConsumer.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/BolusProgressIndicationConsumer.java new file mode 100644 index 00000000000..35d07e36225 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/BolusProgressIndicationConsumer.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +// TODO replace with Consumer when our min API level >= 24 +@FunctionalInterface +public interface BolusProgressIndicationConsumer { + void accept(double estimatedUnitsDelivered, int percentage); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationService.java new file mode 100644 index 00000000000..7222376e7e7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodCommunicationService.java @@ -0,0 +1,333 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkCommunicationException; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RLMessageType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkBLEError; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.OmnipodAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodPacket; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.DeactivatePodCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommunicationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPacketTypeException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalResponseException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NonceOutOfSyncException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NonceResyncException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NotEnoughDataException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException; + +/** + * Created by andy on 6/29/18. + */ + +public class OmnipodCommunicationService extends RileyLinkCommunicationManager { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + public OmnipodCommunicationService(RFSpy rfspy) { + super(rfspy); + } + + @Override + protected void configurePumpSpecificSettings() { + } + + @Override + public boolean tryToConnectToDevice() { + // TODO + return false; + } + + @Override + public byte[] createPumpMessageContent(RLMessageType type) { + return new byte[0]; + } + + @Override + public boolean hasTunning() { + return false; + } + + @Override + public E createResponseMessage(byte[] payload, Class clazz) { + return (E) new OmnipodPacket(payload); + } + + public T sendCommand(Class responseClass, PodState podState, MessageBlock command) { + return sendCommand(responseClass, podState, command, true); + } + + public T sendCommand(Class responseClass, PodState podState, MessageBlock command, boolean automaticallyResyncNone) { + OmnipodMessage message = new OmnipodMessage(podState.getAddress(), Collections.singletonList(command), podState.getMessageNumber()); + return exchangeMessages(responseClass, podState, message, automaticallyResyncNone); + } + + // Convenience method + public T executeAction(OmnipodAction action) { + return action.execute(this); + } + + public T exchangeMessages(Class responseClass, PodState podState, OmnipodMessage message) { + return exchangeMessages(responseClass, podState, message, true); + } + + public T exchangeMessages(Class responseClass, PodState podState, OmnipodMessage message, boolean automaticallyResyncNonce) { + return exchangeMessages(responseClass, podState, message, null, null, automaticallyResyncNonce); + } + + public synchronized T exchangeMessages(Class responseClass, PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) { + return exchangeMessages(responseClass, podState, message, addressOverride, ackAddressOverride, true); + } + + public synchronized T exchangeMessages(Class responseClass, PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride, boolean automaticallyResyncNonce) { + if (isLoggingEnabled()) { + LOG.debug("Exchanging OmnipodMessage [responseClass={}, podState={}, message={}, addressOverride={}, ackAddressOverride={}, automaticallyResyncNonce={}]: {}", // + responseClass.getSimpleName(), podState, message, addressOverride, ackAddressOverride, automaticallyResyncNonce, message); + } + + for (int i = 0; 2 > i; i++) { + + if (podState.hasNonceState() && message.isNonceResyncable()) { + podState.advanceToNextNonce(); + } + + MessageBlock responseMessageBlock = transportMessages(podState, message, addressOverride, ackAddressOverride); + + if (responseMessageBlock instanceof StatusResponse) { + podState.updateFromStatusResponse((StatusResponse) responseMessageBlock); + } + + if (responseClass.isInstance(responseMessageBlock)) { + return (T) responseMessageBlock; + } else { + if (responseMessageBlock.getType() == MessageBlockType.ERROR_RESPONSE) { + ErrorResponse error = (ErrorResponse) responseMessageBlock; + if (error.getErrorResponseType() == ErrorResponseType.BAD_NONCE) { + podState.resyncNonce(error.getNonceSearchKey(), message.getSentNonce(), message.getSequenceNumber()); + if (automaticallyResyncNonce) { + message.resyncNonce(podState.getCurrentNonce()); + } else { + throw new NonceOutOfSyncException(); + } + } else { + throw new PodReturnedErrorResponseException((ErrorResponse) responseMessageBlock); + } + } else if (responseMessageBlock.getType() == MessageBlockType.POD_INFO_RESPONSE && ((PodInfoResponse) responseMessageBlock).getSubType() == PodInfoType.FAULT_EVENT) { + PodInfoFaultEvent faultEvent = ((PodInfoResponse) responseMessageBlock).getPodInfo(); + podState.setFaultEvent(faultEvent); + throw new PodFaultException(faultEvent); + } else { + throw new IllegalResponseException(responseClass.getSimpleName(), responseMessageBlock.getType()); + } + } + } + + throw new NonceResyncException(); + } + + private MessageBlock transportMessages(PodState podState, OmnipodMessage message, Integer addressOverride, Integer ackAddressOverride) { + int packetAddress = podState.getAddress(); + if (addressOverride != null) { + packetAddress = addressOverride; + } + + boolean firstPacket = true; + byte[] encodedMessage; + // this does not work well with the deactivate pod command, we somehow either + // receive an ACK instead of a normal response, or a partial response and a communication timeout + if (message.isNonceResyncable() && !message.containsBlock(DeactivatePodCommand.class)) { + OmnipodMessage paddedMessage = new OmnipodMessage(message); + // If messages are nonce resyncable, we want do distinguish between certain and uncertain failures for verification purposes + // However, some commands (e.g. cancel delivery) are single packet command by nature. When we get a timeout with a single packet, + // we are unsure whether or not the command was received by the pod + // However, if we send > 1 packet, we know that the command wasn't received if we never send the subsequent packets, + // because the last packet contains the CRC. + // So we pad the message with get status commands to make it > packet + paddedMessage.padWithGetStatusCommands(PacketType.PDM.getMaxBodyLength()); // First packet is of type PDM + encodedMessage = paddedMessage.getEncoded(); + } else { + encodedMessage = message.getEncoded(); + } + + OmnipodPacket response = null; + while (encodedMessage.length > 0) { + PacketType packetType = firstPacket ? PacketType.PDM : PacketType.CON; + OmnipodPacket packet = new OmnipodPacket(packetAddress, packetType, podState.getPacketNumber(), encodedMessage); + byte[] encodedMessageInPacket = packet.getEncodedMessage(); + + // getting the data remaining to be sent + encodedMessage = ByteUtil.substring(encodedMessage, encodedMessageInPacket.length, encodedMessage.length - encodedMessageInPacket.length); + firstPacket = false; + + try { + // We actually ignore previous (ack) responses if it was not last packet to send + response = exchangePackets(podState, packet); + } catch (Exception ex) { + OmnipodException newException; + if (ex instanceof OmnipodException) { + newException = (OmnipodException) ex; + } else { + newException = new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex); + } + + boolean lastPacket = encodedMessage.length == 0; + + // If this is not the last packet, the message wasn't fully sent, + // so it's impossible for the pod to have received the message + newException.setCertainFailure(!lastPacket); + + if (isLoggingEnabled()) { + LOG.debug("Caught exception in transportMessages. Set certainFailure to {} because encodedMessage.length={}", newException.isCertainFailure(), encodedMessage.length); + } + + throw newException; + } + } + + if (response.getPacketType() == PacketType.ACK) { + podState.increasePacketNumber(1); + throw new IllegalPacketTypeException(null, PacketType.ACK); + } + + OmnipodMessage receivedMessage = null; + byte[] receivedMessageData = response.getEncodedMessage(); + while (receivedMessage == null) { + try { + receivedMessage = OmnipodMessage.decodeMessage(receivedMessageData); + } catch (NotEnoughDataException ex) { + // Message is (probably) not complete yet + OmnipodPacket ackForCon = createAckPacket(podState, packetAddress, ackAddressOverride); + + try { + OmnipodPacket conPacket = exchangePackets(podState, ackForCon, 3, 40); + if (conPacket.getPacketType() != PacketType.CON) { + throw new IllegalPacketTypeException(PacketType.CON, conPacket.getPacketType()); + } + receivedMessageData = ByteUtil.concat(receivedMessageData, conPacket.getEncodedMessage()); + } catch (OmnipodException ex2) { + throw ex2; + } catch (Exception ex2) { + throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex2); + } + + } + } + + podState.increaseMessageNumber(2); + + ackUntilQuiet(podState, packetAddress, ackAddressOverride); + + List messageBlocks = receivedMessage.getMessageBlocks(); + + if (messageBlocks.size() == 0) { + throw new NotEnoughDataException(receivedMessageData); + } else if (messageBlocks.size() > 1) { + // BS: don't expect this to happen + if (isLoggingEnabled()) { + LOG.error("Received more than one message block: {}", messageBlocks.toString()); + } + } + + return messageBlocks.get(0); + } + + private OmnipodPacket createAckPacket(PodState podState, Integer packetAddress, Integer messageAddress) { + int pktAddress = podState.getAddress(); + int msgAddress = podState.getAddress(); + if (packetAddress != null) { + pktAddress = packetAddress; + } + if (messageAddress != null) { + msgAddress = messageAddress; + } + return new OmnipodPacket(pktAddress, PacketType.ACK, podState.getPacketNumber(), ByteUtil.getBytesFromInt(msgAddress)); + } + + private void ackUntilQuiet(PodState podState, Integer packetAddress, Integer messageAddress) { + OmnipodPacket ack = createAckPacket(podState, packetAddress, messageAddress); + boolean quiet = false; + while (!quiet) try { + sendAndListen(ack, 300, 1, 0, 40, OmnipodPacket.class); + } catch (RileyLinkCommunicationException ex) { + if (RileyLinkBLEError.Timeout.equals(ex.getErrorCode())) { + quiet = true; + } else { + if (isLoggingEnabled()) { + LOG.debug("Ignoring exception in ackUntilQuiet", ex); + } + } + } catch (OmnipodException ex) { + if (isLoggingEnabled()) { + LOG.debug("Ignoring exception in ackUntilQuiet", ex); + } + } catch (Exception ex) { + throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex); + } + + podState.increasePacketNumber(1); + } + + private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet) { + return exchangePackets(podState, packet, 0, 333, 9000, 127); + } + + private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int preambleExtensionMilliseconds) { + return exchangePackets(podState, packet, repeatCount, 333, 9000, preambleExtensionMilliseconds); + } + + private OmnipodPacket exchangePackets(PodState podState, OmnipodPacket packet, int repeatCount, int responseTimeoutMilliseconds, int exchangeTimeoutMilliseconds, int preambleExtensionMilliseconds) { + long timeoutTime = System.currentTimeMillis() + exchangeTimeoutMilliseconds; + + while (System.currentTimeMillis() < timeoutTime) { + OmnipodPacket response = null; + try { + response = sendAndListen(packet, responseTimeoutMilliseconds, repeatCount, 9, preambleExtensionMilliseconds, OmnipodPacket.class); + } catch (RileyLinkCommunicationException | OmnipodException ex) { + if (isLoggingEnabled()) { + LOG.debug("Ignoring exception in exchangePackets", ex); + } + } catch (Exception ex) { + throw new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, ex); + } + if (response == null || !response.isValid()) { + continue; + } + if (response.getAddress() != packet.getAddress()) { + continue; + } + if (response.getSequenceNumber() != ((podState.getPacketNumber() + 1) & 0b11111)) { + continue; + } + + podState.increasePacketNumber(2); + return response; + } + throw new CommunicationException(CommunicationException.Type.TIMEOUT); + } + + private boolean isLoggingEnabled() { + return L.isEnabled(L.PUMPCOMM); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java new file mode 100644 index 00000000000..3acaa7e712a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java @@ -0,0 +1,682 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.EnumSet; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AcknowledgeAlertsAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.AssignAddressAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.CancelDeliveryAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigurePodAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.DeactivatePodAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetPodInfoAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.GetStatusAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.InsertCannulaAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.PrimeAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetTempBasalAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.CancelDeliveryCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommunicationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalDeliveryStatusException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NonceOutOfSyncException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.utils.SP; +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.Single; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.SingleSubject; + +public class OmnipodManager { + private static final int ACTION_VERIFICATION_TRIES = 3; + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + protected final OmnipodCommunicationService communicationService; + private final PodStateChangedHandler podStateChangedHandler; + protected PodSessionState podState; + + private ActiveBolusData activeBolusData; + private final Object bolusDataMutex = new Object(); + + public OmnipodManager(OmnipodCommunicationService communicationService, PodSessionState podState, + PodStateChangedHandler podStateChangedHandler) { + if (communicationService == null) { + throw new IllegalArgumentException("Communication service cannot be null"); + } + this.communicationService = communicationService; + if (podState != null) { + podState.setStateChangedHandler(podStateChangedHandler); + } + this.podState = podState; + this.podStateChangedHandler = podStateChangedHandler; + } + + public OmnipodManager(OmnipodCommunicationService communicationService, PodSessionState podState) { + this(communicationService, podState, null); + } + + public synchronized Single pairAndPrime() { + logStartingCommandExecution("pairAndPrime"); + + try { + if (podState == null) { + podState = communicationService.executeAction( + new AssignAddressAction(podStateChangedHandler)); + } else if (SetupProgress.PRIMING.isBefore(podState.getSetupProgress())) { + throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress()); + } + + if (SetupProgress.ADDRESS_ASSIGNED.equals(podState.getSetupProgress())) { + communicationService.executeAction(new ConfigurePodAction(podState)); + } + + communicationService.executeAction(new PrimeAction(new PrimeService(), podState)); + } finally { + logCommandExecutionFinished("pairAndPrime"); + } + + long delayInSeconds = calculateBolusDuration(OmnipodConst.POD_PRIME_BOLUS_UNITS, OmnipodConst.POD_PRIMING_DELIVERY_RATE).getStandardSeconds(); + + return Single.timer(delayInSeconds, TimeUnit.SECONDS) // + .map(o -> verifySetupAction(statusResponse -> + PrimeAction.updatePrimingStatus(podState, statusResponse), SetupProgress.PRIMING_FINISHED)) // + .observeOn(Schedulers.io()); + } + + public synchronized Single insertCannula(BasalSchedule basalSchedule) { + if (podState == null || podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) { + throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, podState == null ? null : podState.getSetupProgress()); + } else if (podState.getSetupProgress().isAfter(SetupProgress.CANNULA_INSERTING)) { + throw new IllegalSetupProgressException(SetupProgress.CANNULA_INSERTING, podState.getSetupProgress()); + } + + logStartingCommandExecution("insertCannula [basalSchedule=" + basalSchedule + "]"); + + try { + communicationService.executeAction(new InsertCannulaAction(new InsertCannulaService(), podState, basalSchedule)); + } finally { + logCommandExecutionFinished("insertCannula"); + } + + long delayInSeconds = calculateBolusDuration(OmnipodConst.POD_CANNULA_INSERTION_BOLUS_UNITS, OmnipodConst.POD_CANNULA_INSERTION_DELIVERY_RATE).getStandardSeconds(); + + return Single.timer(delayInSeconds, TimeUnit.SECONDS) // + .map(o -> verifySetupAction(statusResponse -> + InsertCannulaAction.updateCannulaInsertionStatus(podState, statusResponse), SetupProgress.COMPLETED)) // + .observeOn(Schedulers.io()); + } + + public synchronized StatusResponse getPodStatus() { + if (podState == null) { + throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, null); + } + + logStartingCommandExecution("getPodStatus"); + + try { + return communicationService.executeAction(new GetStatusAction(podState)); + } finally { + logCommandExecutionFinished("getPodStatus"); + } + } + + public synchronized PodInfoResponse getPodInfo(PodInfoType podInfoType) { + assertReadyForDelivery(); + + logStartingCommandExecution("getPodInfo"); + + try { + return communicationService.executeAction(new GetPodInfoAction(podState, podInfoType)); + } finally { + logCommandExecutionFinished("getPodInfo"); + } + } + + public synchronized StatusResponse acknowledgeAlerts() { + assertReadyForDelivery(); + + logStartingCommandExecution("acknowledgeAlerts"); + + try { + return executeAndVerify(() -> communicationService.executeAction(new AcknowledgeAlertsAction(podState, podState.getActiveAlerts()))); + } finally { + logCommandExecutionFinished("acknowledgeAlerts"); + } + } + + // CAUTION: cancels all delivery + // CAUTION: suspends and then resumes delivery. An OmnipodException[certainFailure=false] indicates that the pod is or might be suspended + public synchronized StatusResponse setBasalSchedule(BasalSchedule schedule, boolean acknowledgementBeep) { + assertReadyForDelivery(); + + logStartingCommandExecution("setBasalSchedule [basalSchedule=" + schedule + ", acknowledgementBeep=" + acknowledgementBeep + "]"); + + try { + cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeep); + } catch (Exception ex) { + logCommandExecutionFinished("setBasalSchedule"); + throw ex; + } + + try { + try { + return executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podState, schedule, + false, podState.getScheduleOffset(), acknowledgementBeep))); + } catch (OmnipodException ex) { + // Treat all exceptions as uncertain failures, because all delivery has been suspended here. + // Setting this to an uncertain failure will enable for the user to get an appropriate warning + ex.setCertainFailure(false); + throw ex; + } + } finally { + logCommandExecutionFinished("setBasalSchedule"); + } + } + + // CAUTION: cancels temp basal and then sets new temp basal. An OmnipodException[certainFailure=false] indicates that the pod might have cancelled the previous temp basal, but did not set a new temp basal + public synchronized StatusResponse setTemporaryBasal(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep) { + assertReadyForDelivery(); + + logStartingCommandExecution("setTemporaryBasal [rate=" + rate + ", duration=" + duration + ", acknowledgementBeep=" + acknowledgementBeep + ", completionBeep=" + completionBeep + "]"); + + try { + cancelDelivery(EnumSet.of(DeliveryType.TEMP_BASAL), acknowledgementBeep); + } catch (Exception ex) { + logCommandExecutionFinished("setTemporaryBasal"); + throw ex; + } + + try { + return executeAndVerify(() -> communicationService.executeAction(new SetTempBasalAction( + podState, rate, duration, + acknowledgementBeep, completionBeep))); + } catch (OmnipodException ex) { + // Treat all exceptions as uncertain failures, because all delivery has been suspended here. + // Setting this to an uncertain failure will enable for the user to get an appropriate warning + ex.setCertainFailure(false); + throw ex; + } finally { + logCommandExecutionFinished("setTemporaryBasal"); + } + } + + public synchronized void cancelTemporaryBasal(boolean acknowledgementBeep) { + cancelDelivery(EnumSet.of(DeliveryType.TEMP_BASAL), acknowledgementBeep); + } + + private synchronized StatusResponse cancelDelivery(EnumSet deliveryTypes, boolean acknowledgementBeep) { + assertReadyForDelivery(); + + logStartingCommandExecution("cancelDelivery [deliveryTypes=" + deliveryTypes + ", acknowledgementBeep=" + acknowledgementBeep + "]"); + + try { + return executeAndVerify(() -> { + StatusResponse statusResponse = communicationService.executeAction(new CancelDeliveryAction(podState, deliveryTypes, acknowledgementBeep)); + if (isLoggingEnabled()) { + LOG.info("Status response after cancel delivery[types={}]: {}", deliveryTypes.toString(), statusResponse.toString()); + } + return statusResponse; + }); + } finally { + logCommandExecutionFinished("cancelDelivery"); + } + } + + // Returns a SingleSubject that returns when the bolus has finished. + // When a bolus is cancelled, it will return after cancellation and report the estimated units delivered + // Only throws OmnipodException[certainFailure=false] + public synchronized BolusCommandResult bolus(Double units, boolean acknowledgementBeep, boolean completionBeep, BolusProgressIndicationConsumer progressIndicationConsumer) { + assertReadyForDelivery(); + + logStartingCommandExecution("bolus [units=" + units + ", acknowledgementBeep=" + acknowledgementBeep + ", completionBeep=" + completionBeep + "]"); + + CommandDeliveryStatus commandDeliveryStatus = CommandDeliveryStatus.SUCCESS; + + try { + executeAndVerify(() -> communicationService.executeAction(new BolusAction(podState, units, acknowledgementBeep, completionBeep))); + } catch (OmnipodException ex) { + if (ex.isCertainFailure()) { + throw ex; + } + + // Catch uncertain exceptions as we still want to report bolus progress indication + if (isLoggingEnabled()) { + LOG.error("Caught exception[certainFailure=false] in bolus", ex); + } + commandDeliveryStatus = CommandDeliveryStatus.UNCERTAIN_FAILURE; + } finally { + logCommandExecutionFinished("bolus"); + } + + DateTime startDate = DateTime.now().minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); + + CompositeDisposable disposables = new CompositeDisposable(); + Duration bolusDuration = calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE); + Duration estimatedRemainingBolusDuration = bolusDuration.minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); + + if (progressIndicationConsumer != null) { + int numberOfProgressReports = Math.max(20, Math.min(100, (int) Math.ceil(units) * 10)); + long progressReportInterval = estimatedRemainingBolusDuration.getMillis() / numberOfProgressReports; + + disposables.add(Flowable.intervalRange(0, numberOfProgressReports + 1, 0, progressReportInterval, TimeUnit.MILLISECONDS) // + .observeOn(Schedulers.io()) // + .subscribe(count -> { + int percentage = (int) ((double) count / numberOfProgressReports * 100); + double estimatedUnitsDelivered = activeBolusData == null ? 0 : activeBolusData.estimateUnitsDelivered(); + progressIndicationConsumer.accept(estimatedUnitsDelivered, percentage); + })); + } + + SingleSubject bolusCompletionSubject = SingleSubject.create(); + + synchronized (bolusDataMutex) { + activeBolusData = new ActiveBolusData(units, startDate, bolusCompletionSubject, disposables); + } + + disposables.add(Completable.complete() // + .delay(estimatedRemainingBolusDuration.getMillis() + 250, TimeUnit.MILLISECONDS) // + .observeOn(Schedulers.io()) // + .doOnComplete(() -> { + synchronized (bolusDataMutex) { + double unitsNotDelivered = 0.0d; + + for (int i = 0; i < ACTION_VERIFICATION_TRIES; i++) { + try { + // Retrieve a status response in order to update the pod state + StatusResponse statusResponse = getPodStatus(); + if (statusResponse.getDeliveryStatus().isBolusing()) { + throw new IllegalDeliveryStatusException(DeliveryStatus.NORMAL, statusResponse.getDeliveryStatus()); + } else { + break; + } + } catch (PodFaultException ex) { + // Substract units not delivered in case of a Pod failure + unitsNotDelivered = ex.getFaultEvent().getInsulinNotDelivered(); + + if (isLoggingEnabled()) { + LOG.debug("Caught PodFaultException in bolus completion verification", ex); + } + break; + } catch (Exception ex) { + if (isLoggingEnabled()) { + LOG.debug("Ignoring exception in bolus completion verification", ex); + } + } + } + + if (hasActiveBolus()) { + activeBolusData.bolusCompletionSubject.onSuccess(new BolusDeliveryResult(units - unitsNotDelivered)); + activeBolusData = null; + } + } + }) + .subscribe()); + + return new BolusCommandResult(commandDeliveryStatus, bolusCompletionSubject); + } + + public synchronized void cancelBolus(boolean acknowledgementBeep) { + assertReadyForDelivery(); + + synchronized (bolusDataMutex) { + if (activeBolusData == null) { + throw new IllegalDeliveryStatusException(DeliveryStatus.BOLUS_IN_PROGRESS, podState.getLastDeliveryStatus()); + } + + logStartingCommandExecution("cancelBolus [acknowledgementBeep=" + acknowledgementBeep + "]"); + + try { + StatusResponse statusResponse = cancelDelivery(EnumSet.of(DeliveryType.BOLUS), acknowledgementBeep); + discardActiveBolusData(statusResponse.getInsulinNotDelivered()); + } catch (PodFaultException ex) { + discardActiveBolusData(ex.getFaultEvent().getInsulinNotDelivered()); + throw ex; + } finally { + logCommandExecutionFinished("cancelBolus"); + } + } + } + + private void discardActiveBolusData(double unitsNotDelivered) { + synchronized (bolusDataMutex) { + activeBolusData.getDisposables().dispose(); + activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(activeBolusData.getUnits() - unitsNotDelivered)); + activeBolusData = null; + } + } + + public synchronized void suspendDelivery(boolean acknowledgementBeep) { + cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeep); + } + + // Same as setting basal schedule, but without suspending delivery first + public synchronized StatusResponse resumeDelivery(boolean acknowledgementBeep) { + assertReadyForDelivery(); + logStartingCommandExecution("resumeDelivery"); + + try { + return executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podState, podState.getBasalSchedule(), + false, podState.getScheduleOffset(), acknowledgementBeep))); + } finally { + logCommandExecutionFinished("resumeDelivery"); + } + } + + // CAUTION: cancels all delivery + // CAUTION: suspends and then resumes delivery. An OmnipodException[certainFailure=false] indicates that the pod is or might be suspended + public synchronized void setTime(boolean acknowledgementBeeps) { + assertReadyForDelivery(); + + logStartingCommandExecution("setTime [acknowledgementBeeps=" + acknowledgementBeeps + "]"); + + try { + cancelDelivery(EnumSet.allOf(DeliveryType.class), acknowledgementBeeps); + } catch (Exception ex) { + logCommandExecutionFinished("setTime"); + throw ex; + } + + DateTimeZone oldTimeZone = podState.getTimeZone(); + + try { + // Joda seems to cache the default time zone, so we use the JVM's + DateTimeZone.setDefault(DateTimeZone.forTimeZone(TimeZone.getDefault())); + podState.setTimeZone(DateTimeZone.getDefault()); + + setBasalSchedule(podState.getBasalSchedule(), acknowledgementBeeps); + } catch (OmnipodException ex) { + // Treat all exceptions as uncertain failures, because all delivery has been suspended here. + // Setting this to an uncertain failure will enable for the user to get an appropriate warning + podState.setTimeZone(oldTimeZone); + ex.setCertainFailure(false); + throw ex; + } finally { + logCommandExecutionFinished("setTime"); + } + } + + public synchronized void deactivatePod() { + if (podState == null) { + throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, null); + } + + logStartingCommandExecution("deactivatePod"); + + // Try to get pulse log for diagnostics + // FIXME replace by storing to file + if (isLoggingEnabled()) { + try { + PodInfoResponse podInfoResponse = communicationService.executeAction(new GetPodInfoAction(podState, PodInfoType.RECENT_PULSE_LOG)); + PodInfoRecentPulseLog pulseLogInfo = podInfoResponse.getPodInfo(); + LOG.info("Retrieved pulse log from the pod: {}", pulseLogInfo.toString()); + } catch (Exception ex) { + LOG.warn("Failed to retrieve pulse log from the pod", ex); + } + } + + try { + // Always send acknowledgement beeps here. Matches the PDM's behavior + communicationService.executeAction(new DeactivatePodAction(podState, true)); + } catch (PodFaultException ex) { + if (isLoggingEnabled()) { + LOG.info("Ignoring PodFaultException in deactivatePod", ex); + } + } finally { + logCommandExecutionFinished("deactivatePod"); + } + + resetPodState(false); + } + + public void resetPodState(boolean forcedByUser) { + if (isLoggingEnabled()) { + LOG.warn("resetPodState has been called. forcedByUser={}", forcedByUser); + } + podState = null; + SP.remove(OmnipodConst.Prefs.PodState); + } + + public OmnipodCommunicationService getCommunicationService() { + return communicationService; + } + + public DateTime getTime() { + return podState.getTime(); + } + + public boolean isReadyForDelivery() { + return podState != null && podState.getSetupProgress() == SetupProgress.COMPLETED; + } + + public boolean hasActiveBolus() { + synchronized (bolusDataMutex) { + return activeBolusData != null; + } + } + + // FIXME this is dirty, we should not expose the original pod state + public PodSessionState getPodState() { + return this.podState; + } + + public String getPodStateAsString() { + return podState == null ? "null" : podState.toString(); + } + + // Only works for commands with nonce resyncable message blocks + // FIXME method is too big, needs refactoring + private StatusResponse executeAndVerify(VerifiableAction runnable) { + try { + return runnable.run(); + } catch (Exception originalException) { + if (isCertainFailure(originalException)) { + throw originalException; + } else { + if (isLoggingEnabled()) { + LOG.warn("Caught exception in executeAndVerify. Verifying command by using cancel none command to verify nonce", originalException); + } + + try { + logStartingCommandExecution("verifyCommand"); + StatusResponse statusResponse = communicationService.sendCommand(StatusResponse.class, podState, + new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.NO_BEEP, DeliveryType.NONE), false); + if (isLoggingEnabled()) { + LOG.info("Command status resolved to SUCCESS. Status response after cancelDelivery[types=DeliveryType.NONE]: {}", statusResponse); + } + + return statusResponse; + } catch (NonceOutOfSyncException verificationException) { + if (isLoggingEnabled()) { + LOG.error("Command resolved to FAILURE (CERTAIN_FAILURE)", verificationException); + } + if (originalException instanceof OmnipodException) { + ((OmnipodException) originalException).setCertainFailure(true); + throw originalException; + } else { + OmnipodException newException = new CommunicationException(CommunicationException.Type.UNEXPECTED_EXCEPTION, originalException); + newException.setCertainFailure(true); + throw newException; + } + } catch (Exception verificationException) { + if (isLoggingEnabled()) { + LOG.error("Command unresolved (UNCERTAIN_FAILURE)", verificationException); + } + throw originalException; + } finally { + logCommandExecutionFinished("verifyCommand"); + } + } + } + } + + private void assertReadyForDelivery() { + if (!isReadyForDelivery()) { + throw new IllegalSetupProgressException(SetupProgress.COMPLETED, podState == null ? null : podState.getSetupProgress()); + } + } + + private SetupActionResult verifySetupAction(StatusResponseConsumer setupActionResponseHandler, SetupProgress expectedSetupProgress) { + SetupActionResult result = null; + for (int i = 0; ACTION_VERIFICATION_TRIES > i; i++) { + try { + StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState)); + setupActionResponseHandler.accept(delayedStatusResponse); + + if (podState.getSetupProgress().equals(expectedSetupProgress)) { + result = new SetupActionResult(SetupActionResult.ResultType.SUCCESS); + break; + } else { + result = new SetupActionResult(SetupActionResult.ResultType.FAILURE) // + .setupProgress(podState.getSetupProgress()); + break; + } + } catch (Exception ex) { + result = new SetupActionResult(SetupActionResult.ResultType.VERIFICATION_FAILURE) // + .exception(ex); + } + } + return result; + } + + private void logStartingCommandExecution(String action) { + if (isLoggingEnabled()) { + LOG.debug("Starting command execution for action: " + action); + } + } + + private void logCommandExecutionFinished(String action) { + if (isLoggingEnabled()) { + LOG.debug("Command execution finished for action: " + action); + } + } + + private boolean isLoggingEnabled() { + return L.isEnabled(L.PUMP); + } + + private static Duration calculateBolusDuration(double units, double deliveryRate) { + return Duration.standardSeconds((long) Math.ceil(units / deliveryRate)); + } + + public static Duration calculateBolusDuration(double units) { + return calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE); + } + + public static boolean isCertainFailure(Exception ex) { + return ex instanceof OmnipodException && ((OmnipodException) ex).isCertainFailure(); + } + + public static class BolusCommandResult { + private final CommandDeliveryStatus commandDeliveryStatus; + private final SingleSubject deliveryResultSubject; + + public BolusCommandResult(CommandDeliveryStatus commandDeliveryStatus, SingleSubject deliveryResultSubject) { + this.commandDeliveryStatus = commandDeliveryStatus; + this.deliveryResultSubject = deliveryResultSubject; + } + + public CommandDeliveryStatus getCommandDeliveryStatus() { + return commandDeliveryStatus; + } + + public SingleSubject getDeliveryResultSubject() { + return deliveryResultSubject; + } + } + + public static class BolusDeliveryResult { + private final double unitsDelivered; + + public BolusDeliveryResult(double unitsDelivered) { + this.unitsDelivered = unitsDelivered; + } + + public double getUnitsDelivered() { + return unitsDelivered; + } + } + + public enum CommandDeliveryStatus { + SUCCESS, + CERTAIN_FAILURE, + UNCERTAIN_FAILURE + } + + // TODO replace with Consumer when our min API level >= 24 + @FunctionalInterface + private interface StatusResponseConsumer { + void accept(StatusResponse statusResponse); + } + + private static class ActiveBolusData { + private final double units; + private volatile DateTime startDate; + private volatile SingleSubject bolusCompletionSubject; + private volatile CompositeDisposable disposables; + + private ActiveBolusData(double units, DateTime startDate, SingleSubject bolusCompletionSubject, CompositeDisposable disposables) { + this.units = units; + this.startDate = startDate; + this.bolusCompletionSubject = bolusCompletionSubject; + this.disposables = disposables; + } + + public double getUnits() { + return units; + } + + public DateTime getStartDate() { + return startDate; + } + + public CompositeDisposable getDisposables() { + return disposables; + } + + public SingleSubject getBolusCompletionSubject() { + return bolusCompletionSubject; + } + + public double estimateUnitsDelivered() { + long elapsedMillis = new Duration(startDate, DateTime.now()).getMillis(); + long totalDurationMillis = (long) (units / OmnipodConst.POD_BOLUS_DELIVERY_RATE * 1000); + double factor = (double) elapsedMillis / totalDurationMillis; + double estimatedUnits = Math.min(1D, factor) * units; + + int roundingDivisor = (int) (1 / OmnipodConst.POD_PULSE_SIZE); + return (double) Math.round(estimatedUnits * roundingDivisor) / roundingDivisor; + } + } + + // Could be replaced with Supplier when min API level >= 24 + @FunctionalInterface + private interface VerifiableAction { + StatusResponse run(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/SetupActionResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/SetupActionResult.java new file mode 100644 index 00000000000..b4c881bfcbe --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/SetupActionResult.java @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; + +public class SetupActionResult { + private final ResultType resultType; + private String message; + private Exception exception; + private SetupProgress setupProgress; + + public SetupActionResult(ResultType resultType) { + this.resultType = resultType; + } + + public SetupActionResult message(String message) { + this.message = message; + return this; + } + + public SetupActionResult exception(Exception ex) { + exception = ex; + return this; + } + + public SetupActionResult setupProgress(SetupProgress setupProgress) { + this.setupProgress = setupProgress; + return this; + } + + public ResultType getResultType() { + return resultType; + } + + public String getMessage() { + return message; + } + + public Exception getException() { + return exception; + } + + public SetupProgress getSetupProgress() { + return setupProgress; + } + + public enum ResultType { + SUCCESS(true), + VERIFICATION_FAILURE(false), + FAILURE(false); + + private final boolean success; + + ResultType(boolean success) { + this.success = success; + } + + public boolean isSuccess() { + return success; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AcknowledgeAlertsAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AcknowledgeAlertsAction.java new file mode 100644 index 00000000000..70cea8da0a6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AcknowledgeAlertsAction.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import java.util.Collections; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AcknowledgeAlertsCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class AcknowledgeAlertsAction implements OmnipodAction { + private final PodSessionState podState; + private final AlertSet alerts; + + public AcknowledgeAlertsAction(PodSessionState podState, AlertSet alerts) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (alerts == null) { + throw new ActionInitializationException("Alert set can not be null"); + } else if (alerts.size() == 0) { + throw new ActionInitializationException("Alert set can not be empty"); + } + this.podState = podState; + this.alerts = alerts; + } + + public AcknowledgeAlertsAction(PodSessionState podState, AlertSlot alertSlot) { + this(podState, new AlertSet(Collections.singletonList(alertSlot))); + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + return communicationService.sendCommand(StatusResponse.class, podState, + new AcknowledgeAlertsCommand(podState.getCurrentNonce(), alerts)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AssignAddressAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AssignAddressAction.java new file mode 100644 index 00000000000..09b096f78d3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/AssignAddressAction.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.joda.time.DateTimeZone; + +import java.util.Collections; +import java.util.Random; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.AssignAddressCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSetupState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodStateChangedHandler; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class AssignAddressAction implements OmnipodAction { + private final int address; + private final PodStateChangedHandler podStateChangedHandler; + + public AssignAddressAction(PodStateChangedHandler podStateChangedHandler) { + this.address = generateRandomAddress(); + this.podStateChangedHandler = podStateChangedHandler; + } + + private static int generateRandomAddress() { + return 0x1f000000 | (new Random().nextInt() & 0x000fffff); + } + + @Override + public PodSessionState execute(OmnipodCommunicationService communicationService) { + PodSetupState setupState = new PodSetupState(address, 0x00, 0x00); + + AssignAddressCommand assignAddress = new AssignAddressCommand(setupState.getAddress()); + OmnipodMessage assignAddressMessage = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS, + Collections.singletonList(assignAddress), setupState.getMessageNumber()); + + VersionResponse assignAddressResponse = communicationService.exchangeMessages(VersionResponse.class, setupState, assignAddressMessage, + OmnipodConst.DEFAULT_ADDRESS, setupState.getAddress()); + + DateTimeZone timeZone = DateTimeZone.getDefault(); + + PodSessionState podState = new PodSessionState(timeZone, address, assignAddressResponse.getPiVersion(), + assignAddressResponse.getPmVersion(), assignAddressResponse.getLot(), assignAddressResponse.getTid(), + setupState.getPacketNumber(), 0x00); // At this point, for an unknown reason, the pod starts counting messages from 0 again + + podState.setStateChangedHandler(podStateChangedHandler); + return podState; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/BolusAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/BolusAction.java new file mode 100644 index 00000000000..49bff8bff09 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/BolusAction.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.joda.time.Duration; + +import java.util.Arrays; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BolusExtraCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class BolusAction implements OmnipodAction { + private final PodSessionState podState; + private final double units; + private final Duration timeBetweenPulses; + private final boolean acknowledgementBeep; + private final boolean completionBeep; + + public BolusAction(PodSessionState podState, double units, Duration timeBetweenPulses, + boolean acknowledgementBeep, boolean completionBeep) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (timeBetweenPulses == null) { + throw new ActionInitializationException("Time between pulses cannot be null"); + } + this.podState = podState; + this.units = units; + this.timeBetweenPulses = timeBetweenPulses; + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + } + + public BolusAction(PodSessionState podState, double units, boolean acknowledgementBeep, boolean completionBeep) { + this(podState, units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep); + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + BolusDeliverySchedule bolusDeliverySchedule = new BolusDeliverySchedule(units, timeBetweenPulses); + SetInsulinScheduleCommand setInsulinScheduleCommand = new SetInsulinScheduleCommand( + podState.getCurrentNonce(), bolusDeliverySchedule); + BolusExtraCommand bolusExtraCommand = new BolusExtraCommand(units, timeBetweenPulses, + acknowledgementBeep, completionBeep); + OmnipodMessage primeBolusMessage = new OmnipodMessage(podState.getAddress(), + Arrays.asList(setInsulinScheduleCommand, bolusExtraCommand), podState.getMessageNumber()); + return communicationService.exchangeMessages(StatusResponse.class, podState, primeBolusMessage); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java new file mode 100644 index 00000000000..9dc84d18abf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/CancelDeliveryAction.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.CancelDeliveryCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class CancelDeliveryAction implements OmnipodAction { + private final PodSessionState podState; + private final EnumSet deliveryTypes; + private final boolean acknowledgementBeep; + + public CancelDeliveryAction(PodSessionState podState, EnumSet deliveryTypes, + boolean acknowledgementBeep) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (deliveryTypes == null) { + throw new ActionInitializationException("Delivery types cannot be null"); + } + this.podState = podState; + this.deliveryTypes = deliveryTypes; + this.acknowledgementBeep = acknowledgementBeep; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + List messageBlocks = new ArrayList<>(); + + if (acknowledgementBeep && deliveryTypes.size() > 1) { + // Workaround for strange beep behaviour when cancelling multiple delivery types + List deliveryTypeList = new ArrayList<>(deliveryTypes); + + EnumSet deliveryTypeWithBeep = EnumSet.of(deliveryTypeList.remove(deliveryTypeList.size() - 1)); + EnumSet deliveryTypesWithoutBeep = EnumSet.copyOf(deliveryTypeList); + + messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.NO_BEEP, deliveryTypesWithoutBeep)); + messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(), BeepType.BEEP, deliveryTypeWithBeep)); + } else { + messageBlocks.add(new CancelDeliveryCommand(podState.getCurrentNonce(), + acknowledgementBeep && deliveryTypes.size() == 1 ? BeepType.BEEP : BeepType.NO_BEEP, deliveryTypes)); + } + + return communicationService.exchangeMessages(StatusResponse.class, podState, + new OmnipodMessage(podState.getAddress(), messageBlocks, podState.getMessageNumber())); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigureAlertsAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigureAlertsAction.java new file mode 100644 index 00000000000..b89588b72bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigureAlertsAction.java @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigureAlertsCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class ConfigureAlertsAction implements OmnipodAction { + private final PodSessionState podState; + private final List alertConfigurations; + + public ConfigureAlertsAction(PodSessionState podState, List alertConfigurations) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (alertConfigurations == null) { + throw new ActionInitializationException("Alert configurations cannot be null"); + } + this.podState = podState; + this.alertConfigurations = alertConfigurations; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + ConfigureAlertsCommand configureAlertsCommand = new ConfigureAlertsCommand(podState.getCurrentNonce(), alertConfigurations); + StatusResponse statusResponse = communicationService.sendCommand(StatusResponse.class, podState, configureAlertsCommand); + for (AlertConfiguration alertConfiguration : alertConfigurations) { + podState.putConfiguredAlert(alertConfiguration.getAlertSlot(), alertConfiguration.getAlertType()); + } + return statusResponse; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigurePodAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigurePodAction.java new file mode 100644 index 00000000000..8b60c92e6bc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/ConfigurePodAction.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.joda.time.DateTime; + +import java.util.Collections; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.ConfigurePodCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPacketTypeException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPodProgressException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class ConfigurePodAction implements OmnipodAction { + private final PodSessionState podState; + + public ConfigurePodAction(PodSessionState podState) { + this.podState = podState; + } + + @Override + public VersionResponse execute(OmnipodCommunicationService communicationService) { + if (!podState.getSetupProgress().equals(SetupProgress.ADDRESS_ASSIGNED)) { + throw new IllegalSetupProgressException(SetupProgress.ADDRESS_ASSIGNED, podState.getSetupProgress()); + } + DateTime activationDate = DateTime.now(podState.getTimeZone()); + + ConfigurePodCommand configurePodCommand = new ConfigurePodCommand(podState.getAddress(), activationDate, + podState.getLot(), podState.getTid()); + OmnipodMessage message = new OmnipodMessage(OmnipodConst.DEFAULT_ADDRESS, + Collections.singletonList(configurePodCommand), podState.getMessageNumber()); + VersionResponse configurePodResponse; + try { + configurePodResponse = communicationService.exchangeMessages(VersionResponse.class, podState, + message, OmnipodConst.DEFAULT_ADDRESS, podState.getAddress()); + } catch (IllegalPacketTypeException ex) { + if (PacketType.ACK.equals(ex.getActual())) { + // Pod is already configured + podState.setSetupProgress(SetupProgress.POD_CONFIGURED); + return null; + } + throw ex; + } + + if (configurePodResponse.getPodProgressStatus() != PodProgressStatus.PAIRING_SUCCESS) { + throw new IllegalPodProgressException(PodProgressStatus.PAIRING_SUCCESS, configurePodResponse.getPodProgressStatus()); + } + + podState.setSetupProgress(SetupProgress.POD_CONFIGURED); + + return configurePodResponse; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/DeactivatePodAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/DeactivatePodAction.java new file mode 100644 index 00000000000..e88fafdb6cd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/DeactivatePodAction.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import java.util.EnumSet; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.DeactivatePodCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException; + +public class DeactivatePodAction implements OmnipodAction { + private final PodSessionState podState; + private final boolean acknowledgementBeep; + + public DeactivatePodAction(PodSessionState podState, boolean acknowledgementBeep) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + this.podState = podState; + this.acknowledgementBeep = acknowledgementBeep; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + if (!podState.isSuspended() && !podState.hasFaultEvent()) { + try { + communicationService.executeAction(new CancelDeliveryAction(podState, + EnumSet.allOf(DeliveryType.class), acknowledgementBeep)); + } catch(PodFaultException ex) { + // Ignore + } + } + + return communicationService.sendCommand(StatusResponse.class, podState, new DeactivatePodCommand(podState.getCurrentNonce())); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetPodInfoAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetPodInfoAction.java new file mode 100644 index 00000000000..a120bc02d95 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetPodInfoAction.java @@ -0,0 +1,29 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class GetPodInfoAction implements OmnipodAction { + private final PodSessionState podState; + private final PodInfoType podInfoType; + + public GetPodInfoAction(PodSessionState podState, PodInfoType podInfoType) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (podInfoType == null) { + throw new ActionInitializationException("Pod info type cannot be null"); + } + this.podState = podState; + this.podInfoType = podInfoType; + } + + @Override + public PodInfoResponse execute(OmnipodCommunicationService communicationService) { + return communicationService.sendCommand(PodInfoResponse.class, podState, new GetStatusCommand(podInfoType)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetStatusAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetStatusAction.java new file mode 100644 index 00000000000..a76f7cd3fa8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/GetStatusAction.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class GetStatusAction implements OmnipodAction { + private final PodSessionState podState; + + public GetStatusAction(PodSessionState podState) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + this.podState = podState; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + return communicationService.sendCommand(StatusResponse.class, podState, new GetStatusCommand(PodInfoType.NORMAL)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/InsertCannulaAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/InsertCannulaAction.java new file mode 100644 index 00000000000..f2cc3328d32 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/InsertCannulaAction.java @@ -0,0 +1,75 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.InsertCannulaService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException; + +public class InsertCannulaAction implements OmnipodAction { + private static final Logger LOG = LoggerFactory.getLogger(InsertCannulaAction.class); + + private final PodSessionState podState; + private final InsertCannulaService service; + private final BasalSchedule initialBasalSchedule; + + public InsertCannulaAction(InsertCannulaService insertCannulaService, PodSessionState podState, BasalSchedule initialBasalSchedule) { + if (insertCannulaService == null) { + throw new ActionInitializationException("Insert cannula service cannot be null"); + } + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (initialBasalSchedule == null) { + throw new ActionInitializationException("Initial basal schedule cannot be null"); + } + this.service = insertCannulaService; + this.podState = podState; + this.initialBasalSchedule = initialBasalSchedule; + } + + public static void updateCannulaInsertionStatus(PodSessionState podState, StatusResponse statusResponse) { + if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING) && + statusResponse.getPodProgressStatus().isReadyForDelivery()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Updating SetupProgress from CANNULA_INSERTING to COMPLETED"); + } + podState.setSetupProgress(SetupProgress.COMPLETED); + } + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING_FINISHED)) { + throw new IllegalSetupProgressException(SetupProgress.PRIMING_FINISHED, podState.getSetupProgress()); + } + + if (podState.getSetupProgress().isBefore(SetupProgress.INITIAL_BASAL_SCHEDULE_SET)) { + service.programInitialBasalSchedule(communicationService, podState, initialBasalSchedule); + podState.setSetupProgress(SetupProgress.INITIAL_BASAL_SCHEDULE_SET); + } + if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_INSERT_CANNULA)) { + service.executeExpirationRemindersAlertCommand(communicationService, podState); + podState.setSetupProgress(SetupProgress.STARTING_INSERT_CANNULA); + } + + if (podState.getSetupProgress().isBefore(SetupProgress.CANNULA_INSERTING)) { + StatusResponse statusResponse = service.executeInsertionBolusCommand(communicationService, podState); + podState.setSetupProgress(SetupProgress.CANNULA_INSERTING); + return statusResponse; + } else if (podState.getSetupProgress().equals(SetupProgress.CANNULA_INSERTING)) { + // Check status + StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState)); + updateCannulaInsertionStatus(podState, statusResponse); + return statusResponse; + } else { + throw new IllegalSetupProgressException(null, podState.getSetupProgress()); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/OmnipodAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/OmnipodAction.java new file mode 100644 index 00000000000..5cdcf12f76e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/OmnipodAction.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; + +public interface OmnipodAction { + T execute(OmnipodCommunicationService communicationService); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/PrimeAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/PrimeAction.java new file mode 100644 index 00000000000..57eff83694a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/PrimeAction.java @@ -0,0 +1,65 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service.PrimeService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException; + +public class PrimeAction implements OmnipodAction { + private static final Logger LOG = LoggerFactory.getLogger(PrimeAction.class); + + private final PrimeService service; + private final PodSessionState podState; + + public PrimeAction(PrimeService primeService, PodSessionState podState) { + if (primeService == null) { + throw new ActionInitializationException("Prime service cannot be null"); + } + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + this.service = primeService; + this.podState = podState; + } + + public static void updatePrimingStatus(PodSessionState podState, StatusResponse statusResponse) { + if (podState.getSetupProgress().equals(SetupProgress.PRIMING) && statusResponse.getPodProgressStatus().equals(PodProgressStatus.READY_FOR_BASAL_SCHEDULE)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Updating SetupProgress from PRIMING to PRIMING_FINISHED"); + } + podState.setSetupProgress(SetupProgress.PRIMING_FINISHED); + } + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + if (podState.getSetupProgress().isBefore(SetupProgress.POD_CONFIGURED)) { + throw new IllegalSetupProgressException(SetupProgress.POD_CONFIGURED, podState.getSetupProgress()); + } + if (podState.getSetupProgress().isBefore(SetupProgress.STARTING_PRIME)) { + service.executeDisableTab5Sub16FaultConfigCommand(communicationService, podState); + service.executeFinishSetupReminderAlertCommand(communicationService, podState); + podState.setSetupProgress(SetupProgress.STARTING_PRIME); + } + + if (podState.getSetupProgress().isBefore(SetupProgress.PRIMING)) { + StatusResponse statusResponse = service.executePrimeBolusCommand(communicationService, podState); + podState.setSetupProgress(SetupProgress.PRIMING); + return statusResponse; + } else if (podState.getSetupProgress().equals(SetupProgress.PRIMING)) { + // Check status + StatusResponse statusResponse = communicationService.executeAction(new GetStatusAction(podState)); + updatePrimingStatus(podState, statusResponse); + return statusResponse; + } else { + throw new IllegalSetupProgressException(null, podState.getSetupProgress()); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetBasalScheduleAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetBasalScheduleAction.java new file mode 100644 index 00000000000..f954a2c4663 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetBasalScheduleAction.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.joda.time.Duration; + +import java.util.Arrays; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.BasalScheduleExtraCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalDeliveryStatusException; + +public class SetBasalScheduleAction implements OmnipodAction { + private final PodSessionState podState; + private final BasalSchedule basalSchedule; + private final boolean confidenceReminder; + private final Duration scheduleOffset; + private final boolean acknowledgementBeep; + + public SetBasalScheduleAction(PodSessionState podState, BasalSchedule basalSchedule, + boolean confidenceReminder, Duration scheduleOffset, boolean acknowledgementBeep) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (basalSchedule == null) { + throw new ActionInitializationException("Basal schedule cannot be null"); + } + if (scheduleOffset == null) { + throw new ActionInitializationException("Schedule offset cannot be null"); + } + this.podState = podState; + this.basalSchedule = basalSchedule; + this.confidenceReminder = confidenceReminder; + this.scheduleOffset = scheduleOffset; + this.acknowledgementBeep = acknowledgementBeep; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + SetInsulinScheduleCommand setBasal = new SetInsulinScheduleCommand(podState.getCurrentNonce(), basalSchedule, scheduleOffset); + BasalScheduleExtraCommand extraCommand = new BasalScheduleExtraCommand(basalSchedule, scheduleOffset, + acknowledgementBeep, confidenceReminder, Duration.ZERO); + OmnipodMessage basalMessage = new OmnipodMessage(podState.getAddress(), Arrays.asList(setBasal, extraCommand), + podState.getMessageNumber()); + + StatusResponse statusResponse = communicationService.exchangeMessages(StatusResponse.class, podState, basalMessage); + podState.setBasalSchedule(basalSchedule); + return statusResponse; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java new file mode 100644 index 00000000000..52c37d329cd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/SetTempBasalAction.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action; + +import org.joda.time.Duration; + +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.SetInsulinScheduleCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.TempBasalExtraCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; + +public class SetTempBasalAction implements OmnipodAction { + private final PodSessionState podState; + private final double rate; + private final Duration duration; + private final boolean acknowledgementBeep; + private final boolean completionBeep; + + public SetTempBasalAction(PodSessionState podState, double rate, Duration duration, + boolean acknowledgementBeep, boolean completionBeep) { + if (podState == null) { + throw new ActionInitializationException("Pod state cannot be null"); + } + if (duration == null) { + throw new ActionInitializationException("Duration cannot be null"); + } + this.podState = podState; + this.rate = rate; + this.duration = duration; + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + } + + @Override + public StatusResponse execute(OmnipodCommunicationService communicationService) { + List messageBlocks = Arrays.asList( // + new SetInsulinScheduleCommand(podState.getCurrentNonce(), rate, duration), + new TempBasalExtraCommand(rate, duration, acknowledgementBeep, completionBeep, Duration.ZERO)); + + OmnipodMessage message = new OmnipodMessage(podState.getAddress(), messageBlocks, podState.getMessageNumber()); + return communicationService.exchangeMessages(StatusResponse.class, podState, message); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/InsertCannulaService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/InsertCannulaService.java new file mode 100644 index 00000000000..43248af7d5b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/InsertCannulaService.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service; + +import org.joda.time.DateTime; +import org.joda.time.Duration; + +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.SetBasalScheduleAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class InsertCannulaService { + public StatusResponse programInitialBasalSchedule(OmnipodCommunicationService communicationService, + PodSessionState podState, BasalSchedule basalSchedule) { + return communicationService.executeAction(new SetBasalScheduleAction(podState, basalSchedule, + true, podState.getScheduleOffset(), false)); + } + + public StatusResponse executeExpirationRemindersAlertCommand(OmnipodCommunicationService communicationService, + PodSessionState podState) { + AlertConfiguration lowReservoirAlertConfiguration = AlertConfigurationFactory.createLowReservoirAlertConfiguration(OmnipodConst.LOW_RESERVOIR_ALERT); + + DateTime endOfServiceTime = podState.getActivatedAt().plus(OmnipodConst.SERVICE_DURATION); + + Duration timeUntilExpirationAdvisoryAlarm = new Duration(DateTime.now(), + endOfServiceTime.minus(OmnipodConst.EXPIRATION_ADVISORY_WINDOW)); + Duration timeUntilShutdownImminentAlarm = new Duration(DateTime.now(), + endOfServiceTime.minus(OmnipodConst.END_OF_SERVICE_IMMINENT_WINDOW)); + + AlertConfiguration expirationAdvisoryAlertConfiguration = AlertConfigurationFactory.createExpirationAdvisoryAlertConfiguration( + timeUntilExpirationAdvisoryAlarm, OmnipodConst.EXPIRATION_ADVISORY_WINDOW); + AlertConfiguration shutdownImminentAlertConfiguration = AlertConfigurationFactory.createShutdownImminentAlertConfiguration( + timeUntilShutdownImminentAlarm); + AlertConfiguration autoOffAlertConfiguration = AlertConfigurationFactory.createAutoOffAlertConfiguration( + false, Duration.ZERO); + + List alertConfigurations = Arrays.asList( // + lowReservoirAlertConfiguration, // + expirationAdvisoryAlertConfiguration, // + shutdownImminentAlertConfiguration, // + autoOffAlertConfiguration // + ); + + return new ConfigureAlertsAction(podState, alertConfigurations).execute(communicationService); + } + + public StatusResponse executeInsertionBolusCommand(OmnipodCommunicationService communicationService, PodSessionState podState) { + return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_CANNULA_INSERTION_BOLUS_UNITS, + Duration.standardSeconds(1), false, false)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/PrimeService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/PrimeService.java new file mode 100644 index 00000000000..87444354101 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/action/service/PrimeService.java @@ -0,0 +1,37 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.action.service; + +import org.joda.time.Duration; + +import java.util.Collections; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.BolusAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.action.ConfigureAlertsAction; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.OmnipodMessage; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.FaultConfigCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfigurationFactory; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class PrimeService { + + public StatusResponse executeDisableTab5Sub16FaultConfigCommand(OmnipodCommunicationService communicationService, PodSessionState podState) { + FaultConfigCommand faultConfigCommand = new FaultConfigCommand(podState.getCurrentNonce(), (byte) 0x00, (byte) 0x00); + OmnipodMessage faultConfigMessage = new OmnipodMessage(podState.getAddress(), + Collections.singletonList(faultConfigCommand), podState.getMessageNumber()); + return communicationService.exchangeMessages(StatusResponse.class, podState, faultConfigMessage); + } + + public StatusResponse executeFinishSetupReminderAlertCommand(OmnipodCommunicationService communicationService, PodSessionState podState) { + AlertConfiguration finishSetupReminderAlertConfiguration = AlertConfigurationFactory.createFinishSetupReminderAlertConfiguration(); + return communicationService.executeAction(new ConfigureAlertsAction(podState, + Collections.singletonList(finishSetupReminderAlertConfiguration))); + } + + public StatusResponse executePrimeBolusCommand(OmnipodCommunicationService communicationService, PodSessionState podState) { + return communicationService.executeAction(new BolusAction(podState, OmnipodConst.POD_PRIME_BOLUS_UNITS, + Duration.standardSeconds(1), false, false)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/IRawRepresentable.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/IRawRepresentable.java new file mode 100644 index 00000000000..2eec2afaf82 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/IRawRepresentable.java @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message; + +public interface IRawRepresentable { + byte[] getRawData(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/MessageBlock.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/MessageBlock.java new file mode 100644 index 00000000000..dba0035df71 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/MessageBlock.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public abstract class MessageBlock { + protected byte[] encodedData = new byte[0]; + + public MessageBlock() { + } + + public abstract MessageBlockType getType(); + + //This method returns raw message representation + //It should be rewritten in a derived class if raw representation of a concrete message + //is something else than just message type concatenated with message data + public byte[] getRawData() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(this.getType().getValue()); + stream.write((byte) encodedData.length); + stream.write(encodedData); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return stream.toByteArray(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/NonceResyncableMessageBlock.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/NonceResyncableMessageBlock.java new file mode 100644 index 00000000000..61701643668 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/NonceResyncableMessageBlock.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message; + +public abstract class NonceResyncableMessageBlock extends MessageBlock { + public abstract int getNonce(); + + public abstract void setNonce(int nonce); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodMessage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodMessage.java new file mode 100644 index 00000000000..f3dc4a1856e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodMessage.java @@ -0,0 +1,148 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command.GetStatusCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CrcMismatchException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.MessageDecodingException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NotEnoughDataException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC; + +public class OmnipodMessage { + + private final int address; + private final List messageBlocks; + private final int sequenceNumber; + + public OmnipodMessage(OmnipodMessage other) { + address = other.address; + messageBlocks = new ArrayList<>(other.messageBlocks); + sequenceNumber = other.sequenceNumber; + } + + public OmnipodMessage(int address, List messageBlocks, int sequenceNumber) { + this.address = address; + this.messageBlocks = messageBlocks; + this.sequenceNumber = sequenceNumber; + } + + public static OmnipodMessage decodeMessage(byte[] data) { + if (data.length < 10) { + throw new NotEnoughDataException(data); + } + + int address = ByteUtil.toInt((int) data[0], (int) data[1], (int) data[2], + (int) data[3], ByteUtil.BitConversion.BIG_ENDIAN); + byte b9 = data[4]; + int bodyLength = ByteUtil.convertUnsignedByteToInt(data[5]); + if (data.length - 8 < bodyLength) { + throw new NotEnoughDataException(data); + } + int sequenceNumber = (((int) b9 >> 2) & 0b11111); + int crc = ByteUtil.toInt(data[data.length - 2], data[data.length - 1]); + int calculatedCrc = OmniCRC.crc16(ByteUtil.substring(data, 0, data.length - 2)); + if (crc != calculatedCrc) { + throw new CrcMismatchException(calculatedCrc, crc); + } + List blocks = decodeBlocks(ByteUtil.substring(data, 6, data.length - 6 - 2)); + if (blocks == null || blocks.size() == 0) { + throw new MessageDecodingException("No blocks decoded"); + } + + return new OmnipodMessage(address, blocks, sequenceNumber); + } + + private static List decodeBlocks(byte[] data) { + List blocks = new ArrayList<>(); + int index = 0; + while (index < data.length) { + try { + MessageBlockType blockType = MessageBlockType.fromByte(data[index]); + MessageBlock block = blockType.decode(ByteUtil.substring(data, index)); + blocks.add(block); + int blockLength = block.getRawData().length; + index += blockLength; + } catch (Exception ex) { + throw new MessageDecodingException("Failed to decode blocks", ex); + } + } + + return blocks; + } + + public byte[] getEncoded() { + byte[] encodedData = new byte[0]; + for (MessageBlock messageBlock : messageBlocks) { + encodedData = ByteUtil.concat(encodedData, messageBlock.getRawData()); + } + + byte[] header = new byte[0]; + //right before the message blocks we have 6 bits of seqNum and 10 bits of length + header = ByteUtil.concat(header, ByteUtil.getBytesFromInt(address)); + header = ByteUtil.concat(header, (byte) (((sequenceNumber & 0x1F) << 2) + ((encodedData.length >> 8) & 0x03))); + header = ByteUtil.concat(header, (byte) (encodedData.length & 0xFF)); + encodedData = ByteUtil.concat(header, encodedData); + int crc = OmniCRC.crc16(encodedData); + encodedData = ByteUtil.concat(encodedData, ByteUtil.substring(ByteUtil.getBytesFromInt(crc), 2, 2)); + return encodedData; + } + + public void padWithGetStatusCommands(int packetSize) { + while (getEncoded().length < packetSize) { + messageBlocks.add(new GetStatusCommand(PodInfoType.NORMAL)); + } + } + + public List getMessageBlocks() { + return messageBlocks; + } + + public int getSequenceNumber() { + return sequenceNumber; + } + + public boolean isNonceResyncable() { + return containsBlock(NonceResyncableMessageBlock.class); + } + + public int getSentNonce() { + for (MessageBlock messageBlock : messageBlocks) { + if (messageBlock instanceof NonceResyncableMessageBlock) { + return ((NonceResyncableMessageBlock) messageBlock).getNonce(); + } + } + throw new UnsupportedOperationException("Message is not nonce resyncable"); + } + + public void resyncNonce(int nonce) { + for (MessageBlock messageBlock : messageBlocks) { + if (messageBlock instanceof NonceResyncableMessageBlock) { + ((NonceResyncableMessageBlock) messageBlock).setNonce(nonce); + } + } + } + + public boolean containsBlock(Class blockType) { + for (MessageBlock messageBlock : messageBlocks) { + if (blockType.isInstance(messageBlock)) { + return true; + } + } + return false; + } + + + @Override + public String toString() { + return "OmnipodMessage{" + + "address=" + address + + ", messageBlocks=" + messageBlocks + + ", encoded=" + ByteUtil.shortHexStringWithoutSpaces(getEncoded()) + + ", sequenceNumber=" + sequenceNumber + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodPacket.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodPacket.java new file mode 100644 index 00000000000..a0658010819 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/OmnipodPacket.java @@ -0,0 +1,82 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message; + +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.data.RLMessage; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CrcMismatchException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPacketTypeException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC; + +/** + * Created by andy on 6/1/18. + */ +public class OmnipodPacket implements RLMessage { + private int packetAddress = 0; + private PacketType packetType = PacketType.INVALID; + private int sequenceNumber = 0; + private byte[] encodedMessage = null; + private boolean valid = false; + + public OmnipodPacket(byte[] encoded) { + if (encoded.length < 7) { + return; + } + this.packetAddress = ByteUtil.toInt((int) encoded[0], (int) encoded[1], + (int) encoded[2], (int) encoded[3], ByteUtil.BitConversion.BIG_ENDIAN); + try { + this.packetType = PacketType.fromByte((byte) (((int) encoded[4] & 0xFF) >> 5)); + } catch (IllegalArgumentException ex) { + throw new IllegalPacketTypeException(null, null); + } + this.sequenceNumber = (encoded[4] & 0b11111); + byte crc = OmniCRC.crc8(ByteUtil.substring(encoded, 0, encoded.length - 1)); + if (crc != encoded[encoded.length - 1]) { + throw new CrcMismatchException(crc, encoded[encoded.length - 1]); + } + this.encodedMessage = ByteUtil.substring(encoded, 5, encoded.length - 1 - 5); + valid = true; + } + + public OmnipodPacket(int packetAddress, PacketType packetType, int packetNumber, byte[] encodedMessage) { + this.packetAddress = packetAddress; + this.packetType = packetType; + this.sequenceNumber = packetNumber; + this.encodedMessage = encodedMessage; + if (encodedMessage.length > packetType.getMaxBodyLength()) { + this.encodedMessage = ByteUtil.substring(encodedMessage, 0, packetType.getMaxBodyLength()); + } + this.valid = true; + } + + public PacketType getPacketType() { + return packetType; + } + + public int getAddress() { + return packetAddress; + } + + public int getSequenceNumber() { + return sequenceNumber; + } + + public byte[] getEncodedMessage() { + return encodedMessage; + } + + @Override + public byte[] getTxData() { + byte[] output = new byte[0]; + output = ByteUtil.concat(output, ByteUtil.getBytesFromInt(this.packetAddress)); + output = ByteUtil.concat(output, (byte) ((this.packetType.getValue() << 5) + (sequenceNumber & 0b11111))); + output = ByteUtil.concat(output, encodedMessage); + output = ByteUtil.concat(output, OmniCRC.crc8(output)); + return output; + } + + @Override + public boolean isValid() { + return valid; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AcknowledgeAlertsCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AcknowledgeAlertsCommand.java new file mode 100644 index 00000000000..f429ed8b6fc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AcknowledgeAlertsCommand.java @@ -0,0 +1,54 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import java.util.Collections; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class AcknowledgeAlertsCommand extends NonceResyncableMessageBlock { + + private final AlertSet alerts; + private int nonce; + + public AcknowledgeAlertsCommand(int nonce, AlertSet alerts) { + this.nonce = nonce; + this.alerts = alerts; + encode(); + } + + public AcknowledgeAlertsCommand(int nonce, AlertSlot alertSlot) { + this(nonce, new AlertSet(Collections.singletonList(alertSlot))); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.ACKNOWLEDGE_ALERT; + } + + private void encode() { + encodedData = ByteUtil.getBytesFromInt(nonce); + encodedData = ByteUtil.concat(encodedData, alerts.getRawValue()); + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "AcknowledgeAlertsCommand{" + + "alerts=" + alerts + + ", nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AssignAddressCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AssignAddressCommand.java new file mode 100644 index 00000000000..e7a942885c9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/AssignAddressCommand.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import java.nio.ByteBuffer; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class AssignAddressCommand extends MessageBlock { + private final int address; + + public AssignAddressCommand(int address) { + this.address = address; + encodedData = ByteBuffer.allocate(4).putInt(this.address).array(); + } + + public int getAddress() { + return address; + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.ASSIGN_ADDRESS; + } + + @Override + public String toString() { + return "AssignAddressCommand{" + + "address=" + address + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BasalScheduleExtraCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BasalScheduleExtraCommand.java new file mode 100644 index 00000000000..5d628537f96 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BasalScheduleExtraCommand.java @@ -0,0 +1,127 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class BasalScheduleExtraCommand extends MessageBlock { + private final boolean acknowledgementBeep; + private final boolean completionBeep; + private final Duration programReminderInterval; + private final byte currentEntryIndex; + private final double remainingPulses; + // We use a double for the delay between pulses because the Joda time API lacks precision for our calculations + private final double delayUntilNextTenthOfPulseInSeconds; + private final List rateEntries; + + public BasalScheduleExtraCommand(boolean acknowledgementBeep, boolean completionBeep, + Duration programReminderInterval, byte currentEntryIndex, + double remainingPulses, double delayUntilNextTenthOfPulseInSeconds, List rateEntries) { + + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + this.programReminderInterval = programReminderInterval; + this.currentEntryIndex = currentEntryIndex; + this.remainingPulses = remainingPulses; + this.delayUntilNextTenthOfPulseInSeconds = delayUntilNextTenthOfPulseInSeconds; + this.rateEntries = rateEntries; + encode(); + } + + public BasalScheduleExtraCommand(BasalSchedule schedule, Duration scheduleOffset, + boolean acknowledgementBeep, boolean completionBeep, Duration programReminderInterval) { + rateEntries = new ArrayList<>(); + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + this.programReminderInterval = programReminderInterval; + Duration scheduleOffsetNearestSecond = Duration.standardSeconds(Math.round(scheduleOffset.getMillis() / 1000.0)); + + BasalSchedule mergedSchedule = new BasalSchedule(schedule.adjacentEqualRatesMergedEntries()); + List durations = mergedSchedule.getDurations(); + + for (BasalSchedule.BasalScheduleDurationEntry entry : durations) { + rateEntries.addAll(RateEntry.createEntries(entry.getRate(), entry.getDuration())); + } + + BasalSchedule.BasalScheduleLookupResult entryLookupResult = mergedSchedule.lookup(scheduleOffsetNearestSecond); + currentEntryIndex = (byte) entryLookupResult.getIndex(); + double timeRemainingInEntryInSeconds = entryLookupResult.getStartTime().minus(scheduleOffsetNearestSecond.minus(entryLookupResult.getDuration())).getMillis() / 1000.0; + double rate = mergedSchedule.rateAt(scheduleOffsetNearestSecond); + int pulsesPerHour = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE); + double timeBetweenPulses = 3600.0 / pulsesPerHour; + delayUntilNextTenthOfPulseInSeconds = (timeRemainingInEntryInSeconds % (timeBetweenPulses / 10.0)); + remainingPulses = pulsesPerHour * (timeRemainingInEntryInSeconds - delayUntilNextTenthOfPulseInSeconds) / 3600.0 + 0.1; + + encode(); + } + + private void encode() { + byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0)); + + encodedData = new byte[]{ + beepOptions, + currentEntryIndex + }; + + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10))); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) Math.round(delayUntilNextTenthOfPulseInSeconds * 1000 * 1000))); + + for (RateEntry entry : rateEntries) { + encodedData = ByteUtil.concat(encodedData, entry.getRawData()); + } + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.BASAL_SCHEDULE_EXTRA; + } + + public boolean isAcknowledgementBeep() { + return acknowledgementBeep; + } + + public boolean isCompletionBeep() { + return completionBeep; + } + + public Duration getProgramReminderInterval() { + return programReminderInterval; + } + + public byte getCurrentEntryIndex() { + return currentEntryIndex; + } + + public double getRemainingPulses() { + return remainingPulses; + } + + public double getDelayUntilNextTenthOfPulseInSeconds() { + return delayUntilNextTenthOfPulseInSeconds; + } + + public List getRateEntries() { + return new ArrayList<>(rateEntries); + } + + @Override + public String toString() { + return "BasalScheduleExtraCommand{" + + "acknowledgementBeep=" + acknowledgementBeep + + ", completionBeep=" + completionBeep + + ", programReminderInterval=" + programReminderInterval + + ", currentEntryIndex=" + currentEntryIndex + + ", remainingPulses=" + remainingPulses + + ", delayUntilNextTenthOfPulseInSeconds=" + delayUntilNextTenthOfPulseInSeconds + + ", rateEntries=" + rateEntries + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BeepConfigCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BeepConfigCommand.java new file mode 100644 index 00000000000..9695548f854 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BeepConfigCommand.java @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepConfigType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class BeepConfigCommand extends MessageBlock { + private final BeepConfigType beepType; + private final boolean basalCompletionBeep; + private final Duration basalIntervalBeep; + private final boolean tempBasalCompletionBeep; + private final Duration tempBasalIntervalBeep; + private final boolean bolusCompletionBeep; + private final Duration bolusIntervalBeep; + + public BeepConfigCommand(BeepConfigType beepType, boolean basalCompletionBeep, Duration basalIntervalBeep, + boolean tempBasalCompletionBeep, Duration tempBasalIntervalBeep, + boolean bolusCompletionBeep, Duration bolusIntervalBeep) { + this.beepType = beepType; + this.basalCompletionBeep = basalCompletionBeep; + this.basalIntervalBeep = basalIntervalBeep; + this.tempBasalCompletionBeep = tempBasalCompletionBeep; + this.tempBasalIntervalBeep = tempBasalIntervalBeep; + this.bolusCompletionBeep = bolusCompletionBeep; + this.bolusIntervalBeep = bolusIntervalBeep; + + encode(); + } + + public BeepConfigCommand(BeepConfigType beepType) { + this(beepType, false, Duration.ZERO, false, Duration.ZERO, false, Duration.ZERO); + } + + private void encode() { + encodedData = new byte[]{beepType.getValue()}; + encodedData = ByteUtil.concat(encodedData, (byte) ((basalCompletionBeep ? (1 << 6) : 0) + (basalIntervalBeep.getStandardMinutes() & 0x3f))); + encodedData = ByteUtil.concat(encodedData, (byte) ((tempBasalCompletionBeep ? (1 << 6) : 0) + (tempBasalIntervalBeep.getStandardMinutes() & 0x3f))); + encodedData = ByteUtil.concat(encodedData, (byte) ((bolusCompletionBeep ? (1 << 6) : 0) + (bolusIntervalBeep.getStandardMinutes() & 0x3f))); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.BEEP_CONFIG; + } + + @Override + public String toString() { + return "BeepConfigCommand{" + + "beepType=" + beepType + + ", basalCompletionBeep=" + basalCompletionBeep + + ", basalIntervalBeep=" + basalIntervalBeep + + ", tempBasalCompletionBeep=" + tempBasalCompletionBeep + + ", tempBasalIntervalBeep=" + tempBasalIntervalBeep + + ", bolusCompletionBeep=" + bolusCompletionBeep + + ", bolusIntervalBeep=" + bolusIntervalBeep + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BolusExtraCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BolusExtraCommand.java new file mode 100644 index 00000000000..e12aece3fc5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/BolusExtraCommand.java @@ -0,0 +1,76 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommandInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class BolusExtraCommand extends MessageBlock { + private final boolean acknowledgementBeep; + private final boolean completionBeep; + private final Duration programReminderInterval; + private final double units; + private final Duration timeBetweenPulses; + private final double squareWaveUnits; + private final Duration squareWaveDuration; + + public BolusExtraCommand(double units, boolean acknowledgementBeep, boolean completionBeep) { + this(units, Duration.standardSeconds(2), acknowledgementBeep, completionBeep); + } + + public BolusExtraCommand(double units, Duration timeBetweenPulses, boolean acknowledgementBeep, boolean completionBeep) { + this(units, 0.0, Duration.ZERO, acknowledgementBeep, completionBeep, Duration.ZERO, timeBetweenPulses); + } + + public BolusExtraCommand(double units, double squareWaveUnits, Duration squareWaveDuration, + boolean acknowledgementBeep, boolean completionBeep, + Duration programReminderInterval, Duration timeBetweenPulses) { + if (units <= 0D) { + throw new CommandInitializationException("Units should be > 0"); + } else if (units > OmnipodConst.MAX_BOLUS) { + throw new CommandInitializationException("Units exceeds max bolus"); + } + this.units = units; + this.squareWaveUnits = squareWaveUnits; + this.squareWaveDuration = squareWaveDuration; + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + this.programReminderInterval = programReminderInterval; + this.timeBetweenPulses = timeBetweenPulses; + encode(); + } + + private void encode() { + byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0)); + + int squareWavePulseCountCountX10 = (int) Math.round(squareWaveUnits * 200); + int timeBetweenExtendedPulses = squareWavePulseCountCountX10 > 0 ? (int) squareWaveDuration.getMillis() * 100 / squareWavePulseCountCountX10 : 0; + + encodedData = ByteUtil.concat(encodedData, beepOptions); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(units * 200))); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) timeBetweenPulses.getMillis() * 100)); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(squareWavePulseCountCountX10)); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(timeBetweenExtendedPulses)); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.BOLUS_EXTRA; + } + + @Override + public String toString() { + return "BolusExtraCommand{" + + "acknowledgementBeep=" + acknowledgementBeep + + ", completionBeep=" + completionBeep + + ", programReminderInterval=" + programReminderInterval + + ", units=" + units + + ", timeBetweenPulses=" + timeBetweenPulses + + ", squareWaveUnits=" + squareWaveUnits + + ", squareWaveDuration=" + squareWaveDuration + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/CancelDeliveryCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/CancelDeliveryCommand.java new file mode 100644 index 00000000000..451f9205f65 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/CancelDeliveryCommand.java @@ -0,0 +1,71 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import java.util.EnumSet; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.BeepType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class CancelDeliveryCommand extends NonceResyncableMessageBlock { + + private final BeepType beepType; + private final EnumSet deliveryTypes; + private int nonce; + + public CancelDeliveryCommand(int nonce, BeepType beepType, EnumSet deliveryTypes) { + this.nonce = nonce; + this.beepType = beepType; + this.deliveryTypes = deliveryTypes; + encode(); + } + + public CancelDeliveryCommand(int nonce, BeepType beepType, DeliveryType deliveryType) { + this(nonce, beepType, EnumSet.of(deliveryType)); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.CANCEL_DELIVERY; + } + + private void encode() { + encodedData = new byte[5]; + System.arraycopy(ByteUtil.getBytesFromInt(nonce), 0, encodedData, 0, 4); + byte beepTypeValue = beepType.getValue(); + if (beepTypeValue > 8) { + beepTypeValue = 0; + } + encodedData[4] = (byte) ((beepTypeValue & 0x0F) << 4); + if (deliveryTypes.contains(DeliveryType.BASAL)) { + encodedData[4] |= 1; + } + if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) { + encodedData[4] |= 2; + } + if (deliveryTypes.contains(DeliveryType.BOLUS)) { + encodedData[4] |= 4; + } + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "CancelDeliveryCommand{" + + "beepType=" + beepType + + ", deliveryTypes=" + deliveryTypes + + ", nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigureAlertsCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigureAlertsCommand.java new file mode 100644 index 00000000000..ac0a6a9f671 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigureAlertsCommand.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertConfiguration; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class ConfigureAlertsCommand extends NonceResyncableMessageBlock { + private final List configurations; + private int nonce; + + public ConfigureAlertsCommand(int nonce, List configurations) { + this.nonce = nonce; + this.configurations = configurations; + encode(); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.CONFIGURE_ALERTS; + } + + private void encode() { + encodedData = ByteUtil.getBytesFromInt(nonce); + for (AlertConfiguration config : configurations) { + encodedData = ByteUtil.concat(encodedData, config.getRawData()); + } + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "ConfigureAlertsCommand{" + + "configurations=" + configurations + + ", nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigurePodCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigurePodCommand.java new file mode 100644 index 00000000000..0f35a1fa242 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/ConfigurePodCommand.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.DateTime; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class ConfigurePodCommand extends MessageBlock { + + private static final byte PACKET_TIMEOUT_LIMIT = 0x04; + + private final int lot; + private final int tid; + private final DateTime date; + private final int address; + + public ConfigurePodCommand(int address, DateTime date, int lot, int tid) { + this.address = address; + this.lot = lot; + this.tid = tid; + this.date = date; + encode(); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.SETUP_POD; + } + + private void encode() { + encodedData = new byte[0]; + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(address)); + encodedData = ByteUtil.concat(encodedData, new byte[]{ // + (byte) 0x14, // unknown + PACKET_TIMEOUT_LIMIT, // + (byte) date.monthOfYear().get(), // + (byte) date.dayOfMonth().get(), // + (byte) (date.year().get() - 2000), // + (byte) date.hourOfDay().get(), // + (byte) date.minuteOfHour().get() // + }); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(lot)); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt(tid)); + } + + @Override + public String toString() { + return "ConfigurePodCommand{" + + "lot=" + lot + + ", tid=" + tid + + ", date=" + date + + ", address=" + address + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/DeactivatePodCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/DeactivatePodCommand.java new file mode 100644 index 00000000000..554d778d141 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/DeactivatePodCommand.java @@ -0,0 +1,41 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class DeactivatePodCommand extends NonceResyncableMessageBlock { + private int nonce; + + public DeactivatePodCommand(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.DEACTIVATE_POD; + } + + private void encode() { + encodedData = ByteUtil.getBytesFromInt(nonce); + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "DeactivatePodCommand{" + + "nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/FaultConfigCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/FaultConfigCommand.java new file mode 100644 index 00000000000..9d165583e38 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/FaultConfigCommand.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class FaultConfigCommand extends NonceResyncableMessageBlock { + private final byte tab5sub16; + private final byte tab5sub17; + private int nonce; + + public FaultConfigCommand(int nonce, byte tab5sub16, byte tab5sub17) { + this.nonce = nonce; + this.tab5sub16 = tab5sub16; + this.tab5sub17 = tab5sub17; + + encode(); + } + + private void encode() { + encodedData = ByteUtil.getBytesFromInt(nonce); + encodedData = ByteUtil.concat(encodedData, tab5sub16); + encodedData = ByteUtil.concat(encodedData, tab5sub17); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.FAULT_CONFIG; + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "FaultConfigCommand{" + + "tab5sub16=" + tab5sub16 + + ", tab5sub17=" + tab5sub17 + + ", nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/GetStatusCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/GetStatusCommand.java new file mode 100644 index 00000000000..68af71e2774 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/GetStatusCommand.java @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class GetStatusCommand extends MessageBlock { + private final PodInfoType podInfoType; + + public GetStatusCommand(PodInfoType podInfoType) { + this.podInfoType = podInfoType; + encode(); + } + + private void encode() { + encodedData = new byte[]{podInfoType.getValue()}; + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.GET_STATUS; + } + + @Override + public String toString() { + return "GetStatusCommand{" + + "podInfoType=" + podInfoType + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/SetInsulinScheduleCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/SetInsulinScheduleCommand.java new file mode 100644 index 00000000000..8f1dae6edb6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/SetInsulinScheduleCommand.java @@ -0,0 +1,98 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.NonceResyncableMessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliverySchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalDeliveryTable; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BolusDeliverySchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.DeliverySchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.TempBasalDeliverySchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommandInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class SetInsulinScheduleCommand extends NonceResyncableMessageBlock { + + private final DeliverySchedule schedule; + private int nonce; + + // Bolus + public SetInsulinScheduleCommand(int nonce, BolusDeliverySchedule schedule) { + this.nonce = nonce; + this.schedule = schedule; + encode(); + } + + // Basal schedule + public SetInsulinScheduleCommand(int nonce, BasalSchedule schedule, Duration scheduleOffset) { + int scheduleOffsetInSeconds = (int) scheduleOffset.getStandardSeconds(); + + BasalDeliveryTable table = new BasalDeliveryTable(schedule); + double rate = schedule.rateAt(scheduleOffset); + byte segment = (byte) (scheduleOffsetInSeconds / BasalDeliveryTable.SEGMENT_DURATION); + int segmentOffset = scheduleOffsetInSeconds % BasalDeliveryTable.SEGMENT_DURATION; + + int timeRemainingInSegment = BasalDeliveryTable.SEGMENT_DURATION - segmentOffset; + + double timeBetweenPulses = 3600 / (rate / OmnipodConst.POD_PULSE_SIZE); + + double offsetToNextTenth = timeRemainingInSegment % (timeBetweenPulses / 10.0); + + int pulsesRemainingInSegment = (int) ((timeRemainingInSegment + timeBetweenPulses / 10.0 - offsetToNextTenth) / timeBetweenPulses); + + this.nonce = nonce; + this.schedule = new BasalDeliverySchedule(segment, timeRemainingInSegment, pulsesRemainingInSegment, table); + encode(); + } + + // Temp basal + public SetInsulinScheduleCommand(int nonce, double tempBasalRate, Duration duration) { + if (tempBasalRate < 0D) { + throw new CommandInitializationException("Rate should be >= 0"); + } else if (tempBasalRate > OmnipodConst.MAX_BASAL_RATE) { + throw new CommandInitializationException("Rate exceeds max basal rate"); + } + if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) { + throw new CommandInitializationException("Duration exceeds max temp basal duration"); + } + int pulsesPerHour = (int) Math.round(tempBasalRate / OmnipodConst.POD_PULSE_SIZE); + int pulsesPerSegment = pulsesPerHour / 2; + this.nonce = nonce; + this.schedule = new TempBasalDeliverySchedule(BasalDeliveryTable.SEGMENT_DURATION, pulsesPerSegment, new BasalDeliveryTable(tempBasalRate, duration)); + encode(); + } + + private void encode() { + encodedData = ByteUtil.getBytesFromInt(nonce); + encodedData = ByteUtil.concat(encodedData, schedule.getType().getValue()); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(schedule.getChecksum())); + encodedData = ByteUtil.concat(encodedData, schedule.getRawData()); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.SET_INSULIN_SCHEDULE; + } + + @Override + public int getNonce() { + return nonce; + } + + @Override + public void setNonce(int nonce) { + this.nonce = nonce; + encode(); + } + + @Override + public String toString() { + return "SetInsulinScheduleCommand{" + + "schedule=" + schedule + + ", nonce=" + nonce + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/TempBasalExtraCommand.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/TempBasalExtraCommand.java new file mode 100644 index 00000000000..8f21c69a5e5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/command/TempBasalExtraCommand.java @@ -0,0 +1,108 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.command; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.RateEntry; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommandInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class TempBasalExtraCommand extends MessageBlock { + private final boolean acknowledgementBeep; + private final boolean completionBeep; + private final Duration programReminderInterval; + private final double remainingPulses; + // We use a double for the delay until next pulse because the Joda time API lacks precision for our calculations + private final double delayUntilNextPulse; + private final List rateEntries; + + public TempBasalExtraCommand(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep, + Duration programReminderInterval) { + if (rate < 0D) { + throw new CommandInitializationException("Rate should be >= 0"); + } else if (rate > OmnipodConst.MAX_BASAL_RATE) { + throw new CommandInitializationException("Rate exceeds max basal rate"); + } + if (duration.isLongerThan(OmnipodConst.MAX_TEMP_BASAL_DURATION)) { + throw new CommandInitializationException("Duration exceeds max temp basal duration"); + } + + this.acknowledgementBeep = acknowledgementBeep; + this.completionBeep = completionBeep; + this.programReminderInterval = programReminderInterval; + + rateEntries = RateEntry.createEntries(rate, duration); + + RateEntry currentRateEntry = rateEntries.get(0); + remainingPulses = currentRateEntry.getTotalPulses(); + delayUntilNextPulse = currentRateEntry.getDelayBetweenPulsesInSeconds(); + + encode(); + } + + private void encode() { + byte beepOptions = (byte) ((programReminderInterval.getStandardMinutes() & 0x3f) + (completionBeep ? 1 << 6 : 0) + (acknowledgementBeep ? 1 << 7 : 0)); + + encodedData = new byte[]{ + beepOptions, + (byte) 0x00 + }; + + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16((int) Math.round(remainingPulses * 10))); + if (remainingPulses == 0) { + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100) * 10)); + } else { + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt((int) (delayUntilNextPulse * 1000 * 100))); + } + + for (RateEntry entry : rateEntries) { + encodedData = ByteUtil.concat(encodedData, entry.getRawData()); + } + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.TEMP_BASAL_EXTRA; + } + + public boolean isAcknowledgementBeep() { + return acknowledgementBeep; + } + + public boolean isCompletionBeep() { + return completionBeep; + } + + public Duration getProgramReminderInterval() { + return programReminderInterval; + } + + public double getRemainingPulses() { + return remainingPulses; + } + + public double getDelayUntilNextPulse() { + return delayUntilNextPulse; + } + + public List getRateEntries() { + return new ArrayList<>(rateEntries); + } + + @Override + public String toString() { + return "TempBasalExtraCommand{" + + "acknowledgementBeep=" + acknowledgementBeep + + ", completionBeep=" + completionBeep + + ", programReminderInterval=" + programReminderInterval + + ", remainingPulses=" + remainingPulses + + ", delayUntilNextPulse=" + delayUntilNextPulse + + ", rateEntries=" + rateEntries + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/ErrorResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/ErrorResponse.java new file mode 100644 index 00000000000..18f02475e1a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/ErrorResponse.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.ErrorResponseType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class ErrorResponse extends MessageBlock { + private static final int MESSAGE_LENGTH = 5; + + private final ErrorResponseType errorResponseType; + private final int nonceSearchKey; + + public ErrorResponse(byte[] encodedData) { + if (encodedData.length < MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + this.encodedData = ByteUtil.substring(encodedData, 2, MESSAGE_LENGTH - 2); + + ErrorResponseType errorResponseType = null; + try { + errorResponseType = ErrorResponseType.fromByte(encodedData[2]); + } catch (IllegalArgumentException ex) { + } + + this.errorResponseType = errorResponseType; + this.nonceSearchKey = ByteUtil.makeUnsignedShort((int) encodedData[3], (int) encodedData[4]); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.ERROR_RESPONSE; + } + + public ErrorResponseType getErrorResponseType() { + return errorResponseType; + } + + public int getNonceSearchKey() { + return nonceSearchKey; + } + + @Override + public String toString() { + return "ErrorResponse{" + + "errorResponseType=" + errorResponseType + + ", nonceSearchKey=" + nonceSearchKey + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/StatusResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/StatusResponse.java new file mode 100644 index 00000000000..a9d831f8ae8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/StatusResponse.java @@ -0,0 +1,119 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response; + +import org.joda.time.Duration; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class StatusResponse extends MessageBlock { + private static final int MESSAGE_LENGTH = 10; + + private final DeliveryStatus deliveryStatus; + private final PodProgressStatus podProgressStatus; + private final Duration timeActive; + private final Double reservoirLevel; + private final double insulinDelivered; + private final double insulinNotDelivered; + private final byte podMessageCounter; + private final AlertSet alerts; + + public StatusResponse(byte[] encodedData) { + if (encodedData.length < MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + this.encodedData = ByteUtil.substring(encodedData, 1, MESSAGE_LENGTH - 1); + + this.deliveryStatus = DeliveryStatus.fromByte((byte) (ByteUtil.convertUnsignedByteToInt(encodedData[1]) >>> 4)); + this.podProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[1] & 0x0F)); + + int minutes = ((encodedData[7] & 0x7F) << 6) | ((encodedData[8] & 0xFC) >>> 2); + this.timeActive = Duration.standardMinutes(minutes); + + int highInsulinBits = (encodedData[2] & 0xF) << 9; + int middleInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[3]) << 1; + int lowInsulinBits = ByteUtil.convertUnsignedByteToInt(encodedData[4]) >>> 7; + this.insulinDelivered = OmnipodConst.POD_PULSE_SIZE * (highInsulinBits | middleInsulinBits | lowInsulinBits); + this.podMessageCounter = (byte) ((encodedData[4] >>> 3) & 0xf); + + this.insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * (((encodedData[4] & 0x03) << 8) | ByteUtil.convertUnsignedByteToInt(encodedData[5])); + this.alerts = new AlertSet((byte) (((encodedData[6] & 0x7f) << 1) | (ByteUtil.convertUnsignedByteToInt(encodedData[7]) >>> 7))); + + double reservoirValue = (((encodedData[8] & 0x3) << 8) + ByteUtil.convertUnsignedByteToInt(encodedData[9])) * OmnipodConst.POD_PULSE_SIZE; + if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) { + reservoirLevel = null; + } else { + reservoirLevel = reservoirValue; + } + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.STATUS_RESPONSE; + } + + public DeliveryStatus getDeliveryStatus() { + return deliveryStatus; + } + + public PodProgressStatus getPodProgressStatus() { + return podProgressStatus; + } + + public Duration getTimeActive() { + return timeActive; + } + + public Double getReservoirLevel() { + return reservoirLevel; + } + + public double getInsulinDelivered() { + return insulinDelivered; + } + + public double getInsulinNotDelivered() { + return insulinNotDelivered; + } + + public byte getPodMessageCounter() { + return podMessageCounter; + } + + public AlertSet getAlerts() { + return alerts; + } + + public byte[] getRawData() { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + stream.write(this.getType().getValue()); + stream.write(encodedData); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return stream.toByteArray(); + } + + @Override + public String toString() { + return "StatusResponse{" + + "deliveryStatus=" + deliveryStatus + + ", podProgressStatus=" + podProgressStatus + + ", timeActive=" + timeActive + + ", reservoirLevel=" + reservoirLevel + + ", insulinDelivered=" + insulinDelivered + + ", insulinNotDelivered=" + insulinNotDelivered + + ", podMessageCounter=" + podMessageCounter + + ", alerts=" + alerts + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/VersionResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/VersionResponse.java new file mode 100644 index 00000000000..a8c9e9403de --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/VersionResponse.java @@ -0,0 +1,91 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; + +public class VersionResponse extends MessageBlock { + private final PodProgressStatus podProgressStatus; + private final FirmwareVersion pmVersion; + private final FirmwareVersion piVersion; + private final int lot; + private final int tid; + private final int address; + + public VersionResponse(byte[] encodedData) { + int length = ByteUtil.convertUnsignedByteToInt(encodedData[1]) + 2; + this.encodedData = ByteUtil.substring(encodedData, 2, length - 2); + + boolean extraByte; + byte[] truncatedData; + + switch (length) { + case 0x17: + truncatedData = ByteUtil.substring(encodedData, 2); + extraByte = true; + break; + case 0x1D: + truncatedData = ByteUtil.substring(encodedData, 9); + extraByte = false; + break; + default: + throw new IllegalArgumentException("Unrecognized VersionResponse message length: " + length); + } + + this.podProgressStatus = PodProgressStatus.fromByte(truncatedData[7]); + this.pmVersion = new FirmwareVersion(truncatedData[0], truncatedData[1], truncatedData[2]); + this.piVersion = new FirmwareVersion(truncatedData[3], truncatedData[4], truncatedData[5]); + this.lot = ByteUtil.toInt((int) truncatedData[8], (int) truncatedData[9], + (int) truncatedData[10], (int) truncatedData[11], ByteUtil.BitConversion.BIG_ENDIAN); + this.tid = ByteUtil.toInt((int) truncatedData[12], (int) truncatedData[13], + (int) truncatedData[14], (int) truncatedData[15], ByteUtil.BitConversion.BIG_ENDIAN); + + int indexIncrementor = extraByte ? 1 : 0; + + this.address = ByteUtil.toInt((int) truncatedData[16 + indexIncrementor], (int) truncatedData[17 + indexIncrementor], + (int) truncatedData[18 + indexIncrementor], (int) truncatedData[19 + indexIncrementor], ByteUtil.BitConversion.BIG_ENDIAN); + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.VERSION_RESPONSE; + } + + public PodProgressStatus getPodProgressStatus() { + return podProgressStatus; + } + + public FirmwareVersion getPmVersion() { + return pmVersion; + } + + public FirmwareVersion getPiVersion() { + return piVersion; + } + + public int getLot() { + return lot; + } + + public int getTid() { + return tid; + } + + public int getAddress() { + return address; + } + + @Override + public String toString() { + return "VersionResponse{" + + "podProgressStatus=" + podProgressStatus + + ", pmVersion=" + pmVersion + + ", piVersion=" + piVersion + + ", lot=" + lot + + ", tid=" + tid + + ", address=" + address + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfo.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfo.java new file mode 100644 index 00000000000..9002f0d92e8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfo.java @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public abstract class PodInfo { + private final byte[] encodedData; + + public PodInfo(byte[] encodedData) { + this.encodedData = encodedData; + } + + public abstract PodInfoType getType(); + + public byte[] getEncodedData() { + return encodedData; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoActiveAlerts.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoActiveAlerts.java new file mode 100644 index 00000000000..1f32777fdaa --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoActiveAlerts.java @@ -0,0 +1,92 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class PodInfoActiveAlerts extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 11; + + private final byte[] word278; // Unknown use + private final List alertActivations; + + public PodInfoActiveAlerts(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + word278 = ByteUtil.substring(encodedData, 1, 2); + + alertActivations = new ArrayList<>(); + + for (AlertSlot alertSlot : AlertSlot.values()) { + int valueHighBits = ByteUtil.convertUnsignedByteToInt(encodedData[3 + alertSlot.getValue() * 2]); + int valueLowBits = ByteUtil.convertUnsignedByteToInt(encodedData[4 + alertSlot.getValue() * 2]); + int value = (valueHighBits << 8) | valueLowBits; + if (value != 0) { + alertActivations.add(new AlertActivation(alertSlot, value)); + } + } + } + + @Override + public PodInfoType getType() { + return PodInfoType.ACTIVE_ALERTS; + } + + public byte[] getWord278() { + return word278; + } + + public List getAlertActivations() { + return new ArrayList<>(alertActivations); + } + + @Override + public String toString() { + return "PodInfoActiveAlerts{" + + "word278=" + Arrays.toString(word278) + + ", alertActivations=" + alertActivations + + '}'; + } + + public static class AlertActivation { + private final AlertSlot alertSlot; + private final int value; + + private AlertActivation(AlertSlot alertSlot, int value) { + this.alertSlot = alertSlot; + this.value = value; + } + + public double getValueAsUnits() { + return value * OmnipodConst.POD_PULSE_SIZE; + } + + public Duration getValueAsDuration() { + return Duration.standardMinutes(value); + } + + public AlertSlot getAlertSlot() { + return alertSlot; + } + + @Override + public String toString() { + return "AlertActivation{" + + "alertSlot=" + alertSlot + + ", valueAsUnits=" + getValueAsUnits() + + ", valueAsDuration=" + getValueAsDuration() + + '}'; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoDataLog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoDataLog.java new file mode 100644 index 00000000000..a8eebc76969 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoDataLog.java @@ -0,0 +1,83 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoDataLog extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 8; + private final FaultEventType faultEventType; + private final Duration timeFaultEvent; + private final Duration timeSinceActivation; + private final byte dataChunkSize; + private final byte maximumNumberOfDwords; + private final List dwords; + + public PodInfoDataLog(byte[] encodedData, int bodyLength) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + faultEventType = FaultEventType.fromByte(encodedData[1]); + timeFaultEvent = Duration.standardMinutes(ByteUtil.toInt(encodedData[2], encodedData[3])); + timeSinceActivation = Duration.standardMinutes(ByteUtil.toInt(encodedData[4], encodedData[5])); + dataChunkSize = encodedData[6]; + maximumNumberOfDwords = encodedData[7]; + + dwords = new ArrayList<>(); + + int numberOfDwords = (bodyLength - 8) / 4; + for (int i = 0; i < numberOfDwords; i++) { + dwords.add(ByteUtil.substring(encodedData, 8 + (4 * i), 4)); + } + } + + @Override + public PodInfoType getType() { + return PodInfoType.DATA_LOG; + } + + public FaultEventType getFaultEventType() { + return faultEventType; + } + + public Duration getTimeFaultEvent() { + return timeFaultEvent; + } + + public Duration getTimeSinceActivation() { + return timeSinceActivation; + } + + public byte getDataChunkSize() { + return dataChunkSize; + } + + public byte getMaximumNumberOfDwords() { + return maximumNumberOfDwords; + } + + public List getDwords() { + return Collections.unmodifiableList(dwords); + } + + @Override + public String toString() { + return "PodInfoDataLog{" + + "faultEventType=" + faultEventType + + ", timeFaultEvent=" + timeFaultEvent + + ", timeSinceActivation=" + timeSinceActivation + + ", dataChunkSize=" + dataChunkSize + + ", maximumNumberOfDwords=" + maximumNumberOfDwords + + ", dwords=" + dwords + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultAndInitializationTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultAndInitializationTime.java new file mode 100644 index 00000000000..747b5e821bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultAndInitializationTime.java @@ -0,0 +1,54 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import org.joda.time.DateTime; +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoFaultAndInitializationTime extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 17; + private final FaultEventType faultEventType; + private final Duration timeFaultEvent; + private final DateTime initializationTime; + + public PodInfoFaultAndInitializationTime(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + faultEventType = FaultEventType.fromByte(encodedData[1]); + timeFaultEvent = Duration.standardMinutes(((encodedData[2] & 0b1) << 8) + encodedData[3]); + // We ignore time zones here because we don't keep the time zone in which the pod was initially set up + // Which is fine because we don't use the initialization time for anything important anyway + initializationTime = new DateTime(2000 + encodedData[14], encodedData[12], encodedData[13], encodedData[15], encodedData[16]); + } + + @Override + public PodInfoType getType() { + return PodInfoType.FAULT_AND_INITIALIZATION_TIME; + } + + public FaultEventType getFaultEventType() { + return faultEventType; + } + + public Duration getTimeFaultEvent() { + return timeFaultEvent; + } + + public DateTime getInitializationTime() { + return initializationTime; + } + + @Override + public String toString() { + return "PodInfoFaultAndInitializationTime{" + + "faultEventType=" + faultEventType + + ", timeFaultEvent=" + timeFaultEvent + + ", initializationTime=" + initializationTime + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultEvent.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultEvent.java new file mode 100644 index 00000000000..9db41a12f75 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoFaultEvent.java @@ -0,0 +1,172 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.LogEventErrorCode; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class PodInfoFaultEvent extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 21; + + private final PodProgressStatus podProgressStatus; + private final DeliveryStatus deliveryStatus; + private final double insulinNotDelivered; + private final byte podMessageCounter; + private final double totalInsulinDelivered; + private final FaultEventType faultEventType; + private final Duration faultEventTime; + private final Double reservoirLevel; + private final Duration timeSinceActivation; + private final AlertSet unacknowledgedAlerts; + private final boolean faultAccessingTables; + private final LogEventErrorCode logEventErrorType; + private final PodProgressStatus logEventErrorPodProgressStatus; + private final byte receiverLowGain; + private final byte radioRSSI; + private final PodProgressStatus podProgressStatusAtTimeOfFirstLoggedFaultEvent; + private final byte[] unknownValue; + + public PodInfoFaultEvent(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + podProgressStatus = PodProgressStatus.fromByte(encodedData[1]); + deliveryStatus = DeliveryStatus.fromByte(encodedData[2]); + insulinNotDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[3], encodedData[4]); + podMessageCounter = encodedData[5]; + totalInsulinDelivered = OmnipodConst.POD_PULSE_SIZE * ByteUtil.toInt(encodedData[6], encodedData[7]); + faultEventType = FaultEventType.fromByte(encodedData[8]); + + int minutesSinceActivation = ByteUtil.toInt(encodedData[9], encodedData[10]); + if (minutesSinceActivation == 0xffff) { + faultEventTime = null; + } else { + faultEventTime = Duration.standardMinutes(minutesSinceActivation); + } + + double reservoirValue = ((encodedData[11] & 0x03) << 8) + + ByteUtil.convertUnsignedByteToInt(encodedData[12]) * OmnipodConst.POD_PULSE_SIZE; + if (reservoirValue > OmnipodConst.MAX_RESERVOIR_READING) { + reservoirLevel = null; + } else { + reservoirLevel = reservoirValue; + } + + int minutesActive = ByteUtil.toInt(encodedData[13], encodedData[14]); + timeSinceActivation = Duration.standardMinutes(minutesActive); + + unacknowledgedAlerts = new AlertSet(encodedData[15]); + faultAccessingTables = encodedData[16] == 0x02; + logEventErrorType = LogEventErrorCode.fromByte((byte) (encodedData[17] >>> 4)); + logEventErrorPodProgressStatus = PodProgressStatus.fromByte((byte) (encodedData[17] & 0x0f)); + receiverLowGain = (byte) (ByteUtil.convertUnsignedByteToInt(encodedData[18]) >>> 6); + radioRSSI = (byte) (encodedData[18] & 0x3f); + podProgressStatusAtTimeOfFirstLoggedFaultEvent = PodProgressStatus.fromByte((byte) (encodedData[19] & 0x0f)); + unknownValue = ByteUtil.substring(encodedData, 20, 2); + } + + @Override + public PodInfoType getType() { + return PodInfoType.FAULT_EVENT; + } + + public PodProgressStatus getPodProgressStatus() { + return podProgressStatus; + } + + public DeliveryStatus getDeliveryStatus() { + return deliveryStatus; + } + + public double getInsulinNotDelivered() { + return insulinNotDelivered; + } + + public byte getPodMessageCounter() { + return podMessageCounter; + } + + public double getTotalInsulinDelivered() { + return totalInsulinDelivered; + } + + public FaultEventType getFaultEventType() { + return faultEventType; + } + + public Duration getFaultEventTime() { + return faultEventTime; + } + + public Double getReservoirLevel() { + return reservoirLevel; + } + + public Duration getTimeSinceActivation() { + return timeSinceActivation; + } + + public AlertSet getUnacknowledgedAlerts() { + return unacknowledgedAlerts; + } + + public boolean isFaultAccessingTables() { + return faultAccessingTables; + } + + public LogEventErrorCode getLogEventErrorType() { + return logEventErrorType; + } + + public PodProgressStatus getLogEventErrorPodProgressStatus() { + return logEventErrorPodProgressStatus; + } + + public byte getReceiverLowGain() { + return receiverLowGain; + } + + public byte getRadioRSSI() { + return radioRSSI; + } + + public PodProgressStatus getPodProgressStatusAtTimeOfFirstLoggedFaultEvent() { + return podProgressStatusAtTimeOfFirstLoggedFaultEvent; + } + + public byte[] getUnknownValue() { + return unknownValue; + } + + @Override + public String toString() { + return "PodInfoFaultEvent{" + + "podProgressStatus=" + podProgressStatus + + ", deliveryStatus=" + deliveryStatus + + ", insulinNotDelivered=" + insulinNotDelivered + + ", podMessageCounter=" + podMessageCounter + + ", totalInsulinDelivered=" + totalInsulinDelivered + + ", faultEventType=" + faultEventType + + ", faultEventTime=" + faultEventTime + + ", reservoirLevel=" + reservoirLevel + + ", timeSinceActivation=" + timeSinceActivation + + ", unacknowledgedAlerts=" + unacknowledgedAlerts + + ", faultAccessingTables=" + faultAccessingTables + + ", logEventErrorType=" + logEventErrorType + + ", logEventErrorPodProgressStatus=" + logEventErrorPodProgressStatus + + ", receiverLowGain=" + receiverLowGain + + ", radioRSSI=" + radioRSSI + + ", podProgressStatusAtTimeOfFirstLoggedFaultEvent=" + podProgressStatusAtTimeOfFirstLoggedFaultEvent + + ", unknownValue=" + ByteUtil.shortHexString(unknownValue) + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoLowFlashLogDump.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoLowFlashLogDump.java new file mode 100644 index 00000000000..72d4d215c49 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoLowFlashLogDump.java @@ -0,0 +1,50 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoLowFlashLogDump extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 8; + + private final byte numberOfBytes; + private final byte[] dataFromFlashMemory; + private final int podAddress; + + public PodInfoLowFlashLogDump(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + numberOfBytes = encodedData[2]; + podAddress = ByteUtil.toInt((int) encodedData[3], (int) encodedData[4], (int) encodedData[5], (int) encodedData[6], ByteUtil.BitConversion.BIG_ENDIAN); + dataFromFlashMemory = ByteUtil.substring(encodedData, 3, ByteUtil.convertUnsignedByteToInt(encodedData[2])); + } + + @Override + public PodInfoType getType() { + return PodInfoType.LOW_FLASH_DUMP_LOG; + } + + public byte getNumberOfBytes() { + return numberOfBytes; + } + + public byte[] getDataFromFlashMemory() { + return dataFromFlashMemory; + } + + public int getPodAddress() { + return podAddress; + } + + @Override + public String toString() { + return "PodInfoLowFlashLogDump{" + + "numberOfBytes=" + numberOfBytes + + ", dataFromFlashMemory=" + ByteUtil.shortHexString(dataFromFlashMemory) + + ", podAddress=" + podAddress + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoOlderPulseLog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoOlderPulseLog.java new file mode 100644 index 00000000000..c36b053cce7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoOlderPulseLog.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoOlderPulseLog extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 3; + + private final ArrayList dwords; + + public PodInfoOlderPulseLog(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + dwords = new ArrayList<>(); + + int numberOfDwordLogEntries = ByteUtil.toInt(encodedData[1], encodedData[2]); + for (int i = 0; numberOfDwordLogEntries > i; i++) { + byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4); + dwords.add(dword); + } + } + + @Override + public PodInfoType getType() { + return PodInfoType.OLDER_PULSE_LOG; + } + + public List getDwords() { + return Collections.unmodifiableList(dwords); + } + + @Override + public String toString() { + String out = "PodInfoOlderPulseLog{" + + "dwords=["; + + List hexDwords = new ArrayList<>(); + for (byte[] dword : dwords) { + hexDwords.add(ByteUtil.shortHexStringWithoutSpaces(dword)); + } + out += TextUtils.join(", ", hexDwords); + out += "]}"; + + return out; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoRecentPulseLog.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoRecentPulseLog.java new file mode 100644 index 00000000000..1008cca6897 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoRecentPulseLog.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoRecentPulseLog extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 3; + + private final ArrayList dwords; + + private final int lastEntryIndex; + + public PodInfoRecentPulseLog(byte[] encodedData, int bodyLength) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + lastEntryIndex = ByteUtil.toInt(encodedData[1], encodedData[2]); + dwords = new ArrayList<>(); + + int numberOfDwords = (bodyLength - 3) / 4; + + for (int i = 0; numberOfDwords > i; i++) { + byte[] dword = ByteUtil.substring(encodedData, 3 + (4 * i), 4); + dwords.add(dword); + } + } + + @Override + public PodInfoType getType() { + return PodInfoType.RECENT_PULSE_LOG; + } + + public List getDwords() { + return Collections.unmodifiableList(dwords); + } + + public int getLastEntryIndex() { + return lastEntryIndex; + } + + @Override + public String toString() { + String out = "PodInfoRecentPulseLog{" + + "lastEntryIndex=" + lastEntryIndex + + ",dwords=["; + + List hexDwords = new ArrayList<>(); + for (byte[] dword : dwords) { + hexDwords.add(ByteUtil.shortHexStringWithoutSpaces(dword)); + } + out += TextUtils.join(", ", hexDwords); + out += "]}"; + return out; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoResponse.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoResponse.java new file mode 100644 index 00000000000..59322f62091 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoResponse.java @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoResponse extends MessageBlock { + private final PodInfoType subType; + private final PodInfo podInfo; + + public PodInfoResponse(byte[] encodedData) { + int bodyLength = ByteUtil.convertUnsignedByteToInt(encodedData[1]); + + this.encodedData = ByteUtil.substring(encodedData, 2, bodyLength); + subType = PodInfoType.fromByte(encodedData[2]); + podInfo = subType.decode(this.encodedData, bodyLength); + } + + public PodInfoType getSubType() { + return subType; + } + + public T getPodInfo() { + return (T) podInfo; + } + + @Override + public MessageBlockType getType() { + return MessageBlockType.POD_INFO_RESPONSE; + } + + @Override + public String toString() { + return "PodInfoResponse{" + + "subType=" + subType.name() + + ", podInfo=" + podInfo.toString() + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoTestValues.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoTestValues.java new file mode 100644 index 00000000000..006ba876f6d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/message/response/podinfo/PodInfoTestValues.java @@ -0,0 +1,55 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; + +public class PodInfoTestValues extends PodInfo { + private static final int MINIMUM_MESSAGE_LENGTH = 5; + private final byte byte1; + private final byte byte2; + private final byte byte3; + private final byte byte4; + + public PodInfoTestValues(byte[] encodedData) { + super(encodedData); + + if (encodedData.length < MINIMUM_MESSAGE_LENGTH) { + throw new IllegalArgumentException("Not enough data"); + } + + byte1 = encodedData[1]; + byte2 = encodedData[2]; + byte3 = encodedData[3]; + byte4 = encodedData[4]; + } + + @Override + public PodInfoType getType() { + return PodInfoType.HARDCODED_TEST_VALUES; + } + + public byte getByte1() { + return byte1; + } + + public byte getByte2() { + return byte2; + } + + public byte getByte3() { + return byte3; + } + + public byte getByte4() { + return byte4; + } + + @Override + public String toString() { + return "PodInfoTestValues{" + + "byte1=" + byte1 + + ", byte2=" + byte2 + + ", byte3=" + byte3 + + ", byte4=" + byte4 + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfiguration.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfiguration.java new file mode 100644 index 00000000000..6c48954cfec --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfiguration.java @@ -0,0 +1,71 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class AlertConfiguration { + private final AlertType alertType; + private final AlertSlot alertSlot; + private final boolean active; + private final boolean autoOffModifier; + private final Duration duration; + private final AlertTrigger alertTrigger; + private final BeepRepeat beepRepeat; + private final BeepType beepType; + + public AlertConfiguration(AlertType alertType, AlertSlot alertSlot, boolean active, boolean autoOffModifier, + Duration duration, AlertTrigger alertTrigger, + BeepType beepType, BeepRepeat beepRepeat) { + this.alertType = alertType; + this.alertSlot = alertSlot; + this.active = active; + this.autoOffModifier = autoOffModifier; + this.duration = duration; + this.alertTrigger = alertTrigger; + this.beepRepeat = beepRepeat; + this.beepType = beepType; + } + + public AlertType getAlertType() { + return alertType; + } + + public AlertSlot getAlertSlot() { + return alertSlot; + } + + public byte[] getRawData() { + int firstByte = (alertSlot.getValue() << 4); + firstByte += active ? (1 << 3) : 0; + + if (alertTrigger instanceof UnitsRemainingAlertTrigger) { + firstByte += 1 << 2; + } + + if (autoOffModifier) { + firstByte += 1 << 1; + } + + firstByte += ((int) duration.getStandardMinutes() >>> 8) & 0x1; + + byte[] encodedData = new byte[]{ + (byte) firstByte, + (byte) duration.getStandardMinutes() + }; + + if (alertTrigger instanceof UnitsRemainingAlertTrigger) { + int ticks = (int) (((UnitsRemainingAlertTrigger) alertTrigger).getValue() / OmnipodConst.POD_PULSE_SIZE / 2); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(ticks)); + } else if (alertTrigger instanceof TimerAlertTrigger) { + int durationInMinutes = (int) ((TimerAlertTrigger) alertTrigger).getValue().getStandardMinutes(); + encodedData = ByteUtil.concat(encodedData, ByteUtil.getBytesFromInt16(durationInMinutes)); + } + + encodedData = ByteUtil.concat(encodedData, beepRepeat.getValue()); + encodedData = ByteUtil.concat(encodedData, beepType.getValue()); + + return encodedData; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfigurationFactory.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfigurationFactory.java new file mode 100644 index 00000000000..400fe18875b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertConfigurationFactory.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import org.joda.time.Duration; + +public class AlertConfigurationFactory { + public static AlertConfiguration createLowReservoirAlertConfiguration(Double units) { + return new AlertConfiguration(AlertType.LOW_RESERVOIR_ALERT, AlertSlot.SLOT4, true, false, Duration.ZERO, + new UnitsRemainingAlertTrigger(units), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_60_MINUTES); + } + + public static AlertConfiguration createExpirationAdvisoryAlertConfiguration(Duration timeUntilAlert, Duration duration) { + return new AlertConfiguration(AlertType.EXPIRATION_ADVISORY_ALERT, AlertSlot.SLOT7, true, false, duration, + new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES); + } + + public static AlertConfiguration createShutdownImminentAlertConfiguration(Duration timeUntilAlert) { + return new AlertConfiguration(AlertType.SHUTDOWN_IMMINENT_ALARM, AlertSlot.SLOT2, true, false, Duration.ZERO, + new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_15_MINUTES); + } + + public static AlertConfiguration createAutoOffAlertConfiguration(boolean active, Duration countdownDuration) { + return new AlertConfiguration(AlertType.AUTO_OFF_ALARM, AlertSlot.SLOT0, active, true, + Duration.standardMinutes(15), new TimerAlertTrigger(countdownDuration), + BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_15_MINUTES); + } + + public static AlertConfiguration createFinishSetupReminderAlertConfiguration() { + return new AlertConfiguration(AlertType.FINISH_SETUP_REMINDER, AlertSlot.SLOT7, true, false, + Duration.standardMinutes(55), new TimerAlertTrigger(Duration.standardMinutes(5)), + BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_5_MINUTES); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java new file mode 100644 index 00000000000..9d65d7d5aa1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSet.java @@ -0,0 +1,44 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import java.util.ArrayList; +import java.util.List; + +public class AlertSet { + private final List alertSlots; + + public AlertSet(byte rawValue) { + alertSlots = new ArrayList<>(); + for (AlertSlot alertSlot : AlertSlot.values()) { + if ((alertSlot.getBitMaskValue() & rawValue) != 0) { + alertSlots.add(alertSlot); + } + } + } + + public AlertSet(List alertSlots) { + this.alertSlots = alertSlots; + } + + public List getAlertSlots() { + return new ArrayList<>(alertSlots); + } + + public int size() { + return alertSlots.size(); + } + + public byte getRawValue() { + byte value = 0; + for (AlertSlot alertSlot : alertSlots) { + value |= alertSlot.getBitMaskValue(); + } + return value; + } + + @Override + public String toString() { + return "AlertSet{" + + "alertSlots=" + alertSlots + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSlot.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSlot.java new file mode 100644 index 00000000000..9f76ae0983c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertSlot.java @@ -0,0 +1,35 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum AlertSlot { + SLOT0((byte) 0x00), + SLOT1((byte) 0x01), + SLOT2((byte) 0x02), + SLOT3((byte) 0x03), + SLOT4((byte) 0x04), + SLOT5((byte) 0x05), + SLOT6((byte) 0x06), + SLOT7((byte) 0x07); + + private byte value; + + AlertSlot(byte value) { + this.value = value; + } + + public static AlertSlot fromByte(byte value) { + for (AlertSlot type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown AlertSlot: " + value); + } + + public byte getBitMaskValue() { + return (byte) (1 << value); + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertTrigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertTrigger.java new file mode 100644 index 00000000000..1dedb458d0f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertTrigger.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public abstract class AlertTrigger { + protected T value; + + public AlertTrigger(T value) { + this.value = value; + } + + public T getValue() { + return value; + } +} + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertType.java new file mode 100644 index 00000000000..eb4b068a6f7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/AlertType.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum AlertType { + FINISH_PAIRING_REMINDER, + FINISH_SETUP_REMINDER, + EXPIRATION_ALERT, + EXPIRATION_ADVISORY_ALERT, + SHUTDOWN_IMMINENT_ALARM, + LOW_RESERVOIR_ALERT, + AUTO_OFF_ALARM +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepConfigType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepConfigType.java new file mode 100644 index 00000000000..3ef731e5c8b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepConfigType.java @@ -0,0 +1,41 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + + +// BeepConfigType is used only for the $1E Beep Config Command. +public enum BeepConfigType { + // 0x0 always returns an error response for Beep Config (use 0xF for no beep) + BEEP_BEEP_BEEP_BEEP((byte) 0x01), + BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP((byte) 0x02), + BIP_BIP((byte) 0x03), + BEEP((byte) 0x04), + BEEP_BEEP_BEEP((byte) 0x05), + BEEEEEEP((byte) 0x06), + BIP_BIP_BIP_BIP_BIP_BIP((byte) 0x07), + BEEEP_BEEEP((byte) 0x08), + // 0x9 and 0xA always return an error response for Beep Config + BEEP_BEEP((byte) 0xB), + BEEEP((byte) 0xC), + BIP_BEEEEEP((byte) 0xD), + FIVE_SECONDS_BEEP((byte) 0xE), // can only be used if Pod is currently suspended + NO_BEEP((byte) 0xF); + + private byte value; + + BeepConfigType(byte value) { + this.value = value; + } + + public static BeepConfigType fromByte(byte value) { + for (BeepConfigType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown BeepConfigType: " + value); + } + + public byte getValue() { + return value; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepRepeat.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepRepeat.java new file mode 100644 index 00000000000..3562dd6c189 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepRepeat.java @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum BeepRepeat { + ONCE((byte) 0x00), + EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_60_MINUTES((byte) 0x01), + EVERY_MINUTE_FOR_15_MINUTES((byte) 0x02), + EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES((byte) 0x03), + EVERY_3_MINUTES_DELAYED((byte) 0x04), + EVERY_60_MINUTES((byte) 0x05), + EVERY_15_MINUTES((byte) 0x06), + EVERY_15_MINUTES_DELAYED((byte) 0x07), + EVERY_5_MINUTES((byte) 0x08); + + private byte value; + + BeepRepeat(byte value) { + this.value = value; + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepType.java new file mode 100644 index 00000000000..e2302764675 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/BeepType.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +// BeepType is used for the $19 Configure Alerts and $1F Cancel Commands +public enum BeepType { + NO_BEEP((byte) 0x00), + BEEP_BEEP_BEEP_BEEP((byte) 0x01), + BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP((byte) 0x02), + BIP_BIP((byte) 0x03), + BEEP((byte) 0x04), + BEEP_BEEP_BEEP((byte) 0x05), + BEEEEEEP((byte) 0x06), + BIP_BIP_BIP_BIP_BIP_BIP((byte) 0x07), + BEEEP_BEEEP((byte) 0x08); + + private byte value; + + BeepType(byte value) { + this.value = value; + } + + public static BeepType fromByte(byte value) { + for (BeepType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown BeepType: " + value); + } + + public byte getValue() { + return value; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryStatus.java new file mode 100644 index 00000000000..9df1d480a56 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryStatus.java @@ -0,0 +1,33 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum DeliveryStatus { + SUSPENDED((byte) 0x00), + NORMAL((byte) 0x01), + TEMP_BASAL_RUNNING((byte) 0x02), + PRIMING((byte) 0x04), + BOLUS_IN_PROGRESS((byte) 0x05), + BOLUS_AND_TEMP_BASAL((byte) 0x06); + + private byte value; + + DeliveryStatus(byte value) { + this.value = value; + } + + public static DeliveryStatus fromByte(byte value) { + for (DeliveryStatus type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown DeliveryStatus: " + value); + } + + public byte getValue() { + return value; + } + + public boolean isBolusing() { + return this.equals(BOLUS_IN_PROGRESS) || this.equals(BOLUS_AND_TEMP_BASAL); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryType.java new file mode 100644 index 00000000000..4d595afc159 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/DeliveryType.java @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum DeliveryType { + NONE((byte) 0x00), + BASAL((byte) 0x01), + TEMP_BASAL((byte) 0x02), + BOLUS((byte) 0x04); + + private byte value; + + DeliveryType(byte value) { + this.value = value; + } + + public static DeliveryType fromByte(byte value) { + for (DeliveryType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown DeliveryType: " + value); + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/ErrorResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/ErrorResponseType.java new file mode 100644 index 00000000000..883ae948215 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/ErrorResponseType.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum ErrorResponseType { + BAD_NONCE((byte) 0x14); + + private byte value; + + ErrorResponseType(byte value) { + this.value = value; + } + + public static ErrorResponseType fromByte(byte value) { + for (ErrorResponseType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown ErrorResponseType: " + value); + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FaultEventType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FaultEventType.java new file mode 100644 index 00000000000..69a74aa4c4c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FaultEventType.java @@ -0,0 +1,148 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import java.util.Locale; + +public enum FaultEventType { + NO_FAULTS((byte) 0x00), + FAILED_FLASH_ERASE((byte) 0x01), + FAILED_FLASH_STORE((byte) 0x02), + TABLE_CORRUPTION_BASAL_SUBCOMMAND((byte) 0x03), + CORRUPTION_BYTE_720((byte) 0x05), + DATA_CORRUPTION_IN_TEST_RTC_INTERRUPT((byte) 0x06), + RTC_INTERRUPT_HANDLER_INCONSISTENT_STATE((byte) 0x07), + VALUE_GREATER_THAN_8((byte) 0x08), + BF_0_NOT_EQUAL_TO_BF_1((byte) 0x0A), + TABLE_CORRUPTION_TEMP_BASAL_SUBCOMMAND((byte) 0x0B), + RESET_DUE_TO_COP((byte) 0x0D), + RESET_DUE_TO_ILLEGAL_OPCODE((byte) 0x0E), + RESET_DUE_TO_ILLEGAL_ADDRESS((byte) 0x0F), + RESET_DUE_TO_SAWCOP((byte) 0x10), + CORRUPTION_IN_BYTE_866((byte) 0x11), + RESET_DUE_TO_LVD((byte) 0x12), + MESSAGE_LENGTH_TOO_LONG((byte) 0x13), + OCCLUDED((byte) 0x14), + CORRUPTION_IN_WORD_129((byte) 0x15), + CORRUPTION_IN_BYTE_868((byte) 0x16), + CORRUPTION_IN_A_VALIDATED_TABLE((byte) 0x17), + RESERVOIR_EMPTY((byte) 0x18), + BAD_POWER_SWITCH_ARRAY_VALUE_1((byte) 0x19), + BAD_POWER_SWITCH_ARRAY_VALUE_2((byte) 0x1A), + BAD_LOAD_CNTH_VALUE((byte) 0x1B), + EXCEEDED_MAXIMUM_POD_LIFE_80_HRS((byte) 0x1C), + BAD_STATE_COMMAND_1_A_SCHEDULE_PARSE((byte) 0x1D), + UNEXPECTED_STATE_IN_REGISTER_UPON_RESET((byte) 0x1E), + WRONG_SUMMARY_FOR_TABLE_129((byte) 0x1F), + VALIDATE_COUNT_ERROR_WHEN_BOLUSING((byte) 0x20), + BAD_TIMER_VARIABLE_STATE((byte) 0x21), + UNEXPECTED_RTC_MODULE_VALUE_DURING_RESET((byte) 0x22), + PROBLEM_CALIBRATE_TIMER((byte) 0x23), + RTC_INTERRUPT_HANDLER_UNEXPECTED_CALL((byte) 0x26), + MISSING_2_HOUR_ALERT_TO_FILL_TANK((byte) 0x27), + FAULT_EVENT_SETUP_POD((byte) 0x28), + ERROR_MAIN_LOOP_HELPER_0((byte) 0x29), + ERROR_MAIN_LOOP_HELPER_1((byte) 0x2A), + ERROR_MAIN_LOOP_HELPER_2((byte) 0x2B), + ERROR_MAIN_LOOP_HELPER_3((byte) 0x2C), + ERROR_MAIN_LOOP_HELPER_4((byte) 0x2D), + ERROR_MAIN_LOOP_HELPER_5((byte) 0x2E), + ERROR_MAIN_LOOP_HELPER_6((byte) 0x2F), + ERROR_MAIN_LOOP_HELPER_7((byte) 0x30), + INSULIN_DELIVERY_COMMAND_ERROR((byte) 0x31), + BAD_VALUE_STARTUP_TEST((byte) 0x32), + CONNECTED_POD_COMMAND_TIMEOUT((byte) 0x33), + RESET_FROM_UNKNOWN_CAUSE((byte) 0x34), + ERROR_FLASH_INITIALIZATION((byte) 0x36), + BAD_PIEZO_VALUE((byte) 0x37), + UNEXPECTED_VALUE_BYTE_358((byte) 0x38), + PROBLEM_WITH_LOAD_1_AND_2((byte) 0x39), + A_GREATER_THAN_7_IN_MESSAGE((byte) 0x3A), + FAILED_TEST_SAW_RESET((byte) 0x3B), + TEST_IN_PROGRESS((byte) 0x3C), + PROBLEM_WITH_PUMP_ANCHOR((byte) 0x3D), + ERROR_FLASH_WRITE((byte) 0x3E), + ENCODER_COUNT_TOO_HIGH((byte) 0x40), + ENCODER_COUNT_EXCESSIVE_VARIANCE((byte) 0x41), + ENCODER_COUNT_TOO_LOW((byte) 0x42), + ENCODER_COUNT_PROBLEM((byte) 0x43), + CHECK_VOLTAGE_OPEN_WIRE_1((byte) 0x44), + CHECK_VOLTAGE_OPEN_WIRE_2((byte) 0x45), + PROBLEM_WITH_LOAD_1_AND_2_TYPE_46((byte) 0x46), + PROBLEM_WITH_LOAD_1_AND_2_TYPE_47((byte) 0x47), + BAD_TIMER_CALIBRATION((byte) 0x48), + BAD_TIMER_RATIOS((byte) 0x49), + BAD_TIMER_VALUES((byte) 0x4A), + TRIM_ICS_TOO_CLOSE_TO_0_X_1_FF((byte) 0x4B), + PROBLEM_FINDING_BEST_TRIM_VALUE((byte) 0x4C), + BAD_SET_TPM_1_MULTI_CASES_VALUE((byte) 0x4D), + UNEXPECTED_RF_ERROR_FLAG_DURING_RESET((byte) 0x4F), + BAD_CHECK_SDRH_AND_BYTE_11_F_STATE((byte) 0x51), + ISSUE_TXO_KPROCESS_INPUT_BUFFER((byte) 0x52), + WRONG_VALUE_WORD_107((byte) 0x53), + PACKET_FRAME_LENGTH_TOO_LONG((byte) 0x54), + UNEXPECTED_IRQ_HIGHIN_TIMER_TICK((byte) 0x55), + UNEXPECTED_IRQ_LOWIN_TIMER_TICK((byte) 0x56), + BAD_ARG_TO_GET_ENTRY((byte) 0x57), + BAD_ARG_TO_UPDATE_37_A_TABLE((byte) 0x58), + ERROR_UPDATING_37_A_TABLE((byte) 0x59), + OCCLUSION_CHECK_VALUE_TOO_HIGH((byte) 0x5A), + LOAD_TABLE_CORRUPTION((byte) 0x5B), + PRIME_OPEN_COUNT_TOO_LOW((byte) 0x5C), + BAD_VALUE_BYTE_109((byte) 0x5D), + DISABLE_FLASH_SECURITY_FAILED((byte) 0x5E), + CHECK_VOLTAGE_FAILURE((byte) 0x5F), + OCCLUSION_CHECK_STARTUP_1((byte) 0x60), + OCCLUSION_CHECK_STARTUP_2((byte) 0x61), + OCCLUSION_CHECK_TIMEOUTS_1((byte) 0x62), + OCCLUSION_CHECK_TIMEOUTS_2((byte) 0x66), + OCCLUSION_CHECK_TIMEOUTS_3((byte) 0x67), + OCCLUSION_CHECK_PULSE_ISSUE((byte) 0x68), + OCCLUSION_CHECK_BOLUS_PROBLEM((byte) 0x69), + OCCLUSION_CHECK_ABOVE_THRESHOLD((byte) 0x6A), + BASAL_UNDER_INFUSION((byte) 0x80), + BASAL_OVER_INFUSION((byte) 0x81), + TEMP_BASAL_UNDER_INFUSION((byte) 0x82), + TEMP_BASAL_OVER_INFUSION((byte) 0x83), + BOLUS_UNDER_INFUSION((byte) 0x84), + BOLUS_OVER_INFUSION((byte) 0x85), + BASAL_OVER_INFUSION_PULSE((byte) 0x86), + TEMP_BASAL_OVER_INFUSION_PULSE((byte) 0x87), + BOLUS_OVER_INFUSION_PULSE((byte) 0x88), + IMMEDIATE_BOLUS_OVER_INFUSION_PULSE((byte) 0x89), + EXTENDED_BOLUS_OVER_INFUSION_PULSE((byte) 0x8A), + CORRUPTION_OF_TABLES((byte) 0x8B), + BAD_INPUT_TO_VERIFY_AND_START_PUMP((byte) 0x8D), + BAD_PUMP_REQ_5_STATE((byte) 0x8E), + COMMAND_1_A_PARSE_UNEXPECTED_FAILED((byte) 0x8F), + BAD_VALUE_FOR_TABLES((byte) 0x90), + BAD_PUMP_REQ_1_STATE((byte) 0x91), + BAD_PUMP_REQ_2_STATE((byte) 0x92), + BAD_PUMP_REQ_3_STATE((byte) 0x93), + BAD_VALUE_FIELD_6_IN_0_X_1_A((byte) 0x95), + BAD_STATE_IN_CLEAR_BOLUS_IST_2_AND_VARS((byte) 0x96), + BAD_STATE_IN_MAYBE_INC_33_D((byte) 0x97), + VALUES_DO_NOT_MATCH_OR_ARE_GREATER_THAN_0_X_97((byte) 0x98); + + private byte value; + + FaultEventType(byte value) { + this.value = value; + } + + public static FaultEventType fromByte(byte value) { + for (FaultEventType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown FaultEventType: " + value); + } + + public byte getValue() { + return value; + } + + @Override + public String toString() { + return String.format(Locale.getDefault(), "Pod fault (%d): %s", value, name()); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FirmwareVersion.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FirmwareVersion.java new file mode 100644 index 00000000000..27dab2c091f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/FirmwareVersion.java @@ -0,0 +1,32 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import java.util.Locale; + +public class FirmwareVersion { + private final int major; + private final int minor; + private final int patch; + + public FirmwareVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + @Override + public String toString() { + return String.format(Locale.getDefault(), "%d.%d.%d", major, minor, patch); + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/LogEventErrorCode.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/LogEventErrorCode.java new file mode 100644 index 00000000000..1cf366fd61c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/LogEventErrorCode.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum LogEventErrorCode { + NONE((byte) 0x00), + IMMEDIATE_BOLUS_IN_PROGRESS((byte) 0x01), + INTERNAL_2_BIT_VARIABLE_SET_AND_MANIPULATED_IN_MAIN_LOOP_ROUTINES_2((byte) 0x02), + INTERNAL_2_BIT_VARIABLE_SET_AND_MANIPULATED_IN_MAIN_LOOP_ROUTINES_3((byte) 0x03), + INSULIN_STATE_TABLE_CORRUPTION((byte) 0x04); + + private byte value; + + LogEventErrorCode(byte value) { + this.value = value; + } + + public static LogEventErrorCode fromByte(byte value) { + for (LogEventErrorCode type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown LogEventErrorCode: " + value); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/MessageBlockType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/MessageBlockType.java new file mode 100644 index 00000000000..a7ced693057 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/MessageBlockType.java @@ -0,0 +1,63 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import org.apache.commons.lang3.NotImplementedException; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.MessageBlock; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.VersionResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; + +public enum MessageBlockType { + VERSION_RESPONSE(0x01), + POD_INFO_RESPONSE(0x02), + SETUP_POD(0x03), + ERROR_RESPONSE(0x06), + ASSIGN_ADDRESS(0x07), + FAULT_CONFIG(0x08), + GET_STATUS(0x0e), + ACKNOWLEDGE_ALERT(0x11), + BASAL_SCHEDULE_EXTRA(0x13), + TEMP_BASAL_EXTRA(0x16), + BOLUS_EXTRA(0x17), + CONFIGURE_ALERTS(0x19), + SET_INSULIN_SCHEDULE(0x1a), + DEACTIVATE_POD(0x1c), + STATUS_RESPONSE(0x1d), + BEEP_CONFIG(0x1e), + CANCEL_DELIVERY(0x1f); + + private byte value; + + MessageBlockType(int value) { + this.value = (byte) value; + } + + public static MessageBlockType fromByte(byte value) { + for (MessageBlockType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown MessageBlockType: " + value); + } + + public byte getValue() { + return value; + } + + public MessageBlock decode(byte[] encodedData) { + switch (this) { + case VERSION_RESPONSE: + return new VersionResponse(encodedData); + case ERROR_RESPONSE: + return new ErrorResponse(encodedData); + case POD_INFO_RESPONSE: + return new PodInfoResponse(encodedData); + case STATUS_RESPONSE: + return new StatusResponse(encodedData); + default: + throw new NotImplementedException(this.name()); + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/NonceState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/NonceState.java new file mode 100644 index 00000000000..4698f88266d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/NonceState.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import java.util.Arrays; + +public class NonceState { + private final long[] table = new long[21]; + private int index; + + public NonceState(int lot, int tid) { + initializeTable(lot, tid, (byte) 0x00); + } + + public NonceState(int lot, int tid, byte seed) { + initializeTable(lot, tid, seed); + } + + private void initializeTable(int lot, int tid, byte seed) { + table[0] = (long) (lot & 0xFFFF) + 0x55543DC3L + (((long) (lot) & 0xFFFFFFFFL) >> 16); + table[0] = table[0] & 0xFFFFFFFFL; + table[1] = (tid & 0xFFFF) + 0xAAAAE44EL + (((long) (tid) & 0xFFFFFFFFL) >> 16); + table[1] = table[1] & 0xFFFFFFFFL; + index = 0; + table[0] += seed; + for (int i = 0; i < 16; i++) { + table[2 + i] = generateEntry(); + } + index = (int) ((table[0] + table[1]) & 0X0F); + } + + private int generateEntry() { + table[0] = (((table[0] >> 16) + (table[0] & 0xFFFF) * 0x5D7FL) & 0xFFFFFFFFL); + table[1] = (((table[1] >> 16) + (table[1] & 0xFFFF) * 0x8CA0L) & 0xFFFFFFFFL); + return (int) ((table[1] + (table[0] << 16)) & 0xFFFFFFFFL); + } + + public int getCurrentNonce() { + return (int) table[(2 + index)]; + } + + public void advanceToNextNonce() { + int nonce = getCurrentNonce(); + table[(2 + index)] = generateEntry(); + index = (nonce & 0x0F); + } + + @Override + public String toString() { + return "NonceState{" + + "table=" + Arrays.toString(table) + + ", index=" + index + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java new file mode 100644 index 00000000000..bb0d40ef59c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommandType.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +/** + * Created by andy on 4.8.2019 + */ +public enum OmnipodCommandType { + + PairAndPrimePod, // + FillCanulaAndSetBasalProfile, // + //InitPod, // + DeactivatePod, // + SetBasalProfile, // + SetBolus, // + CancelBolus, // + SetTemporaryBasal, // + CancelTemporaryBasal, // + ResetPodStatus, // + GetPodStatus, // + SetTime, // + AcknowledgeAlerts, // + GetPodPulseLog; + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java new file mode 100644 index 00000000000..3999cd29234 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCommunicationManagerInterface.java @@ -0,0 +1,79 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; + +public interface OmnipodCommunicationManagerInterface { + + // TODO add methods that can be used by OmniPod Eros and Omnipod Dash + + /** + * Initialize Pod + */ + PumpEnactResult initPod(PodInitActionType podInitActionType, PodInitReceiver podInitReceiver, Profile profile); + + /** + * Get Pod Status (is pod running, battery left ?, reservoir, etc) + */ + // TODO we should probably return a (wrapped) StatusResponse instead of a PumpEnactResult + PumpEnactResult getPodStatus(); + + /** + * Deactivate Pod + */ + PumpEnactResult deactivatePod(PodInitReceiver podInitReceiver); + + /** + * Set Basal Profile + */ + PumpEnactResult setBasalProfile(Profile basalProfile); + + /** + * Reset Pod status (if we forget to disconnect Pod and want to init new pod, and want to forget current pod) + */ + PumpEnactResult resetPodStatus(); + + /** + * Set Bolus + * + * @param detailedBolusInfo DetailedBolusInfo instance with amount and all other required data + */ + PumpEnactResult setBolus(DetailedBolusInfo detailedBolusInfo); + + /** + * Cancel Bolus (if bolus is already stopped, return acknowledgment) + */ + PumpEnactResult cancelBolus(); + + /** + * Set Temporary Basal + * + * @param tempBasalPair TempBasalPair object containg amount and duration in minutes + */ + PumpEnactResult setTemporaryBasal(TempBasalPair tempBasalPair); + + /** + * Cancel Temporary Basal (if TB is already stopped, return acknowledgment) + */ + PumpEnactResult cancelTemporaryBasal(); + + /** + * Acknowledge alerts + */ + PumpEnactResult acknowledgeAlerts(); + + /** + * Set Time on Pod + */ + PumpEnactResult setTime(); + + + void setPumpStatus(OmnipodPumpStatus pumpStatusLocal); + + + PodInfoRecentPulseLog readPulseLog(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java new file mode 100644 index 00000000000..edf7181f18e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodCustomActionType.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; + +/** + * Created by andy on 4.8.2019 + */ + +public enum OmnipodCustomActionType implements CustomActionType { + + ResetRileyLinkConfiguration(), // + PairAndPrime(), // + FillCanulaSetBasalProfile(), // + //InitPod(), // + DeactivatePod(), // + ResetPodStatus(), // + ; + + @Override + public String getKey() { + return this.name(); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPodType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPodType.java new file mode 100644 index 00000000000..849a22e5b0c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPodType.java @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum OmnipodPodType { + Eros, // + Dash +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPumpPluginInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPumpPluginInterface.java new file mode 100644 index 00000000000..8a97ebbbd33 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodPumpPluginInterface.java @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState; + +public interface OmnipodPumpPluginInterface extends PumpInterface { + + void addPodStatusRequest(OmnipodStatusRequest pumpStatusRequest); + + void setDriverState(OmnipodDriverState state); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodStatusRequest.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodStatusRequest.java new file mode 100644 index 00000000000..80f7e4ecfc3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodStatusRequest.java @@ -0,0 +1,20 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum OmnipodStatusRequest { + ResetState(OmnipodCommandType.ResetPodStatus), // + AcknowledgeAlerts(OmnipodCommandType.AcknowledgeAlerts), // + GetPodState(OmnipodCommandType.GetPodStatus), // + GetPodPulseLog(OmnipodCommandType.GetPodPulseLog) + ; + + private OmnipodCommandType commandType; + + OmnipodStatusRequest(OmnipodCommandType commandType) { + this.commandType = commandType; + } + + + public OmnipodCommandType getCommandType() { + return commandType; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java new file mode 100644 index 00000000000..b135c3f590c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/OmnipodUIResponseType.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +/** + * Created by andy on 10/18/18. + */ + +public enum OmnipodUIResponseType { + + Data, + Error, + Invalid; + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PacketType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PacketType.java new file mode 100644 index 00000000000..aae862d9b89 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PacketType.java @@ -0,0 +1,42 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum PacketType { + INVALID((byte) 0), + POD((byte) 0b111), + PDM((byte) 0b101), + CON((byte) 0b100), + ACK((byte) 0b010); + + private byte value; + + PacketType(byte value) { + this.value = value; + } + + public static PacketType fromByte(byte value) { + for (PacketType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown PacketType: " + value); + } + + public int getMaxBodyLength() { + switch (this) { + case ACK: + return 4; + case CON: + case PDM: + case POD: + return 31; + default: + return 0; + } + } + + public byte getValue() { + return value; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java new file mode 100644 index 00000000000..5e903b5ae49 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodDeviceState.java @@ -0,0 +1,38 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 4.8.2019 + */ + +public enum PodDeviceState { + + // FIXME + NeverContacted(R.string.medtronic_pump_status_never_contacted), // + Sleeping(R.string.medtronic_pump_status_sleeping), // + WakingUp(R.string.medtronic_pump_status_waking_up), // + Active(R.string.medtronic_pump_status_active), // + ErrorWhenCommunicating(R.string.medtronic_pump_status_error_comm), // + TimeoutWhenCommunicating(R.string.medtronic_pump_status_timeout_comm), // + // ProblemContacting(R.string.medtronic_pump_status_problem_contacting), // + PumpUnreachable(R.string.medtronic_pump_status_pump_unreachable), // + InvalidConfiguration(R.string.medtronic_pump_status_invalid_config); + + Integer resourceId = null; + + + PodDeviceState() { + + } + + + PodDeviceState(int resourceId) { + this.resourceId = resourceId; + } + + + public Integer getResourceId() { + return resourceId; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInfoType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInfoType.java new file mode 100644 index 00000000000..3ff630ea6ad --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInfoType.java @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfo; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoActiveAlerts; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoDataLog; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultAndInitializationTime; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoLowFlashLogDump; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoOlderPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoTestValues; + +public enum PodInfoType { + NORMAL((byte) 0x00), + ACTIVE_ALERTS((byte) 0x01), + FAULT_EVENT((byte) 0x02), + DATA_LOG((byte) 0x03), // Similar to types $50 & $51. Returns up to the last 60 dwords of data. + FAULT_AND_INITIALIZATION_TIME((byte) 0x05), + HARDCODED_TEST_VALUES((byte) 0x06), + LOW_FLASH_DUMP_LOG((byte) 0x46), // Starting at $4000 + RECENT_PULSE_LOG((byte) 0x50), // Starting at $4200 + OLDER_PULSE_LOG((byte) 0x51); // Starting at $4200 but dumps entries before the last 50 + + private final byte value; + + PodInfoType(byte value) { + this.value = value; + } + + public static PodInfoType fromByte(byte value) { + for (PodInfoType type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown PodInfoType: " + value); + } + + public byte getValue() { + return value; + } + + public PodInfo decode(byte[] encodedData, int bodyLength) { + switch (this) { + case NORMAL: + // We've never observed a PodInfoResponse with 0x00 subtype + // Instead, the pod returns a StatusResponse + throw new UnsupportedOperationException("Cannot decode PodInfoType.NORMAL"); + case ACTIVE_ALERTS: + return new PodInfoActiveAlerts(encodedData); + case FAULT_EVENT: + return new PodInfoFaultEvent(encodedData); + case DATA_LOG: + return new PodInfoDataLog(encodedData, bodyLength); + case FAULT_AND_INITIALIZATION_TIME: + return new PodInfoFaultAndInitializationTime(encodedData); + case HARDCODED_TEST_VALUES: + return new PodInfoTestValues(encodedData); + case LOW_FLASH_DUMP_LOG: + return new PodInfoLowFlashLogDump(encodedData); + case RECENT_PULSE_LOG: + return new PodInfoRecentPulseLog(encodedData, bodyLength); + case OLDER_PULSE_LOG: + return new PodInfoOlderPulseLog(encodedData); + default: + throw new IllegalArgumentException("Cannot decode " + this.name()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitActionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitActionType.java new file mode 100644 index 00000000000..c0a92ed2d74 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitActionType.java @@ -0,0 +1,88 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import info.nightscout.androidaps.R; + +public enum PodInitActionType { + + PairAndPrimeWizardStep(), // + PairPod(R.string.omnipod_init_pod_pair_pod, PairAndPrimeWizardStep), // + PrimePod(R.string.omnipod_init_pod_prime_pod, PairAndPrimeWizardStep), // + + FillCannulaSetBasalProfileWizardStep(), // + FillCannula(R.string.omnipod_init_pod_fill_cannula, FillCannulaSetBasalProfileWizardStep), // + SetBasalProfile(R.string.omnipod_init_pod_set_basal_profile, FillCannulaSetBasalProfileWizardStep), // + + DeactivatePodWizardStep(), // + CancelDelivery(R.string.omnipod_deactivate_pod_cancel_delivery, DeactivatePodWizardStep), // + DeactivatePod(R.string.omnipod_deactivate_pod_deactivate_pod, DeactivatePodWizardStep) // + ; + + + + private int resourceId; + private PodInitActionType parent; + + private static Map> stepsForWizardStep; + + + PodInitActionType(int resourceId, PodInitActionType parent) { + this.resourceId = resourceId; + this.parent = parent; + } + + + PodInitActionType() { + } + + + public boolean isParent() { + return this.parent == null; + } + + + public List getChildren() { + + List outList = new ArrayList<>(); + + for (PodInitActionType value : values()) { + if (value.parent == this) { + outList.add(value); + } + } + + return outList; + } + + + public static List getAvailableWizardSteps(OmnipodPodType podType) { + List outList = new ArrayList<>(); + + if (podType == OmnipodPodType.Eros) { + outList.add(PodInitActionType.PairAndPrimeWizardStep); + outList.add(PodInitActionType.FillCannulaSetBasalProfileWizardStep); + } else { + // TODO we might have different wizard steps, with different handling for Dash + } + + return outList; + } + + + public static List getAvailableActionsForWizardSteps(PodInitActionType wizardStep) { + if (stepsForWizardStep.containsKey(wizardStep)) { + return stepsForWizardStep.get(wizardStep); + } else { + return new ArrayList<>(); + } + } + + + public int getResourceId() { + return resourceId; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitReceiver.java new file mode 100644 index 00000000000..789da1add21 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodInitReceiver.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public interface PodInitReceiver { + + void returnInitTaskStatus(PodInitActionType podInitActionType, boolean isSuccess, String errorMessage); + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodProgressStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodProgressStatus.java new file mode 100644 index 00000000000..4bd9780118b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodProgressStatus.java @@ -0,0 +1,43 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum PodProgressStatus { + INITIAL_VALUE((byte) 0x00), + TANK_POWER_ACTIVATED((byte) 0x01), + TANK_FILL_COMPLETED((byte) 0x02), + PAIRING_SUCCESS((byte) 0x03), + PRIMING((byte) 0x04), + READY_FOR_BASAL_SCHEDULE((byte) 0x05), + READY_FOR_CANNULA_INSERTION((byte) 0x06), + CANNULA_INSERTING((byte) 0x07), + RUNNING_ABOVE_FIFTY_UNITS((byte) 0x08), + RUNNING_BELOW_FIFTY_UNITS((byte) 0x09), + ONE_NOT_USED_BUT_IN_33((byte) 0x0a), + TWO_NOT_USED_BUT_IN_33((byte) 0x0b), + THREE_NOT_USED_BUT_IN_33((byte) 0x0c), + FAULT_EVENT_OCCURRED((byte) 0x0d), + FAILED_TO_INITIALIZE_IN_TIME((byte) 0x0e), + INACTIVE((byte) 0x0f); + + private byte value; + + PodProgressStatus(byte value) { + this.value = value; + } + + public static PodProgressStatus fromByte(byte value) { + for (PodProgressStatus type : values()) { + if (type.value == value) { + return type; + } + } + throw new IllegalArgumentException("Unknown PodProgressStatus: " + value); + } + + public byte getValue() { + return value; + } + + public boolean isReadyForDelivery() { + return this == RUNNING_ABOVE_FIFTY_UNITS || this == RUNNING_BELOW_FIFTY_UNITS; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java new file mode 100644 index 00000000000..7314ae95919 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/PodResponseType.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum PodResponseType { + + Acknowledgment, // set commands would just acknowledge if data was sent + Data, // query commands would return data + Error, // communication/response produced an error + Invalid // invalid response (not supported, should never be returned) +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/SetupProgress.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/SetupProgress.java new file mode 100644 index 00000000000..72a3b5438a2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/SetupProgress.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public enum SetupProgress { + ADDRESS_ASSIGNED, + POD_CONFIGURED, + STARTING_PRIME, + PRIMING, + PRIMING_FINISHED, + INITIAL_BASAL_SCHEDULE_SET, + STARTING_INSERT_CANNULA, + CANNULA_INSERTING, + COMPLETED; + + public boolean isBefore(SetupProgress other) { + return this.ordinal() < other.ordinal(); + } + + public boolean isAfter(SetupProgress other) { + return this.ordinal() > other.ordinal(); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/TimerAlertTrigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/TimerAlertTrigger.java new file mode 100644 index 00000000000..8d4096de594 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/TimerAlertTrigger.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +import org.joda.time.Duration; + +public class TimerAlertTrigger extends AlertTrigger { + public TimerAlertTrigger(Duration value) { + super(value); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/UnitsRemainingAlertTrigger.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/UnitsRemainingAlertTrigger.java new file mode 100644 index 00000000000..6668be13c83 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/UnitsRemainingAlertTrigger.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs; + +public class UnitsRemainingAlertTrigger extends AlertTrigger { + public UnitsRemainingAlertTrigger(Double value) { + super(value); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliverySchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliverySchedule.java new file mode 100644 index 00000000000..ab46dacd760 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliverySchedule.java @@ -0,0 +1,61 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; + +public class BasalDeliverySchedule extends DeliverySchedule implements IRawRepresentable { + + private final byte currentSegment; + private final int secondsRemaining; + private final int pulsesRemaining; + private final BasalDeliveryTable basalTable; + + public BasalDeliverySchedule(byte currentSegment, int secondsRemaining, int pulsesRemaining, + BasalDeliveryTable basalTable) { + this.currentSegment = currentSegment; + this.secondsRemaining = secondsRemaining; + this.pulsesRemaining = pulsesRemaining; + this.basalTable = basalTable; + } + + @Override + public byte[] getRawData() { + byte[] rawData = new byte[0]; + rawData = ByteUtil.concat(rawData, currentSegment); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(secondsRemaining << 3)); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulsesRemaining)); + for (BasalTableEntry entry : basalTable.getEntries()) { + rawData = ByteUtil.concat(rawData, entry.getRawData()); + } + return rawData; + } + + @Override + public InsulinScheduleType getType() { + return InsulinScheduleType.BASAL_SCHEDULE; + } + + @Override + public int getChecksum() { + int checksum = 0; + byte[] rawData = getRawData(); + for (int i = 0; i < rawData.length && i < 5; i++) { + checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]); + } + for (BasalTableEntry entry : basalTable.getEntries()) { + checksum += entry.getChecksum(); + } + + return checksum; + } + + @Override + public String toString() { + return "BasalDeliverySchedule{" + + "currentSegment=" + currentSegment + + ", secondsRemaining=" + secondsRemaining + + ", pulsesRemaining=" + pulsesRemaining + + ", basalTable=" + basalTable + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliveryTable.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliveryTable.java new file mode 100644 index 00000000000..3e18d6b5d0e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalDeliveryTable.java @@ -0,0 +1,112 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class BasalDeliveryTable { + + public static final int SEGMENT_DURATION = 30 * 60; + public static final int MAX_PULSES_PER_RATE_ENTRY = 6400; + + private static final int NUM_SEGMENTS = 48; + private static final int MAX_SEGMENTS_PER_ENTRY = 16; + + private List entries = new ArrayList<>(); + + public BasalDeliveryTable(BasalSchedule schedule) { + TempSegment[] expandedSegments = new TempSegment[48]; + + boolean halfPulseRemainder = false; + for (int i = 0; i < NUM_SEGMENTS; i++) { + double rate = schedule.rateAt(Duration.standardMinutes(i * 30)); + int pulsesPerHour = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE); + int pulsesPerSegment = pulsesPerHour >>> 1; + boolean halfPulse = (pulsesPerHour & 0b1) != 0; + + expandedSegments[i] = new TempSegment(pulsesPerSegment + (halfPulseRemainder && halfPulse ? 1 : 0)); + halfPulseRemainder = halfPulseRemainder != halfPulse; + } + + List segmentsToMerge = new ArrayList<>(); + + boolean altSegmentPulse = false; + for (TempSegment segment : expandedSegments) { + if (segmentsToMerge.isEmpty()) { + segmentsToMerge.add(segment); + continue; + } + + TempSegment firstSegment = segmentsToMerge.get(0); + + int delta = segment.getPulses() - firstSegment.getPulses(); + if (segmentsToMerge.size() == 1) { + altSegmentPulse = delta == 1; + } + + int expectedDelta = altSegmentPulse ? segmentsToMerge.size() % 2 : 0; + + if (expectedDelta != delta || segmentsToMerge.size() == MAX_SEGMENTS_PER_ENTRY) { + addBasalTableEntry(segmentsToMerge, altSegmentPulse); + segmentsToMerge.clear(); + } + + segmentsToMerge.add(segment); + } + + addBasalTableEntry(segmentsToMerge, altSegmentPulse); + } + + public BasalDeliveryTable(double tempBasalRate, Duration duration) { + int pulsesPerHour = (int) Math.round(tempBasalRate / OmnipodConst.POD_PULSE_SIZE); + int pulsesPerSegment = pulsesPerHour >> 1; + boolean alternateSegmentPulse = (pulsesPerHour & 0b1) != 0; + + int remaining = (int) Math.round(duration.getStandardSeconds() / (double) BasalDeliveryTable.SEGMENT_DURATION); + + while (remaining > 0) { + int segments = Math.min(MAX_SEGMENTS_PER_ENTRY, remaining); + entries.add(new BasalTableEntry(segments, pulsesPerSegment, segments > 1 && alternateSegmentPulse)); + remaining -= segments; + } + } + + private void addBasalTableEntry(List segments, boolean alternateSegmentPulse) { + entries.add(new BasalTableEntry(segments.size(), segments.get(0).getPulses(), alternateSegmentPulse)); + } + + public BasalTableEntry[] getEntries() { + return entries.toArray(new BasalTableEntry[0]); + } + + byte numSegments() { + byte numSegments = 0; + for (BasalTableEntry entry : entries) { + numSegments += entry.getSegments(); + } + return numSegments; + } + + @Override + public String toString() { + return "BasalDeliveryTable{" + + "entries=" + entries + + '}'; + } + + private class TempSegment { + private int pulses; + + public TempSegment(int pulses) { + this.pulses = pulses; + } + + public int getPulses() { + return pulses; + } + } +} + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java new file mode 100644 index 00000000000..140c584c0bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalSchedule.java @@ -0,0 +1,147 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BasalSchedule { + private final List entries; + + public BasalSchedule(List entries) { + if (entries == null || entries.size() == 0) { + throw new IllegalArgumentException("Entries can not be empty"); + } else if (!entries.get(0).getStartTime().isEqual(Duration.ZERO)) { + throw new IllegalArgumentException("First basal schedule entry should have 0 offset"); + } + this.entries = entries; + } + + public double rateAt(Duration offset) { + return lookup(offset).getBasalScheduleEntry().getRate(); + } + + public List getEntries() { + return new ArrayList<>(entries); + } + + public BasalScheduleLookupResult lookup(Duration offset) { + if (offset.isLongerThan(Duration.standardHours(24)) || offset.isShorterThan(Duration.ZERO)) { + throw new IllegalArgumentException("Invalid duration"); + } + + List reversedBasalScheduleEntries = reversedBasalScheduleEntries(); + + Duration last = Duration.standardHours(24); + int index = 0; + for (BasalScheduleEntry entry : reversedBasalScheduleEntries) { + if (entry.getStartTime().isShorterThan(offset) || entry.getStartTime().equals(offset)) { + return new BasalScheduleLookupResult( // + reversedBasalScheduleEntries.size() - (index + 1), // + entry, // + entry.getStartTime(), // + last.minus(entry.getStartTime())); + } + last = entry.getStartTime(); + index++; + } + + throw new IllegalArgumentException("Basal schedule incomplete"); + } + + private List reversedBasalScheduleEntries() { + List reversedEntries = new ArrayList<>(entries); + Collections.reverse(reversedEntries); + return reversedEntries; + } + + public List adjacentEqualRatesMergedEntries() { + List mergedEntries = new ArrayList<>(); + Double lastRate = null; + for (BasalScheduleEntry entry : entries) { + if (lastRate == null || entry.getRate() != lastRate) { + mergedEntries.add(entry); + } + lastRate = entry.getRate(); + } + return mergedEntries; + } + + public List getDurations() { + List durations = new ArrayList<>(); + Duration last = Duration.standardHours(24); + List basalScheduleEntries = reversedBasalScheduleEntries(); + for (BasalScheduleEntry entry : basalScheduleEntries) { + durations.add(new BasalScheduleDurationEntry( // + entry.getRate(), // + entry.getStartTime(), // + last.minus(entry.getStartTime()))); + last = entry.getStartTime(); + } + + Collections.reverse(durations); + return durations; + } + + @Override + public String toString() { + return "BasalSchedule{" + + "entries=" + entries + + '}'; + } + + public static class BasalScheduleDurationEntry { + private final double rate; + private final Duration duration; + private final Duration startTime; + + public BasalScheduleDurationEntry(double rate, Duration startTime, Duration duration) { + this.rate = rate; + this.duration = duration; + this.startTime = startTime; + } + + public double getRate() { + return rate; + } + + public Duration getDuration() { + return duration; + } + + public Duration getStartTime() { + return startTime; + } + } + + public static class BasalScheduleLookupResult { + private final int index; + private final BasalScheduleEntry basalScheduleEntry; + private final Duration startTime; + private final Duration duration; + + public BasalScheduleLookupResult(int index, BasalScheduleEntry basalScheduleEntry, Duration startTime, Duration duration) { + this.index = index; + this.basalScheduleEntry = basalScheduleEntry; + this.startTime = startTime; + this.duration = duration; + } + + public int getIndex() { + return index; + } + + public BasalScheduleEntry getBasalScheduleEntry() { + return basalScheduleEntry; + } + + public Duration getStartTime() { + return startTime; + } + + public Duration getDuration() { + return duration; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java new file mode 100644 index 00000000000..f8065e0f4be --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalScheduleEntry.java @@ -0,0 +1,40 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class BasalScheduleEntry { + private final double rate; + private final Duration startTime; + + public BasalScheduleEntry(double rate, Duration startTime) { + if (startTime.isLongerThan(Duration.standardHours(24).minus(Duration.standardSeconds(1))) || startTime.isShorterThan(Duration.ZERO) || startTime.getStandardSeconds() % 1800 != 0) { + throw new IllegalArgumentException("Invalid start time"); + } else if (rate < 0D) { + throw new IllegalArgumentException("Rate should be >= 0"); + } else if (rate > OmnipodConst.MAX_BASAL_RATE) { + throw new IllegalArgumentException("Rate exceeds max basal rate"); + } else if (rate % OmnipodConst.POD_PULSE_SIZE > 0.000001 && rate % OmnipodConst.POD_PULSE_SIZE - OmnipodConst.POD_PULSE_SIZE < -0.000001) { + throw new IllegalArgumentException("Unsupported basal rate precision"); + } + this.rate = rate; + this.startTime = startTime; + } + + public double getRate() { + return rate; + } + + public Duration getStartTime() { + return startTime; + } + + @Override + public String toString() { + return "BasalScheduleEntry{" + + "rate=" + rate + + ", startTime=" + startTime.getStandardSeconds() + "s" + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalTableEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalTableEntry.java new file mode 100644 index 00000000000..55339865a0b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BasalTableEntry.java @@ -0,0 +1,53 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; + +public class BasalTableEntry implements IRawRepresentable { + + private final int segments; + private final int pulses; + private final boolean alternateSegmentPulse; + + public BasalTableEntry(int segments, int pulses, boolean alternateSegmentPulse) { + this.segments = segments; + this.pulses = pulses; + this.alternateSegmentPulse = alternateSegmentPulse; + } + + @Override + public byte[] getRawData() { + byte[] rawData = new byte[2]; + byte pulsesHighByte = (byte) ((pulses >>> 8) & 0b11); + byte pulsesLowByte = (byte) pulses; + rawData[0] = (byte) ((byte) ((segments - 1) << 4) + (byte) ((alternateSegmentPulse ? 1 : 0) << 3) + pulsesHighByte); + rawData[1] = pulsesLowByte; + return rawData; + } + + public int getChecksum() { + int checksumPerSegment = ByteUtil.convertUnsignedByteToInt((byte) pulses) + (pulses >>> 8); + return (checksumPerSegment * segments + (alternateSegmentPulse ? segments / 2 : 0)); + } + + public int getSegments() { + return this.segments; + } + + public int getPulses() { + return pulses; + } + + public boolean isAlternateSegmentPulse() { + return alternateSegmentPulse; + } + + @Override + public String toString() { + return "BasalTableEntry{" + + "segments=" + segments + + ", pulses=" + pulses + + ", alternateSegmentPulse=" + alternateSegmentPulse + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BolusDeliverySchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BolusDeliverySchedule.java new file mode 100644 index 00000000000..167f1bcce5f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/BolusDeliverySchedule.java @@ -0,0 +1,60 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class BolusDeliverySchedule extends DeliverySchedule implements IRawRepresentable { + + private final double units; + private final Duration timeBetweenPulses; + + public BolusDeliverySchedule(double units, Duration timeBetweenPulses) { + if (units <= 0D) { + throw new IllegalArgumentException("Units should be > 0"); + } else if (units > OmnipodConst.MAX_BOLUS) { + throw new IllegalArgumentException("Units exceeds max bolus"); + } + this.units = units; + this.timeBetweenPulses = timeBetweenPulses; + } + + @Override + public byte[] getRawData() { + byte[] rawData = new byte[]{1}; // Number of half hour segments + + int pulseCount = (int) Math.round(units / OmnipodConst.POD_PULSE_SIZE); + int multiplier = (int) timeBetweenPulses.getStandardSeconds() * 8; + int fieldA = pulseCount * multiplier; + + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(fieldA)); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulseCount)); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(pulseCount)); + return rawData; + } + + @Override + public InsulinScheduleType getType() { + return InsulinScheduleType.BOLUS; + } + + @Override + public int getChecksum() { + int checksum = 0; + byte[] rawData = getRawData(); + for (int i = 0; i < rawData.length && i < 7; i++) { + checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]); + } + return checksum; + } + + @Override + public String toString() { + return "BolusDeliverySchedule{" + + "units=" + units + + ", timeBetweenPulses=" + timeBetweenPulses + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/DeliverySchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/DeliverySchedule.java new file mode 100644 index 00000000000..325b7f19e48 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/DeliverySchedule.java @@ -0,0 +1,10 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; + +public abstract class DeliverySchedule implements IRawRepresentable { + + public abstract InsulinScheduleType getType(); + + public abstract int getChecksum(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/InsulinScheduleType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/InsulinScheduleType.java new file mode 100644 index 00000000000..6c7a364e2d3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/InsulinScheduleType.java @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +public enum InsulinScheduleType { + BASAL_SCHEDULE(0), + TEMP_BASAL_SCHEDULE(1), + BOLUS(2); + + private byte value; + + InsulinScheduleType(int value) { + this.value = (byte) value; + } + + public static InsulinScheduleType fromByte(byte input) { + for (InsulinScheduleType type : values()) { + if (type.value == input) { + return type; + } + } + return null; + } + + public byte getValue() { + return value; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/RateEntry.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/RateEntry.java new file mode 100644 index 00000000000..c9bda115626 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/RateEntry.java @@ -0,0 +1,77 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import org.joda.time.Duration; + +import java.util.ArrayList; +import java.util.List; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; + +public class RateEntry implements IRawRepresentable { + + private final double totalPulses; + // We use a double for the delay between pulses because the Joda time API lacks precision for our calculations + private final double delayBetweenPulsesInSeconds; + + public RateEntry(double totalPulses, double delayBetweenPulsesInSeconds) { + this.totalPulses = totalPulses; + this.delayBetweenPulsesInSeconds = delayBetweenPulsesInSeconds; + } + + public static List createEntries(double rate, Duration duration) { + List entries = new ArrayList<>(); + int remainingSegments = (int) Math.round(duration.getStandardSeconds() / 1800.0); + double pulsesPerSegment = (int) Math.round(rate / OmnipodConst.POD_PULSE_SIZE) / 2.0; + int maxSegmentsPerEntry = pulsesPerSegment > 0 ? (int) (BasalDeliveryTable.MAX_PULSES_PER_RATE_ENTRY / pulsesPerSegment) : 1; + + double durationInHours = duration.getStandardSeconds() / 3600.0; + + double remainingPulses = rate * durationInHours / OmnipodConst.POD_PULSE_SIZE; + double delayBetweenPulses = 3600 / rate * OmnipodConst.POD_PULSE_SIZE; + + while (remainingSegments > 0) { + if (rate == 0.0) { + entries.add(new RateEntry(0, 30D * 60)); + remainingSegments -= 1; + } else { + int numSegments = Math.min(maxSegmentsPerEntry, (int) Math.round(remainingPulses / pulsesPerSegment)); + double totalPulses = pulsesPerSegment * numSegments; + entries.add(new RateEntry(totalPulses, delayBetweenPulses)); + remainingSegments -= numSegments; + remainingPulses -= totalPulses; + } + } + + return entries; + } + + public double getTotalPulses() { + return totalPulses; + } + + public double getDelayBetweenPulsesInSeconds() { + return delayBetweenPulsesInSeconds; + } + + @Override + public byte[] getRawData() { + byte[] rawData = new byte[0]; + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16((int) Math.round(totalPulses * 10))); + if (totalPulses == 0) { + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt((int) (delayBetweenPulsesInSeconds * 1000 * 1000))); + } else { + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt((int) (delayBetweenPulsesInSeconds * 1000 * 100))); + } + return rawData; + } + + @Override + public String toString() { + return "RateEntry{" + + "totalPulses=" + totalPulses + + ", delayBetweenPulsesInSeconds=" + delayBetweenPulsesInSeconds + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/TempBasalDeliverySchedule.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/TempBasalDeliverySchedule.java new file mode 100644 index 00000000000..a4c320b1d3f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/schedule/TempBasalDeliverySchedule.java @@ -0,0 +1,69 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.IRawRepresentable; + +public class TempBasalDeliverySchedule extends DeliverySchedule implements IRawRepresentable { + + private final int secondsRemaining; + private final int firstSegmentPulses; + private final BasalDeliveryTable basalTable; + + public TempBasalDeliverySchedule(int secondsRemaining, int firstSegmentPulses, BasalDeliveryTable basalTable) { + this.secondsRemaining = secondsRemaining; + this.firstSegmentPulses = firstSegmentPulses; + this.basalTable = basalTable; + } + + @Override + public byte[] getRawData() { + byte[] rawData = new byte[0]; + rawData = ByteUtil.concat(rawData, basalTable.numSegments()); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(secondsRemaining << 3)); + rawData = ByteUtil.concat(rawData, ByteUtil.getBytesFromInt16(firstSegmentPulses)); + for (BasalTableEntry entry : basalTable.getEntries()) { + rawData = ByteUtil.concat(rawData, entry.getRawData()); + } + return rawData; + } + + @Override + public InsulinScheduleType getType() { + return InsulinScheduleType.TEMP_BASAL_SCHEDULE; + } + + @Override + public int getChecksum() { + int checksum = 0; + byte[] rawData = getRawData(); + for (int i = 0; i < rawData.length && i < 5; i++) { + checksum += ByteUtil.convertUnsignedByteToInt(rawData[i]); + } + for (BasalTableEntry entry : basalTable.getEntries()) { + checksum += entry.getChecksum(); + } + + return checksum; + } + + public int getSecondsRemaining() { + return secondsRemaining; + } + + public int getFirstSegmentPulses() { + return firstSegmentPulses; + } + + public BasalDeliveryTable getBasalTable() { + return basalTable; + } + + @Override + public String toString() { + return "TempBasalDeliverySchedule{" + + "secondsRemaining=" + secondsRemaining + + ", firstSegmentPulses=" + firstSegmentPulses + + ", basalTable=" + basalTable + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSessionState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSessionState.java new file mode 100644 index 00000000000..550e986740e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSessionState.java @@ -0,0 +1,288 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; + +import com.google.gson.Gson; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSet; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.NonceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmniCRC; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.SP; + +public class PodSessionState extends PodState { + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private final Map configuredAlerts; + private transient PodStateChangedHandler stateChangedHandler; + private DateTime activatedAt; + private DateTime expiresAt; + private final FirmwareVersion piVersion; + private final FirmwareVersion pmVersion; + private final int lot; + private final int tid; + private Double reservoirLevel; + private boolean suspended; + + private DateTimeZone timeZone; + private NonceState nonceState; + private SetupProgress setupProgress; + private AlertSet activeAlerts; + private BasalSchedule basalSchedule; + private DeliveryStatus lastDeliveryStatus; + + public PodSessionState(DateTimeZone timeZone, int address, FirmwareVersion piVersion, + FirmwareVersion pmVersion, int lot, int tid, int packetNumber, int messageNumber) { + super(address, messageNumber, packetNumber); + if (timeZone == null) { + throw new IllegalArgumentException("Time zone can not be null"); + } + + suspended = false; + configuredAlerts = new HashMap<>(); + configuredAlerts.put(AlertSlot.SLOT7, AlertType.FINISH_SETUP_REMINDER); + + this.timeZone = timeZone; + this.setupProgress = SetupProgress.ADDRESS_ASSIGNED; + this.piVersion = piVersion; + this.pmVersion = pmVersion; + this.lot = lot; + this.tid = tid; + this.nonceState = new NonceState(lot, tid); + handleUpdates(); + } + + public void setStateChangedHandler(PodStateChangedHandler handler) { + // FIXME this is an ugly workaround for not being able to serialize the PodStateChangedHandler + if (stateChangedHandler != null) { + throw new IllegalStateException("A PodStateChangedHandler has already been already registered"); + } + stateChangedHandler = handler; + } + + public AlertType getConfiguredAlertType(AlertSlot alertSlot) { + return configuredAlerts.get(alertSlot); + } + + public void putConfiguredAlert(AlertSlot alertSlot, AlertType alertType) { + configuredAlerts.put(alertSlot, alertType); + handleUpdates(); + } + + public void removeConfiguredAlert(AlertSlot alertSlot) { + configuredAlerts.remove(alertSlot); + handleUpdates(); + } + + public DateTime getActivatedAt() { + return activatedAt == null ? null : activatedAt.withZone(timeZone); + } + + public DateTime getExpiresAt() { + return expiresAt == null ? null : expiresAt.withZone(timeZone); + } + + public String getExpiryDateAsString() { + return expiresAt == null ? "???" : DateUtil.dateAndTimeString(expiresAt.toDate()); + } + + public FirmwareVersion getPiVersion() { + return piVersion; + } + + public FirmwareVersion getPmVersion() { + return pmVersion; + } + + public int getLot() { + return lot; + } + + public int getTid() { + return tid; + } + + public Double getReservoirLevel() { + return reservoirLevel; + } + + public synchronized void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) { + int sum = (sentNonce & 0xFFFF) + + OmniCRC.crc16lookup[sequenceNumber] + + (this.lot & 0xFFFF) + + (this.tid & 0xFFFF); + int seed = ((sum & 0xFFFF) ^ syncWord); + + this.nonceState = new NonceState(lot, tid, (byte) (seed & 0xFF)); + handleUpdates(); + } + + public int getCurrentNonce() { + return nonceState.getCurrentNonce(); + } + + public synchronized void advanceToNextNonce() { + nonceState.advanceToNextNonce(); + handleUpdates(); + } + + public SetupProgress getSetupProgress() { + return setupProgress; + } + + public synchronized void setSetupProgress(SetupProgress setupProgress) { + if (setupProgress == null) { + throw new IllegalArgumentException("Setup state cannot be null"); + } + this.setupProgress = setupProgress; + handleUpdates(); + } + + public boolean isSuspended() { + return suspended; + } + + public boolean hasActiveAlerts() { + return activeAlerts != null && activeAlerts.size() > 0; + } + + public AlertSet getActiveAlerts() { + return activeAlerts; + } + + public DateTimeZone getTimeZone() { + return timeZone; + } + + public void setTimeZone(DateTimeZone timeZone) { + if (timeZone == null) { + throw new IllegalArgumentException("Time zone can not be null"); + } + this.timeZone = timeZone; + handleUpdates(); + } + + public DateTime getTime() { + DateTime now = DateTime.now(); + return now.withZone(timeZone); + } + + public Duration getScheduleOffset() { + DateTime now = getTime(); + DateTime startOfDay = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), + 0, 0, 0, timeZone); + return new Duration(startOfDay, now); + } + + public boolean hasNonceState() { + return true; + } + + @Override + public void setPacketNumber(int packetNumber) { + super.setPacketNumber(packetNumber); + handleUpdates(); + } + + @Override + public void setMessageNumber(int messageNumber) { + super.setMessageNumber(messageNumber); + handleUpdates(); + } + + public BasalSchedule getBasalSchedule() { + return basalSchedule; + } + + public void setBasalSchedule(BasalSchedule basalSchedule) { + this.basalSchedule = basalSchedule; + handleUpdates(); + } + + public DeliveryStatus getLastDeliveryStatus() { + return lastDeliveryStatus; + } + + @Override + public void setFaultEvent(PodInfoFaultEvent faultEvent) { + super.setFaultEvent(faultEvent); + suspended = true; + handleUpdates(); + } + + @Override + public void updateFromStatusResponse(StatusResponse statusResponse) { + DateTime activatedAtCalculated = getTime().minus(statusResponse.getTimeActive()); + if (activatedAt == null) { + activatedAt = activatedAtCalculated; + } + DateTime expiresAtCalculated = activatedAtCalculated.plus(OmnipodConst.NOMINAL_POD_LIFE); + if (expiresAt == null || expiresAtCalculated.isBefore(expiresAt) || expiresAtCalculated.isAfter(expiresAt.plusMinutes(1))) { + expiresAt = expiresAtCalculated; + } + + boolean newSuspendedState = statusResponse.getDeliveryStatus() == DeliveryStatus.SUSPENDED; + if (suspended != newSuspendedState) { + LOG.info("Updating pod suspended state in updateFromStatusResponse. newSuspendedState={}, statusResponse={}", newSuspendedState, statusResponse.toString()); + suspended = newSuspendedState; + } + activeAlerts = statusResponse.getAlerts(); + lastDeliveryStatus = statusResponse.getDeliveryStatus(); + reservoirLevel = statusResponse.getReservoirLevel(); + handleUpdates(); + } + + private void handleUpdates() { + Gson gson = OmnipodUtil.getGsonInstance(); + String gsonValue = gson.toJson(this); + LOG.info("PodSessionState-SP: Saved Session State to SharedPreferences: " + gsonValue); + SP.putString(OmnipodConst.Prefs.PodState, gsonValue); + if (stateChangedHandler != null) { + stateChangedHandler.handle(this); + } + } + + @Override + public String toString() { + return "PodSessionState{" + + "configuredAlerts=" + configuredAlerts + + ", stateChangedHandler=" + stateChangedHandler + + ", activatedAt=" + activatedAt + + ", expiresAt=" + expiresAt + + ", piVersion=" + piVersion + + ", pmVersion=" + pmVersion + + ", lot=" + lot + + ", tid=" + tid + + ", reservoirLevel=" + reservoirLevel + + ", suspended=" + suspended + + ", timeZone=" + timeZone + + ", nonceState=" + nonceState + + ", setupProgress=" + setupProgress + + ", activeAlerts=" + activeAlerts + + ", basalSchedule=" + basalSchedule + + ", lastDeliveryStatus=" + lastDeliveryStatus + + ", address=" + address + + ", packetNumber=" + packetNumber + + ", messageNumber=" + messageNumber + + ", faultEvent=" + faultEvent + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSetupState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSetupState.java new file mode 100644 index 00000000000..26b58022584 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodSetupState.java @@ -0,0 +1,43 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; + +public class PodSetupState extends PodState { + public PodSetupState(int address, int packetNumber, int messageNumber) { + super(address, packetNumber, messageNumber); + } + + @Override + public boolean hasNonceState() { + return false; + } + + @Override + public int getCurrentNonce() { + throw new UnsupportedOperationException("PodSetupState does not have a nonce state"); + } + + @Override + public void advanceToNextNonce() { + throw new UnsupportedOperationException("PodSetupState does not have a nonce state"); + } + + @Override + public void resyncNonce(int syncWord, int sentNonce, int sequenceNumber) { + throw new UnsupportedOperationException("PodSetupState does not have a nonce state"); + } + + @Override + public void updateFromStatusResponse(StatusResponse statusResponse) { + } + + @Override + public String toString() { + return "PodSetupState{" + + "address=" + address + + ", packetNumber=" + packetNumber + + ", messageNumber=" + messageNumber + + ", faultEvent=" + faultEvent + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodState.java new file mode 100644 index 00000000000..2e19de12863 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodState.java @@ -0,0 +1,68 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; + +public abstract class PodState { + protected final int address; + protected int packetNumber; + protected int messageNumber; + + protected PodInfoFaultEvent faultEvent; + + public PodState(int address, int packetNumber, int messageNumber) { + this.address = address; + this.packetNumber = packetNumber; + this.messageNumber = messageNumber; + } + + public abstract boolean hasNonceState(); + + public abstract int getCurrentNonce(); + + public abstract void advanceToNextNonce(); + + public abstract void resyncNonce(int syncWord, int sentNonce, int sequenceNumber); + + public abstract void updateFromStatusResponse(StatusResponse statusResponse); + + public int getAddress() { + return address; + } + + public int getMessageNumber() { + return messageNumber; + } + + public void setMessageNumber(int messageNumber) { + this.messageNumber = messageNumber; + } + + public int getPacketNumber() { + return packetNumber; + } + + public void setPacketNumber(int packetNumber) { + this.packetNumber = packetNumber; + } + + public void increaseMessageNumber(int increment) { + setMessageNumber((messageNumber + increment) & 0b1111); + } + + public void increasePacketNumber(int increment) { + setPacketNumber((packetNumber + increment) & 0b11111); + } + + public boolean hasFaultEvent() { + return faultEvent != null; + } + + public PodInfoFaultEvent getFaultEvent() { + return faultEvent; + } + + public void setFaultEvent(PodInfoFaultEvent faultEvent) { + this.faultEvent = faultEvent; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java new file mode 100644 index 00000000000..6e706131a21 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/defs/state/PodStateChangedHandler.java @@ -0,0 +1,6 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.defs.state; + +@FunctionalInterface +public interface PodStateChangedHandler { + void handle(PodSessionState podState); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodHistoryActivity.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodHistoryActivity.java new file mode 100644 index 00000000000..a44f9854ad1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodHistoryActivity.java @@ -0,0 +1,345 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs; + +import android.os.Bundle; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.utils.ProfileUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistory; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +public class PodHistoryActivity extends NoSplashAppCompatActivity { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + Spinner historyTypeSpinner; + TextView statusView; + RecyclerView recyclerView; + LinearLayoutManager llm; + + static TypeList showingType = null; + static PumpHistoryEntryGroup selectedGroup = PumpHistoryEntryGroup.All; + List fullHistoryList = new ArrayList<>(); + List filteredHistoryList = new ArrayList<>(); + + RecyclerViewAdapter recyclerViewAdapter; + boolean manualChange = false; + + List typeListFull; + + + public PodHistoryActivity() { + super(); + } + + + private void prepareData() { + GregorianCalendar gc = new GregorianCalendar(); + gc.add(Calendar.HOUR_OF_DAY, -24); + + MainApp.getDbHelper().getPodHistoryFromTime(gc.getTimeInMillis(), false); + + fullHistoryList.addAll(MainApp.getDbHelper().getPodHistoryFromTime(gc.getTimeInMillis(), true)); + } + + + private void filterHistory(PumpHistoryEntryGroup group) { + + this.filteredHistoryList.clear(); + + LOG.debug("Items on full list: {}", fullHistoryList.size()); + + if (group == PumpHistoryEntryGroup.All) { + this.filteredHistoryList.addAll(fullHistoryList); + } else { + for (PodHistory pumpHistoryEntry : fullHistoryList) { + if (pumpHistoryEntry.getPodDbEntryType().getGroup() == group) { + this.filteredHistoryList.add(pumpHistoryEntry); + } + } + } + + if (this.recyclerViewAdapter != null) { + this.recyclerViewAdapter.setHistoryList(this.filteredHistoryList); + this.recyclerViewAdapter.notifyDataSetChanged(); + } + + LOG.debug("Items on filtered list: {}", filteredHistoryList.size()); + } + + + @Override + protected void onResume() { + super.onResume(); + filterHistory(selectedGroup); + setHistoryTypeSpinner(); + } + + + private void setHistoryTypeSpinner() { + this.manualChange = true; + + for (int i = 0; i < typeListFull.size(); i++) { + if (typeListFull.get(i).entryGroup == selectedGroup) { + historyTypeSpinner.setSelection(i); + break; + } + } + + SystemClock.sleep(200); + this.manualChange = false; + } + + + @Override + protected void onPause() { + super.onPause(); + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.omnipod_pod_history_activity); + + historyTypeSpinner = (Spinner) findViewById(R.id.omnipod_historytype); + statusView = (TextView) findViewById(R.id.omnipod_historystatus); + recyclerView = (RecyclerView) findViewById(R.id.omnipod_history_recyclerview); + + recyclerView.setHasFixedSize(true); + llm = new LinearLayoutManager(this); + recyclerView.setLayoutManager(llm); + + prepareData(); + + recyclerViewAdapter = new RecyclerViewAdapter(filteredHistoryList); + recyclerView.setAdapter(recyclerViewAdapter); + + statusView.setVisibility(View.GONE); + + typeListFull = getTypeList(PumpHistoryEntryGroup.getList()); + + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(this, R.layout.spinner_centered, typeListFull); + historyTypeSpinner.setAdapter(spinnerAdapter); + + historyTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (manualChange) + return; + TypeList selected = (TypeList) historyTypeSpinner.getSelectedItem(); + showingType = selected; + selectedGroup = selected.entryGroup; + filterHistory(selectedGroup); + } + + + @Override + public void onNothingSelected(AdapterView parent) { + if (manualChange) + return; + filterHistory(PumpHistoryEntryGroup.All); + } + }); + + } + + + private List getTypeList(List list) { + + ArrayList typeList = new ArrayList<>(); + + for (PumpHistoryEntryGroup pumpHistoryEntryGroup : list) { + typeList.add(new TypeList(pumpHistoryEntryGroup)); + } + + return typeList; + } + + public static class TypeList { + + PumpHistoryEntryGroup entryGroup; + String name; + + + TypeList(PumpHistoryEntryGroup entryGroup) { + this.entryGroup = entryGroup; + this.name = entryGroup.getTranslated(); + } + + + @Override + public String toString() { + return name; + } + } + + public static class RecyclerViewAdapter extends RecyclerView.Adapter { + + List historyList; + + + RecyclerViewAdapter(List historyList) { + this.historyList = historyList; + } + + + public void setHistoryList(List historyList) { + // this.historyList.clear(); + // this.historyList.addAll(historyList); + + this.historyList = historyList; + + Collections.sort(this.historyList); + + // this.notifyDataSetChanged(); + } + + + @Override + public HistoryViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.omnipod_pod_history_item, // + viewGroup, false); + return new HistoryViewHolder(v); + } + + + @Override + public void onBindViewHolder(HistoryViewHolder holder, int position) { + PodHistory record = historyList.get(position); + + if (record != null) { + holder.timeView.setText(record.getDateTimeString()); + holder.typeView.setText(record.getPodDbEntryType().getResourceId()); + setValue(record, holder.valueView); + } + } + + + private void setValue(PodHistory historyEntry, TextView valueView) { + //valueView.setText(""); + + if (historyEntry.isSuccess()) { + switch (historyEntry.getPodDbEntryType()) { + + case SetTemporaryBasal: { + TempBasalPair tempBasalPair = OmnipodUtil.getGsonInstance().fromJson(historyEntry.getData(), TempBasalPair.class); + valueView.setText(MainApp.gs(R.string.omnipod_cmd_tbr_value, tempBasalPair.getInsulinRate(), tempBasalPair.getDurationMinutes())); + } + break; + + case FillCannulaSetBasalProfile: + case SetBasalSchedule: { + if (historyEntry.getData() != null) { + setProfileValue(historyEntry.getData(), valueView); + } + } + break; + + case SetBolus: { + if (historyEntry.getData().contains(";")) { + String[] splitVal = historyEntry.getData().split(";"); + valueView.setText(MainApp.gs(R.string.omnipod_cmd_bolus_value_with_carbs, Double.valueOf(splitVal[0]), Double.valueOf(splitVal[1]))); + } else { + valueView.setText(MainApp.gs(R.string.omnipod_cmd_bolus_value, Double.valueOf(historyEntry.getData()))); + } + } + break; + + case GetPodStatus: + case GetPodInfo: + case SetTime: + case PairAndPrime: + case CancelTemporaryBasal: + case CancelTemporaryBasalForce: + case ConfigureAlerts: + case CancelBolus: + case DeactivatePod: + case ResetPodState: + case AcknowledgeAlerts: + case SuspendDelivery: + case ResumeDelivery: + case UnknownEntryType: + default: + valueView.setText(""); + break; + + } + } else { + valueView.setText(historyEntry.getData()); + } + + } + + private void setProfileValue(String data, TextView valueView) { + LOG.debug("Profile json:\n" + data); + + try { + Profile.ProfileValue[] profileValuesArray = OmnipodUtil.getGsonInstance().fromJson(data, Profile.ProfileValue[].class); + + //profile = new Profile(new JSONObject(data), Constants.MGDL); + valueView.setText(ProfileUtil.getBasalProfilesDisplayable(profileValuesArray, PumpType.Insulet_Omnipod)); + } catch (Exception e) { + LOG.error("Problem parsing Profile json. Ex: {}, Data:\n{}", e.getMessage(), data); + valueView.setText(""); + } + //Profile profile = OmnipodUtil.getGsonInstance().fromJson(data, Profile.class); + + } + + + @Override + public int getItemCount() { + return historyList.size(); + } + + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + } + + + static class HistoryViewHolder extends RecyclerView.ViewHolder { + + TextView timeView; + TextView typeView; + TextView valueView; + + HistoryViewHolder(View itemView) { + super(itemView); + timeView = itemView.findViewById(R.id.omnipod_history_time); + typeView = itemView.findViewById(R.id.omnipod_history_source); + valueView = itemView.findViewById(R.id.omnipod_history_description); + } + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodManagementActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodManagementActivity.kt new file mode 100644 index 00000000000..12a08605c36 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/PodManagementActivity.kt @@ -0,0 +1,157 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs + +import android.content.Intent +import android.os.Bundle +import com.atech.android.library.wizardpager.WizardPagerActivity +import com.atech.android.library.wizardpager.WizardPagerContext +import com.atech.android.library.wizardpager.data.WizardPagerSettings +import com.atech.android.library.wizardpager.defs.WizardStepsWayType +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity +import info.nightscout.androidaps.events.EventRefreshOverview +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.defs.PodActionType +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model.FullInitPodWizardModel +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model.RemovePodWizardModel +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model.ShortInitPodWizardModel +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages.InitPodRefreshAction +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState +import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsOmnipodManager +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil +import info.nightscout.androidaps.utils.OKDialog +import kotlinx.android.synthetic.main.omnipod_pod_mgmt.* + +/** + * Created by andy on 30/08/2019 + */ +class PodManagementActivity : NoSplashAppCompatActivity() { + + private var initPodChanged = false + private var podSessionFullyInitalized = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.omnipod_pod_mgmt) + + initpod_init_pod.setOnClickListener { + initPodAction() + initPodChanged = true + } + + initpod_remove_pod.setOnClickListener { + removePodAction() + initPodChanged = true + } + + initpod_reset_pod.setOnClickListener { + resetPodAction() + initPodChanged = true + } + + + initpod_pod_history.setOnClickListener { + showPodHistory() + } + + refreshButtons(); + + } + + override fun onDestroy() { + super.onDestroy() + + if (initPodChanged) { + RxBus.send(EventOmnipodPumpValuesChanged()) + RxBus.send(EventRefreshOverview("Omnipod Pod Management")) + } + } + + fun initPodAction() { + + val pagerSettings = WizardPagerSettings() + var refreshAction = InitPodRefreshAction(this, PodActionType.InitPod) + + pagerSettings.setWizardStepsWayType(WizardStepsWayType.CancelNext) + pagerSettings.setFinishStringResourceId(R.string.close) + pagerSettings.setFinishButtonBackground(R.drawable.finish_background) + pagerSettings.setNextButtonBackground(R.drawable.selectable_item_background) + pagerSettings.setBackStringResourceId(R.string.cancel) + pagerSettings.cancelAction = refreshAction + pagerSettings.finishAction = refreshAction + + val wizardPagerContext = WizardPagerContext.getInstance() + + wizardPagerContext.clearContext() + wizardPagerContext.pagerSettings = pagerSettings + val podSessionState = OmnipodUtil.getPodSessionState() + val isFullInit = podSessionState == null || podSessionState.setupProgress.isBefore(SetupProgress.PRIMING_FINISHED) + if (isFullInit) { + wizardPagerContext.wizardModel = FullInitPodWizardModel(applicationContext) + } else { + wizardPagerContext.wizardModel = ShortInitPodWizardModel(applicationContext) + } + + val myIntent = Intent(this@PodManagementActivity, WizardPagerActivity::class.java) + this@PodManagementActivity.startActivity(myIntent) + } + + fun removePodAction() { + val pagerSettings = WizardPagerSettings() + var refreshAction = InitPodRefreshAction(this, PodActionType.RemovePod) + + pagerSettings.setWizardStepsWayType(WizardStepsWayType.CancelNext) + pagerSettings.setFinishStringResourceId(R.string.close) + pagerSettings.setFinishButtonBackground(R.drawable.finish_background) + pagerSettings.setNextButtonBackground(R.drawable.selectable_item_background) + pagerSettings.setBackStringResourceId(R.string.cancel) + pagerSettings.cancelAction = refreshAction + pagerSettings.finishAction = refreshAction + + val wizardPagerContext = WizardPagerContext.getInstance(); + + wizardPagerContext.clearContext() + wizardPagerContext.pagerSettings = pagerSettings + wizardPagerContext.wizardModel = RemovePodWizardModel(applicationContext) + + val myIntent = Intent(this@PodManagementActivity, WizardPagerActivity::class.java) + this@PodManagementActivity.startActivity(myIntent) + + } + + fun resetPodAction() { + OKDialog.showConfirmation(this, + MainApp.gs(R.string.omnipod_cmd_reset_pod_desc), Thread { + AapsOmnipodManager.getInstance().resetPodStatus() + OmnipodUtil.setDriverState(OmnipodDriverState.Initalized_NoPod) + refreshButtons() + }) + } + + fun showPodHistory() { +// OKDialog.showConfirmation(this, +// MainApp.gs(R.string.omnipod_cmd_pod_history_na), null) + + startActivity(Intent(applicationContext, PodHistoryActivity::class.java)) + } + + fun refreshButtons() { + initpod_init_pod.isEnabled = (OmnipodUtil.getPodSessionState() == null || + OmnipodUtil.getPodSessionState().getSetupProgress().isBefore(SetupProgress.COMPLETED)) + + val isPodSessionActive = (OmnipodUtil.getPodSessionState() != null) + + initpod_remove_pod.isEnabled = isPodSessionActive + initpod_reset_pod.isEnabled = isPodSessionActive + + if (OmnipodUtil.getDriverState() == OmnipodDriverState.NotInitalized) { + // if rileylink is not running we disable all operations + initpod_init_pod.isEnabled = false + initpod_remove_pod.isEnabled = false + initpod_reset_pod.isEnabled = false + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/defs/PodActionType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/defs/PodActionType.java new file mode 100644 index 00000000000..674703855ad --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/defs/PodActionType.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.defs; + +public enum PodActionType { + InitPod, + RemovePod, + ResetPod +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionFragment.java new file mode 100644 index 00000000000..387daee11a3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionFragment.java @@ -0,0 +1,238 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod; + +import android.app.Activity; +import android.graphics.Color; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; + +import com.atech.android.library.wizardpager.util.WizardPagesUtil; +import com.tech.freak.wizardpager.model.Page; +import com.tech.freak.wizardpager.ui.PageFragmentCallbacks; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; + +/** + * Created by andy on 12/11/2019 + */ +public class InitActionFragment extends Fragment implements PodInitReceiver { + private static final String ARG_KEY = "key"; + + protected PageFragmentCallbacks mCallbacks; + protected String mKey; + protected InitActionPage mPage; + + protected ProgressBar progressBar; + protected TextView errorView; + protected Button retryButton; + + protected PodInitActionType podInitActionType; + protected List children; + protected Map mapCheckBoxes; + protected InitActionFragment instance; + + protected PumpEnactResult callResult; + + + public static InitActionFragment create(String key, PodInitActionType podInitActionType) { + Bundle args = new Bundle(); + args.putString(ARG_KEY, key); + + InitActionFragment fragment = new InitActionFragment(); + fragment.setArguments(args); + fragment.setPodInitActionType(podInitActionType); + return fragment; + } + + public InitActionFragment() { + this.instance = this; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + mKey = args.getString(ARG_KEY); + mPage = (InitActionPage) mCallbacks.onGetPage(mKey); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.omnipod_initpod_init_action, container, false); + WizardPagesUtil.setTitle(mPage, rootView); + + this.progressBar = rootView.findViewById(R.id.initAction_progressBar); + this.errorView = rootView.findViewById(R.id.initAction_textErrorMessage); + + TextView headerView = rootView.findViewById(R.id.initAction_header); + + LinearLayout linearLayout = rootView.findViewById(R.id.initAction_ItemsHolder); + + children = podInitActionType.getChildren(); + mapCheckBoxes = new HashMap<>(); + + for (PodInitActionType child : children) { + + CheckBox checkBox1 = new CheckBox(getContext()); + checkBox1.setText(child.getResourceId()); + checkBox1.setClickable(false); + checkBox1.setTextAppearance(R.style.WizardPagePodListItem); + checkBox1.setHeight(120); + checkBox1.setTextSize(15); + checkBox1.setTextColor(headerView.getTextColors().getDefaultColor()); + + linearLayout.addView(checkBox1); + + mapCheckBoxes.put(child, checkBox1); + } + + if (podInitActionType == PodInitActionType.FillCannulaSetBasalProfileWizardStep) { + headerView.setText(R.string.omnipod_init_pod_wizard_step4_action_header); + } else if (podInitActionType == PodInitActionType.DeactivatePodWizardStep) { + headerView.setText(R.string.omnipod_remove_pod_wizard_step2_action_header); + } + + this.retryButton = rootView.findViewById(R.id.initAction_RetryButton); + + this.retryButton.setOnClickListener(view -> { + + getActivity().runOnUiThread(() -> { + for (PodInitActionType actionType : mapCheckBoxes.keySet()) { + mapCheckBoxes.get(actionType).setChecked(false); + mapCheckBoxes.get(actionType).setTextColor(headerView.getTextColors().getDefaultColor()); + } + }); + + new InitPodTask(instance).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }); + + return rootView; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + if (!(activity instanceof PageFragmentCallbacks)) { + throw new ClassCastException("Activity must implement PageFragmentCallbacks"); + } + + mCallbacks = (PageFragmentCallbacks) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + + public PodInitActionType getPodInitActionType() { + return podInitActionType; + } + + + public void setPodInitActionType(PodInitActionType podInitActionType) { + this.podInitActionType = podInitActionType; + } + + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + //System.out.println("ACTION: setUserVisibleHint="+ isVisibleToUser); + if (isVisibleToUser) { + //System.out.println("ACTION: Visible"); + new InitPodTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + } else { + System.out.println("ACTION: Not visible"); + } + } + + public void actionOnReceiveResponse(String result) { +// System.out.println("ACTION: actionOnReceiveResponse: " + result); +// +// boolean isOk = callResult.success; +// +// progressBar.setVisibility(View.GONE); +// +// if (!isOk) { +// errorView.setVisibility(View.VISIBLE); +// errorView.setText(callResult.comment); +// } +// +// mPage.setActionCompleted(isOk); +// +// mPage.getData().putString(Page.SIMPLE_DATA_KEY, "ddd"); +// mPage.notifyDataChanged(); + } + + @Override + public void returnInitTaskStatus(PodInitActionType podInitActionType, boolean isSuccess, String errorMessage) { + if (podInitActionType.isParent()) { + for (PodInitActionType actionType : mapCheckBoxes.keySet()) { + setCheckBox(actionType, isSuccess); + } + + // special handling for init + processOnFinishedActions(isSuccess, errorMessage); + + } else { + setCheckBox(podInitActionType, isSuccess); + } + } + + + private void processOnFinishedActions(boolean isOk, String errorMessage) { + + getActivity().runOnUiThread(() -> { + + progressBar.setVisibility(View.GONE); + + if (!isOk) { + errorView.setVisibility(View.VISIBLE); + errorView.setText(errorMessage); + + retryButton.setVisibility(View.VISIBLE); + } + + mPage.setActionCompleted(isOk); + + mPage.getData().putString(Page.SIMPLE_DATA_KEY, UUID.randomUUID().toString()); + mPage.notifyDataChanged(); + + }); + + } + + + public void setCheckBox(PodInitActionType podInitActionType, boolean isSuccess) { + getActivity().runOnUiThread(() -> { + mapCheckBoxes.get(podInitActionType).setChecked(isSuccess); + mapCheckBoxes.get(podInitActionType).setTextColor(isSuccess ? Color.rgb(34, 135, 91) : + Color.rgb(168, 36, 15)); + }); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionPage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionPage.java new file mode 100644 index 00000000000..8b34b99193d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitActionPage.java @@ -0,0 +1,73 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod; + +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; + +import com.tech.freak.wizardpager.model.ModelCallbacks; +import com.tech.freak.wizardpager.model.Page; +import com.tech.freak.wizardpager.model.ReviewItem; + +import java.util.ArrayList; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; + + +/** + * Created by andy on 12/11/2019 + * + * This page is for InitPod and RemovePod, but Fragments called for this 2 actions are different + */ +public class InitActionPage extends Page { + + protected PodInitActionType podInitActionType; + + protected boolean actionCompleted = false; + protected boolean actionSuccess = false; + + public InitActionPage(ModelCallbacks callbacks, String title) { + super(callbacks, title); + } + + public InitActionPage(ModelCallbacks callbacks, @StringRes int titleId, PodInitActionType podInitActionType) { + super(callbacks, titleId); + this.podInitActionType = podInitActionType; + } + + @Override + public Fragment createFragment() { + return InitActionFragment.create(getKey(), this.podInitActionType); + } + + @Override + public void getReviewItems(ArrayList dest) { + } + + @Override + public boolean isCompleted() { + return actionCompleted; + } + + public void setActionCompleted(boolean success) { + this.actionCompleted = success; + this.actionSuccess = success; + } + + /** + * This is used just if we want to override default behavior (for example when we enter Page we want prevent any action, until something happens. + * + * @return + */ + public boolean isBackActionPossible() { + return actionCompleted; + } + + /** + * This is used just if we want to override default behavior (for example when we enter Page we want prevent any action, until something happens. + * + * @return + */ + public boolean isNextActionPossible() { + return actionSuccess; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitPodTask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitPodTask.java new file mode 100644 index 00000000000..3dcf957c06a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/initpod/InitPodTask.java @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod; + +import android.os.AsyncTask; +import android.os.SystemClock; +import android.view.View; + +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsOmnipodManager; + +/** + * Created by andy on 11/12/2019 + */ +public class InitPodTask extends AsyncTask { + + private InitActionFragment initActionFragment; + + public InitPodTask(InitActionFragment initActionFragment) { + + this.initActionFragment = initActionFragment; + } + + protected void onPreExecute() { + initActionFragment.progressBar.setVisibility(View.VISIBLE); + initActionFragment.errorView.setVisibility(View.GONE); + initActionFragment.retryButton.setVisibility(View.GONE); + } + + @Override + protected String doInBackground(Void... params) { + + if (initActionFragment.podInitActionType == PodInitActionType.PairAndPrimeWizardStep) { + initActionFragment.callResult = AapsOmnipodManager.getInstance().initPod( + initActionFragment.podInitActionType, + initActionFragment.instance, + null + ); + } else if (initActionFragment.podInitActionType == PodInitActionType.FillCannulaSetBasalProfileWizardStep) { + initActionFragment.callResult = AapsOmnipodManager.getInstance().initPod( + initActionFragment.podInitActionType, + initActionFragment.instance, + ProfileFunctions.getInstance().getProfile() + ); + } else if (initActionFragment.podInitActionType == PodInitActionType.DeactivatePodWizardStep) { + initActionFragment.callResult = AapsOmnipodManager.getInstance().deactivatePod(initActionFragment.instance); + } + + return "OK"; + } + + @Override + protected void onPostExecute(String result) { + super.onPostExecute(result); + + initActionFragment.actionOnReceiveResponse(result); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/FullInitPodWizardModel.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/FullInitPodWizardModel.java new file mode 100644 index 00000000000..a2e7383091c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/FullInitPodWizardModel.java @@ -0,0 +1,48 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model; + +import android.content.Context; + +import com.atech.android.library.wizardpager.model.DisplayTextPage; +import com.tech.freak.wizardpager.model.PageList; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod.InitActionPage; + +/** + * Created by andy on 12/11/2019 + */ +// Full init pod wizard model +// Cannot be merged with ShortInitPodWizardModel, because we can't set any instance variables +// before the onNewRootPageList method is called (which happens in the super constructor) +public class FullInitPodWizardModel extends InitPodWizardModel { + + public FullInitPodWizardModel(Context context) { + super(context); + } + + @Override + protected PageList onNewRootPageList() { + return new PageList( + new DisplayTextPage(this, + R.string.omnipod_init_pod_wizard_step1_title, + R.string.omnipod_init_pod_wizard_step1_desc, + R.style.WizardPagePodContent).setRequired(true).setCancelReason("None"), + + new InitActionPage(this, + R.string.omnipod_init_pod_wizard_step2_title, + PodInitActionType.PairAndPrimeWizardStep + ).setRequired(true).setCancelReason("Cancel"), + + new DisplayTextPage(this, + R.string.omnipod_init_pod_wizard_step3_title, + R.string.omnipod_init_pod_wizard_step3_desc, + R.style.WizardPagePodContent).setRequired(true).setCancelReason("Cancel"), + + new InitActionPage(this, + R.string.omnipod_init_pod_wizard_step4_title, + PodInitActionType.FillCannulaSetBasalProfileWizardStep + ).setRequired(true).setCancelReason("Cancel") + ); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/InitPodWizardModel.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/InitPodWizardModel.java new file mode 100644 index 00000000000..f5208a48d83 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/InitPodWizardModel.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model; + +import android.content.Context; + +import androidx.fragment.app.Fragment; + +import com.tech.freak.wizardpager.model.AbstractWizardModel; + +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages.PodInfoFragment; + +public abstract class InitPodWizardModel extends AbstractWizardModel { + public InitPodWizardModel(Context context) { + super(context); + } + + @Override + public Fragment getReviewFragment() { + PodInfoFragment.isInitPod = true; + return new PodInfoFragment(); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/RemovePodWizardModel.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/RemovePodWizardModel.java new file mode 100644 index 00000000000..57a7f814145 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/RemovePodWizardModel.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012 Roman Nurik + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model; + +import android.content.Context; + +import androidx.fragment.app.Fragment; + +import com.atech.android.library.wizardpager.model.DisplayTextPage; +import com.tech.freak.wizardpager.model.AbstractWizardModel; +import com.tech.freak.wizardpager.model.PageList; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages.PodInfoFragment; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.removepod.RemovePodActionPage; + +/** + * Created by andy on 12/11/2019 + */ +public class RemovePodWizardModel extends AbstractWizardModel { + + public RemovePodWizardModel(Context context) { + super(context); + } + + @Override + protected PageList onNewRootPageList() { + + return new PageList( + + new DisplayTextPage(this, + R.string.omnipod_remove_pod_wizard_step1_title, + R.string.omnipod_remove_pod_wizard_step1_desc, + R.style.WizardPagePodContent).setRequired(true).setCancelReason("None"), + + new RemovePodActionPage(this, + R.string.omnipod_remove_pod_wizard_step2_title, + PodInitActionType.DeactivatePodWizardStep + ).setRequired(true).setCancelReason("Cancel") + + ); + } + + + public Fragment getReviewFragment() { + PodInfoFragment.isInitPod = false; + return new PodInfoFragment(); + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/ShortInitPodWizardModel.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/ShortInitPodWizardModel.java new file mode 100644 index 00000000000..5499fdf8ece --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/model/ShortInitPodWizardModel.java @@ -0,0 +1,39 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.model; + +import android.content.Context; + +import com.atech.android.library.wizardpager.model.DisplayTextPage; +import com.tech.freak.wizardpager.model.PageList; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod.InitActionPage; + +/** + * Created by andy on 12/11/2019 + */ +// Init pod wizard model without the pair and prime step +// Cannot be merged with FullInitPodWizardModel, because we can't set any instance variables +// before the onNewRootPageList method is called (which happens in the super constructor) +public class ShortInitPodWizardModel extends InitPodWizardModel { + + public ShortInitPodWizardModel(Context context) { + super(context); + } + + @Override + protected PageList onNewRootPageList() { + return new PageList( + new DisplayTextPage(this, + R.string.omnipod_init_pod_wizard_step3_title, + R.string.omnipod_init_pod_wizard_step3_desc, + R.style.WizardPagePodContent).setRequired(true).setCancelReason("Cancel"), + + new InitActionPage(this, + R.string.omnipod_init_pod_wizard_step4_title, + PodInitActionType.FillCannulaSetBasalProfileWizardStep + ).setRequired(true).setCancelReason("Cancel") + ); + + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/InitPodRefreshAction.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/InitPodRefreshAction.java new file mode 100644 index 00000000000..1b98976aa51 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/InitPodRefreshAction.java @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages; + +import com.atech.android.library.wizardpager.defs.action.AbstractCancelAction; +import com.atech.android.library.wizardpager.defs.action.FinishActionInterface; + +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.PodManagementActivity; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.defs.PodActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 12/11/2019 + */ +public class InitPodRefreshAction extends AbstractCancelAction implements FinishActionInterface { + + private PodManagementActivity podManagementActivity; + private PodActionType actionType; + + public InitPodRefreshAction(PodManagementActivity podManagementActivity, PodActionType actionType) { + this.podManagementActivity = podManagementActivity; + this.actionType = actionType; + } + + @Override + public void execute(String cancelReason) { + if (cancelReason != null && cancelReason.trim().length() > 0) { + this.cancelActionText = cancelReason; + } + + if (this.cancelActionText.equals("Cancel")) { + //AapsOmnipodManager.getInstance().resetPodStatus(); + } + + podManagementActivity.refreshButtons(); + } + + @Override + public void execute() { + OmnipodUtil.setDriverState(actionType==PodActionType.InitPod ? + OmnipodDriverState.Initalized_PodAvailable : OmnipodDriverState.Initalized_NoPod); + + podManagementActivity.refreshButtons(); + } + + @Override + public String getFinishActionText() { + return "Finish_OK"; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoFragment.java new file mode 100644 index 00000000000..eae3df3eaa3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoFragment.java @@ -0,0 +1,209 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.ListFragment; + +import com.tech.freak.wizardpager.model.ModelCallbacks; +import com.tech.freak.wizardpager.model.Page; +import com.tech.freak.wizardpager.model.ReviewItem; +import com.tech.freak.wizardpager.ui.PageFragmentCallbacks; +import com.tech.freak.wizardpager.ui.ReviewFragment; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FirmwareVersion; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + + +/** + * Created by andy on 12/11/2019 + */ +public class PodInfoFragment extends Fragment { + private static final String ARG_KEY = "key"; + + private PageFragmentCallbacks mCallbacks; + private String mKey; + private PodInfoPage mPage; + public static boolean isInitPod = false; + private ArrayList mCurrentReviewItems; + + public static PodInfoFragment create(String key, boolean initPod) { + Bundle args = new Bundle(); + args.putString(ARG_KEY, key); + isInitPod = initPod; + + PodInfoFragment fragment = new PodInfoFragment(); + fragment.setArguments(args); + return fragment; + } + + public PodInfoFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.omnipod_initpod_pod_info, container, false); + + TextView titleView = (TextView) rootView.findViewById(R.id.podInfoTitle); + titleView.setText(R.string.omnipod_init_pod_wizard_pod_info_title); + titleView.setTextColor(getResources().getColor(com.tech.freak.wizardpager.R.color.review_green)); + + TextView headerText = rootView.findViewById(R.id.podInfoText); + headerText.setText(isInitPod ? // + R.string.omnipod_init_pod_wizard_pod_info_init_pod_description : // + R.string.omnipod_init_pod_wizard_pod_info_remove_pod_description); + + + if (isInitPod) { + if (createDataOfPod()) { + + ListView listView = (ListView) rootView.findViewById(R.id.podInfoList); + listView.setAdapter(new PodInfoAdapter(mCurrentReviewItems, getContext())); + listView.setChoiceMode(ListView.CHOICE_MODE_NONE); + } + } + + + return rootView; + } + + private boolean createDataOfPod() { + + PodSessionState podSessionState = OmnipodUtil.getPodSessionState(); + +// PodSessionState podSessionState = new PodSessionState(DateTimeZone.UTC, +// 483748738, +// new DateTime(), +// new FirmwareVersion(1,0,0), +// new FirmwareVersion(1,0,0), +// 574875, +// 5487584, +// 1, +// 1 +// ); + + if (podSessionState==null) + return false; + + mCurrentReviewItems = new ArrayList<>(); + mCurrentReviewItems.add(new ReviewItem("Pod Address", "" + podSessionState.getAddress(), "33")); + mCurrentReviewItems.add(new ReviewItem("Activated At", podSessionState.getActivatedAt().toString("dd.MM.yyyy HH:mm:ss"), "34")); + mCurrentReviewItems.add(new ReviewItem("Firmware Version", podSessionState.getPiVersion().toString(), "35")); + mCurrentReviewItems.add(new ReviewItem("LOT", "" + podSessionState.getLot(), "36")); + + return true; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + if (!(activity instanceof PageFragmentCallbacks)) { + throw new ClassCastException("Activity must implement PageFragmentCallbacks"); + } + + mCallbacks = (PageFragmentCallbacks) activity; + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + + private class PodInfoAdapter extends ArrayAdapter { + + private ArrayList dataSet; + Context mContext; + + // View lookup cache + + + public PodInfoAdapter(ArrayList data, Context context) { + super(context, com.tech.freak.wizardpager.R.layout.list_item_review, data); + this.dataSet = data; + this.mContext=context; + } + + + + private int lastPosition = -1; + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // Get the data item for this position + ReviewItem dataModel = getItem(position); + // Check if an existing view is being reused, otherwise inflate the view + ViewHolder viewHolder; // view lookup cache stored in tag + + final View result; + + if (convertView == null) { + + viewHolder = new ViewHolder(); + LayoutInflater inflater = LayoutInflater.from(getContext()); + convertView = inflater.inflate(R.layout.omnipod_initpod_pod_info_item, parent, false); + viewHolder.txtName = (TextView) convertView.findViewById(android.R.id.text1); + viewHolder.txtType = (TextView) convertView.findViewById(android.R.id.text2); + + + result=convertView; + + convertView.setTag(viewHolder); + } else { + viewHolder = (ViewHolder) convertView.getTag(); + result=convertView; + } + + + + viewHolder.txtName.setText(dataModel.getTitle()); + viewHolder.txtType.setText(dataModel.getDisplayValue()); + + + // Return the completed view to render on screen + return convertView; + } + + } + + private static class ViewHolder { + TextView txtName; + TextView txtType; + } + + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoPage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoPage.java new file mode 100644 index 00000000000..04ca08a0f32 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/pages/PodInfoPage.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.pages; + +import androidx.fragment.app.Fragment; + +import com.tech.freak.wizardpager.model.ModelCallbacks; +import com.tech.freak.wizardpager.model.Page; +import com.tech.freak.wizardpager.model.ReviewItem; + +import java.util.ArrayList; + + +/** + * Created by andy on 12/11/2019 + */ +public class PodInfoPage extends Page { + + public PodInfoPage(ModelCallbacks callbacks, String title) { + super(callbacks, title); + } + + @Override + public Fragment createFragment() { + return PodInfoFragment.create(getKey(), true); + } + + @Override + public void getReviewItems(ArrayList dest) { + } + + @Override + public boolean isCompleted() { + return true; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemoveActionFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemoveActionFragment.java new file mode 100644 index 00000000000..25311856974 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemoveActionFragment.java @@ -0,0 +1,72 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.removepod; + +import android.os.Bundle; +import android.view.View; + +import com.tech.freak.wizardpager.model.Page; + +import java.util.UUID; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod.InitActionFragment; + +/** + * Created by andy on 29/11/2019 + */ +public class RemoveActionFragment extends InitActionFragment implements PodInitReceiver { + private static final String ARG_KEY = "key"; + + public static RemoveActionFragment create(String key, PodInitActionType podInitActionType) { + Bundle args = new Bundle(); + args.putString(ARG_KEY, key); + + RemoveActionFragment fragment = new RemoveActionFragment(); + fragment.setArguments(args); + fragment.setPodInitActionType(podInitActionType); + return fragment; + } + + public RemoveActionFragment() { + this.instance = this; + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + } + + public void actionOnReceiveResponse(String result) { + System.out.println("ACTION: actionOnReceiveResponse: " + result); + + boolean isOk = callResult.success; + + progressBar.setVisibility(View.GONE); + + if (!isOk) { + errorView.setVisibility(View.VISIBLE); + errorView.setText(callResult.comment); + + retryButton.setVisibility(View.VISIBLE); + } + + mPage.setActionCompleted(isOk); + + mPage.getData().putString(Page.SIMPLE_DATA_KEY, UUID.randomUUID().toString()); + mPage.notifyDataChanged(); + } + + + @Override + public void returnInitTaskStatus(PodInitActionType podInitActionType, boolean isSuccess, String errorMessage) { + if (podInitActionType.isParent()) { + for (PodInitActionType actionType : mapCheckBoxes.keySet()) { + setCheckBox(actionType, isSuccess); + } + } else { + setCheckBox(podInitActionType, isSuccess); + } + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemovePodActionPage.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemovePodActionPage.java new file mode 100644 index 00000000000..7f5b2b85033 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dialogs/wizard/removepod/RemovePodActionPage.java @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.removepod; + +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; + +import com.tech.freak.wizardpager.model.ModelCallbacks; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.dialogs.wizard.initpod.InitActionPage; + + +/** + * Created by andy on 12/11/2019 + * + */ +public class RemovePodActionPage extends InitActionPage { + + public RemovePodActionPage(ModelCallbacks callbacks, String title) { + super(callbacks, title); + } + + public RemovePodActionPage(ModelCallbacks callbacks, @StringRes int titleId, PodInitActionType podInitActionType) { + super(callbacks, titleId, podInitActionType); + } + + @Override + public Fragment createFragment() { + return RemoveActionFragment.create(getKey(), this.podInitActionType); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodDriverState.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodDriverState.java new file mode 100644 index 00000000000..8e619dcecb7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodDriverState.java @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver; + +public enum OmnipodDriverState { + + NotInitalized, + Initalized_NoPod, + Initalized_PodAvailable, + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodPumpStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodPumpStatus.java new file mode 100644 index 00000000000..4fae0302df6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/OmnipodPumpStatus.java @@ -0,0 +1,236 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver; + +import org.joda.time.LocalDateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.PumpStatus; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodPumpStatus extends PumpStatus { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public String errorDescription = null; + public String rileyLinkAddress = null; + public boolean inPreInit = true; + + // statuses + public RileyLinkServiceState rileyLinkServiceState = RileyLinkServiceState.NotStarted; + public RileyLinkError rileyLinkError; + public double currentBasal = 0; + public long tempBasalStart; + public long tempBasalEnd; + public Double tempBasalAmount = 0.0d; + public Integer tempBasalLength; + public long tempBasalPumpId; + public PodSessionState podSessionState; + + private boolean rileyLinkAddressChanged = false; + private String regexMac = "([\\da-fA-F]{1,2}(?:\\:|$)){6}"; + + + public String podNumber; + public PodDeviceState podDeviceState = PodDeviceState.NeverContacted; + public boolean podAvailable = false; + public boolean podAvailibityChecked = false; + public boolean ackAlertsAvailable = false; + public String ackAlertsText = null; + + public boolean beepBolusEnabled = true; + public boolean beepBasalEnabled = true; + public boolean beepSMBEnabled = true; + public boolean beepTBREnabled = true; + public boolean podDebuggingOptionsEnabled = false; + public String podLotNumber = "???"; + public boolean timeChangeEventEnabled = true; + + public OmnipodDriverState driverState = OmnipodDriverState.NotInitalized; + + public OmnipodPumpStatus(PumpDescription pumpDescription) { + super(pumpDescription); + } + + + @Override + public void initSettings() { + this.activeProfileName = ""; + this.reservoirRemainingUnits = 75d; + this.batteryRemaining = 75; + this.lastConnection = SP.getLong(OmnipodConst.Statistics.LastGoodPumpCommunicationTime, 0L); + this.lastDataTime = new LocalDateTime(this.lastConnection); + this.pumpType = PumpType.Insulet_Omnipod; + this.podAvailable = false; + } + + + public boolean verifyConfiguration() { + try { + + this.errorDescription = "-"; + + String rileyLinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, null); + + if (rileyLinkAddress == null) { + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: null"); + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + return false; + } else { + if (!rileyLinkAddress.matches(regexMac)) { + this.errorDescription = MainApp.gs(R.string.medtronic_error_rileylink_address_invalid); + if (isLogEnabled()) + LOG.debug("RileyLink address invalid: {}", rileyLinkAddress); + } else { + if (!rileyLinkAddress.equals(this.rileyLinkAddress)) { + this.rileyLinkAddress = rileyLinkAddress; + rileyLinkAddressChanged = true; + } + } + } + + this.beepBasalEnabled = SP.getBoolean(OmnipodConst.Prefs.BeepBasalEnabled, true); + this.beepBolusEnabled = SP.getBoolean(OmnipodConst.Prefs.BeepBolusEnabled, true); + this.beepSMBEnabled = SP.getBoolean(OmnipodConst.Prefs.BeepSMBEnabled, true); + this.beepTBREnabled = SP.getBoolean(OmnipodConst.Prefs.BeepTBREnabled, true); + this.podDebuggingOptionsEnabled = SP.getBoolean(OmnipodConst.Prefs.PodDebuggingOptionsEnabled, false); + this.timeChangeEventEnabled = SP.getBoolean(OmnipodConst.Prefs.TimeChangeEventEnabled, true); + + LOG.debug("Beeps [basal={}, bolus={}, SMB={}, TBR={}]", this.beepBasalEnabled, this.beepBolusEnabled, this.beepSMBEnabled, this.beepTBREnabled); + + reconfigureService(); + + return true; + + } catch (Exception ex) { + this.errorDescription = ex.getMessage(); + LOG.error("Error on Verification: " + ex.getMessage(), ex); + return false; + } + } + + + private boolean reconfigureService() { + + if (!inPreInit && OmnipodUtil.getOmnipodService() != null) { + + if (rileyLinkAddressChanged) { + OmnipodUtil.sendBroadcastMessage(RileyLinkConst.Intents.RileyLinkNewAddressSet); + rileyLinkAddressChanged = false; + } + } + + return (!rileyLinkAddressChanged); + } + + + public String getErrorInfo() { + verifyConfiguration(); + + return (this.errorDescription == null) ? "-" : this.errorDescription; + } + + + @Override + public void refreshConfiguration() { + verifyConfiguration(); + } + + + public boolean setNotInPreInit() { + this.inPreInit = false; + + return reconfigureService(); + } + + + public void clearTemporaryBasal() { + this.tempBasalStart = 0L; + this.tempBasalEnd = 0L; + this.tempBasalAmount = 0.0d; + this.tempBasalLength = 0; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + public TempBasalPair getTemporaryBasal() { + + TempBasalPair tbr = new TempBasalPair(); + tbr.setDurationMinutes(tempBasalLength); + tbr.setInsulinRate(tempBasalAmount); + tbr.setStartTime(tempBasalStart); + tbr.setEndTime(tempBasalEnd); + + return tbr; + } + + @Override + public String toString() { + return "OmnipodPumpStatus{" + + "errorDescription='" + errorDescription + '\'' + + ", rileyLinkAddress='" + rileyLinkAddress + '\'' + + ", inPreInit=" + inPreInit + + ", rileyLinkServiceState=" + rileyLinkServiceState + + ", rileyLinkError=" + rileyLinkError + + ", currentBasal=" + currentBasal + + ", tempBasalStart=" + tempBasalStart + + ", tempBasalEnd=" + tempBasalEnd + + ", tempBasalAmount=" + tempBasalAmount + + ", tempBasalLength=" + tempBasalLength + + ", podSessionState=" + podSessionState + + ", rileyLinkAddressChanged=" + rileyLinkAddressChanged + + ", regexMac='" + regexMac + '\'' + + ", podNumber='" + podNumber + '\'' + + ", podDeviceState=" + podDeviceState + + ", podAvailable=" + podAvailable + + ", ackAlertsAvailable=" + ackAlertsAvailable + + ", ackAlertsText='" + ackAlertsText + '\'' + + ", lastDataTime=" + lastDataTime + + ", lastConnection=" + lastConnection + + ", previousConnection=" + previousConnection + + ", lastBolusTime=" + lastBolusTime + + ", lastBolusAmount=" + lastBolusAmount + + ", activeProfileName='" + activeProfileName + '\'' + + ", reservoirRemainingUnits=" + reservoirRemainingUnits + + ", reservoirFullUnits=" + reservoirFullUnits + + ", batteryRemaining=" + batteryRemaining + + ", batteryVoltage=" + batteryVoltage + + ", iob='" + iob + '\'' + + ", dailyTotalUnits=" + dailyTotalUnits + + ", maxDailyTotalUnits='" + maxDailyTotalUnits + '\'' + + ", validBasalRateProfileSelectedOnPump=" + validBasalRateProfileSelectedOnPump + + ", pumpType=" + pumpType + + ", profileStore=" + profileStore + + ", units='" + units + '\'' + + ", pumpStatusType=" + pumpStatusType + + ", basalsByHour=" + Arrays.toString(basalsByHour) + + ", currentBasal=" + currentBasal + + ", tempBasalInProgress=" + tempBasalInProgress + + ", tempBasalRatio=" + tempBasalRatio + + ", tempBasalRemainMin=" + tempBasalRemainMin + + ", tempBasalStart=" + tempBasalStart + + ", pumpDescription=" + pumpDescription + + "} "; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java new file mode 100644 index 00000000000..17a54953d3a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java @@ -0,0 +1,743 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.comm; + +import android.content.Intent; +import android.text.TextUtils; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.joda.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.ErrorHelperActivity; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.Source; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventRefreshOverview; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpStatusType; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodManager; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.SetupActionResult; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.StatusResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoResponse; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertSlot; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.AlertType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.FaultEventType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInfoType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalSchedule; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.schedule.BasalScheduleEntry; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistory; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.db.PodHistoryEntryType; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodAcknowledgeAlertsChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.ActionInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommandInitializationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CommunicationException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.CrcMismatchException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalDeliveryStatusException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPacketTypeException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalPodProgressException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalResponseException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.IllegalSetupProgressException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.MessageDecodingException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NonceOutOfSyncException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NonceResyncException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.NotEnoughDataException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException; +import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; +import io.reactivex.disposables.Disposable; + +public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface { + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + private final OmnipodManager delegate; + + private static AapsOmnipodManager instance; + private OmnipodPumpStatus pumpStatus; + + private Date lastBolusTime; + private Double lastBolusUnits; + + public static AapsOmnipodManager getInstance() { + return instance; + } + + public AapsOmnipodManager(OmnipodCommunicationService communicationService, PodSessionState podState, OmnipodPumpStatus _pumpStatus) { + delegate = new OmnipodManager(communicationService, podState, podSessionState -> { + // Handle pod state changes + OmnipodUtil.setPodSessionState(podSessionState); + updatePumpStatus(podSessionState); + }); + this.pumpStatus = _pumpStatus; + instance = this; + } + + private void updatePumpStatus(PodSessionState podSessionState) { + if (pumpStatus != null) { + if (podSessionState == null) { + pumpStatus.ackAlertsText = null; + pumpStatus.ackAlertsAvailable = false; + pumpStatus.lastBolusTime = null; + pumpStatus.lastBolusAmount = null; + pumpStatus.reservoirRemainingUnits = 0.0; + pumpStatus.pumpStatusType = PumpStatusType.Suspended; + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + sendEvent(new EventOmnipodPumpValuesChanged()); + sendEvent(new EventRefreshOverview("Omnipod Pump")); + } else { + // Update active alerts + if (podSessionState.hasActiveAlerts()) { + List alerts = translateActiveAlerts(podSessionState); + String alertsText = TextUtils.join("\n", alerts); + + if (!pumpStatus.ackAlertsAvailable || !alertsText.equals(pumpStatus.ackAlertsText)) { + pumpStatus.ackAlertsAvailable = true; + pumpStatus.ackAlertsText = TextUtils.join("\n", alerts); + + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + } + } else { + if (pumpStatus.ackAlertsAvailable || StringUtils.isNotEmpty(pumpStatus.ackAlertsText)) { + pumpStatus.ackAlertsText = null; + pumpStatus.ackAlertsAvailable = false; + sendEvent(new EventOmnipodAcknowledgeAlertsChanged()); + } + } + + // Update other info: last bolus, units remaining, suspended + if (!Objects.equals(lastBolusTime, pumpStatus.lastBolusTime) // + || !Objects.equals(lastBolusUnits, pumpStatus.lastBolusAmount) // + || !isReservoirStatusUpToDate(pumpStatus, podSessionState.getReservoirLevel()) + || podSessionState.isSuspended() != PumpStatusType.Suspended.equals(pumpStatus.pumpStatusType)) { + pumpStatus.lastBolusTime = lastBolusTime; + pumpStatus.lastBolusAmount = lastBolusUnits; + pumpStatus.reservoirRemainingUnits = podSessionState.getReservoirLevel() == null ? 75.0 : podSessionState.getReservoirLevel(); + pumpStatus.pumpStatusType = podSessionState.isSuspended() ? PumpStatusType.Suspended : PumpStatusType.Running; + sendEvent(new EventOmnipodPumpValuesChanged()); + + if (podSessionState.isSuspended() != PumpStatusType.Suspended.equals(pumpStatus.pumpStatusType)) { + sendEvent(new EventRefreshOverview("Omnipod Pump")); + } + } + } + } + } + + private static boolean isReservoirStatusUpToDate(OmnipodPumpStatus pumpStatus, Double unitsRemaining) { + double expectedUnitsRemaining = unitsRemaining == null ? 75.0 : unitsRemaining; + return Math.abs(expectedUnitsRemaining - pumpStatus.reservoirRemainingUnits) < 0.000001; + } + + private List translateActiveAlerts(PodSessionState podSessionState) { + List alerts = new ArrayList<>(); + for (AlertSlot alertSlot : podSessionState.getActiveAlerts().getAlertSlots()) { + alerts.add(translateAlertType(podSessionState.getConfiguredAlertType(alertSlot))); + } + return alerts; + } + + @Override + public PumpEnactResult initPod(PodInitActionType podInitActionType, PodInitReceiver podInitReceiver, Profile profile) { + long time = System.currentTimeMillis(); + if (PodInitActionType.PairAndPrimeWizardStep.equals(podInitActionType)) { + try { + Disposable disposable = delegate.pairAndPrime().subscribe(res -> // + handleSetupActionResult(podInitActionType, podInitReceiver, res, time, null)); + return new PumpEnactResult().success(true).enacted(true); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + podInitReceiver.returnInitTaskStatus(podInitActionType, false, comment); + addFailureToHistory(time, PodHistoryEntryType.PairAndPrime, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + } else if (PodInitActionType.FillCannulaSetBasalProfileWizardStep.equals(podInitActionType)) { + try { + BasalSchedule basalSchedule; + try { + basalSchedule = mapProfileToBasalSchedule(profile); + } catch (Exception ex) { + throw new CommandInitializationException("Basal profile mapping failed", ex); + } + Disposable disposable = delegate.insertCannula(basalSchedule).subscribe(res -> // + handleSetupActionResult(podInitActionType, podInitReceiver, res, time, profile)); + return new PumpEnactResult().success(true).enacted(true); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + podInitReceiver.returnInitTaskStatus(podInitActionType, false, comment); + addFailureToHistory(time, PodHistoryEntryType.FillCannulaSetBasalProfile, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + } + + return new PumpEnactResult().success(false).enacted(false).comment(getStringResource(R.string.omnipod_error_illegal_init_action_type, podInitActionType.name())); + } + + @Override + public PumpEnactResult getPodStatus() { + long time = System.currentTimeMillis(); + try { + StatusResponse statusResponse = delegate.getPodStatus(); + addSuccessToHistory(time, PodHistoryEntryType.GetPodStatus, statusResponse); + return new PumpEnactResult().success(true).enacted(false); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + addFailureToHistory(time, PodHistoryEntryType.GetPodStatus, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + } + + @Override + public PumpEnactResult deactivatePod(PodInitReceiver podInitReceiver) { + long time = System.currentTimeMillis(); + try { + delegate.deactivatePod(); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + podInitReceiver.returnInitTaskStatus(PodInitActionType.DeactivatePodWizardStep, false, comment); + addFailureToHistory(time, PodHistoryEntryType.DeactivatePod, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + reportImplicitlyCanceledTbr(); + + addSuccessToHistory(time, PodHistoryEntryType.DeactivatePod, null); + + podInitReceiver.returnInitTaskStatus(PodInitActionType.DeactivatePodWizardStep, true, null); + + OmnipodUtil.setPodSessionState(null); + + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public PumpEnactResult setBasalProfile(Profile profile) { + long time = System.currentTimeMillis(); + try { + BasalSchedule basalSchedule; + try { + basalSchedule = mapProfileToBasalSchedule(profile); + } catch (Exception ex) { + throw new CommandInitializationException("Basal profile mapping failed", ex); + } + delegate.setBasalSchedule(basalSchedule, isBasalBeepsEnabled()); + // Because setting a basal profile actually suspends and then resumes delivery, TBR is implicitly cancelled + reportImplicitlyCanceledTbr(); + addSuccessToHistory(time, PodHistoryEntryType.SetBasalSchedule, profile.getBasalValues()); + } catch (Exception ex) { + if ((ex instanceof OmnipodException) && !((OmnipodException) ex).isCertainFailure()) { + reportImplicitlyCanceledTbr(); + addToHistory(time, PodHistoryEntryType.SetBasalSchedule, "Uncertain failure", false); + return new PumpEnactResult().success(false).enacted(false).comment(getStringResource(R.string.omnipod_error_set_basal_failed_uncertain)); + } + String comment = handleAndTranslateException(ex); + reportImplicitlyCanceledTbr(); + addFailureToHistory(time, PodHistoryEntryType.SetBasalSchedule, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public PumpEnactResult resetPodStatus() { + delegate.resetPodState(true); + + reportImplicitlyCanceledTbr(); + + OmnipodUtil.setPodSessionState(null); + + addSuccessToHistory(System.currentTimeMillis(), PodHistoryEntryType.ResetPodState, null); + + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public PumpEnactResult setBolus(DetailedBolusInfo detailedBolusInfo) { + OmnipodManager.BolusCommandResult bolusCommandResult; + + boolean beepsEnabled = detailedBolusInfo.isSMB ? isSmbBeepsEnabled() : isBolusBeepsEnabled(); + + Date bolusStarted; + try { + bolusCommandResult = delegate.bolus(PumpType.Insulet_Omnipod.determineCorrectBolusSize(detailedBolusInfo.insulin), beepsEnabled, beepsEnabled, detailedBolusInfo.isSMB ? null : + (estimatedUnitsDelivered, percentage) -> { + EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE; + progressUpdateEvent.setStatus(getStringResource(R.string.bolusdelivering, detailedBolusInfo.insulin)); + progressUpdateEvent.setPercent(percentage); + sendEvent(progressUpdateEvent); + }); + + bolusStarted = new Date(); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + addFailureToHistory(System.currentTimeMillis(), PodHistoryEntryType.SetBolus, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + if (OmnipodManager.CommandDeliveryStatus.UNCERTAIN_FAILURE.equals(bolusCommandResult.getCommandDeliveryStatus())) { + // For safety reasons, we treat this as a bolus that has successfully been delivered, in order to prevent insulin overdose + + showErrorDialog(getStringResource(R.string.omnipod_bolus_failed_uncertain), R.raw.boluserror); + } + + // Wait for the bolus to finish + OmnipodManager.BolusDeliveryResult bolusDeliveryResult = + bolusCommandResult.getDeliveryResultSubject().blockingGet(); + + double unitsDelivered = bolusDeliveryResult.getUnitsDelivered(); + + if (pumpStatus != null && !detailedBolusInfo.isSMB) { + lastBolusTime = pumpStatus.lastBolusTime = bolusStarted; + lastBolusUnits = pumpStatus.lastBolusAmount = unitsDelivered; + } + + long pumpId = addSuccessToHistory(bolusStarted.getTime(), PodHistoryEntryType.SetBolus, unitsDelivered + ";" + detailedBolusInfo.carbs); + + detailedBolusInfo.date = bolusStarted.getTime(); + detailedBolusInfo.insulin = unitsDelivered; + detailedBolusInfo.pumpId = pumpId; + detailedBolusInfo.source = Source.PUMP; + + TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, false); + + if (delegate.getPodState().hasFaultEvent()) { + showPodFaultErrorDialog(delegate.getPodState().getFaultEvent().getFaultEventType(), R.raw.urgentalarm); + } + + return new PumpEnactResult().success(true).enacted(true).bolusDelivered(unitsDelivered); + } + + @Override + public PumpEnactResult cancelBolus() { + long time = System.currentTimeMillis(); + String comment = null; + while (delegate.hasActiveBolus()) { + try { + delegate.cancelBolus(isBolusBeepsEnabled()); + addSuccessToHistory(time, PodHistoryEntryType.CancelBolus, null); + return new PumpEnactResult().success(true).enacted(true); + } catch (PodFaultException ex) { + showPodFaultErrorDialog(ex.getFaultEvent().getFaultEventType(), null); + addSuccessToHistory(time, PodHistoryEntryType.CancelBolus, null); + return new PumpEnactResult().success(true).enacted(true); + } catch (Exception ex) { + comment = handleAndTranslateException(ex); + } + } + + addFailureToHistory(time, PodHistoryEntryType.CancelBolus, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + @Override + public PumpEnactResult setTemporaryBasal(TempBasalPair tempBasalPair) { + boolean beepsEnabled = isTempBasalBeepsEnabled(); + long time = System.currentTimeMillis(); + try { + delegate.setTemporaryBasal(PumpType.Insulet_Omnipod.determineCorrectBasalSize(tempBasalPair.getInsulinRate()), Duration.standardMinutes(tempBasalPair.getDurationMinutes()), beepsEnabled, beepsEnabled); + time = System.currentTimeMillis(); + } catch (Exception ex) { + if ((ex instanceof OmnipodException) && !((OmnipodException) ex).isCertainFailure()) { + addToHistory(time, PodHistoryEntryType.SetTemporaryBasal, "Uncertain failure", false); + return new PumpEnactResult().success(false).enacted(false).comment(getStringResource(R.string.omnipod_error_set_temp_basal_failed_uncertain)); + } + String comment = handleAndTranslateException(ex); + addFailureToHistory(time, PodHistoryEntryType.SetTemporaryBasal, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + reportImplicitlyCanceledTbr(); + + long pumpId = addSuccessToHistory(time, PodHistoryEntryType.SetTemporaryBasal, tempBasalPair); + + pumpStatus.tempBasalStart = time; + pumpStatus.tempBasalAmount = tempBasalPair.getInsulinRate(); + pumpStatus.tempBasalLength = tempBasalPair.getDurationMinutes(); + pumpStatus.tempBasalEnd = DateTimeUtil.getTimeInFutureFromMinutes(time, tempBasalPair.getDurationMinutes()); + pumpStatus.tempBasalPumpId = pumpId; + + TemporaryBasal tempStart = new TemporaryBasal() // + .date(time) // + .duration(tempBasalPair.getDurationMinutes()) // + .absolute(tempBasalPair.getInsulinRate()) // + .pumpId(pumpId) + .source(Source.PUMP); + + TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempStart); + + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public PumpEnactResult cancelTemporaryBasal() { + long time = System.currentTimeMillis(); + try { + delegate.cancelTemporaryBasal(isTempBasalBeepsEnabled()); + addSuccessToHistory(time, PodHistoryEntryType.CancelTemporaryBasalForce, null); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + addFailureToHistory(time, PodHistoryEntryType.CancelTemporaryBasalForce, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public PumpEnactResult acknowledgeAlerts() { + long time = System.currentTimeMillis(); + try { + delegate.acknowledgeAlerts(); + addSuccessToHistory(time, PodHistoryEntryType.AcknowledgeAlerts, null); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + addFailureToHistory(time, PodHistoryEntryType.AcknowledgeAlerts, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + return new PumpEnactResult().success(true).enacted(true); + } + + @Override + public void setPumpStatus(OmnipodPumpStatus pumpStatus) { + this.pumpStatus = pumpStatus; + this.getCommunicationService().setPumpStatus(pumpStatus); + updatePumpStatus(delegate.getPodState()); + } + + // TODO should we add this to the OmnipodCommunicationManager interface? + public PumpEnactResult getPodInfo(PodInfoType podInfoType) { + long time = System.currentTimeMillis(); + try { + // TODO how can we return the PodInfo response? + // This method is useless unless we return the PodInfoResponse, + // because the pod state we keep, doesn't get updated from a PodInfoResponse. + // We use StatusResponses for that, which can be obtained from the getPodStatus method + PodInfoResponse podInfo = delegate.getPodInfo(podInfoType); + addSuccessToHistory(time, PodHistoryEntryType.GetPodInfo, podInfo); + return new PumpEnactResult().success(true).enacted(true); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + addFailureToHistory(time, PodHistoryEntryType.GetPodInfo, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + } + + public PumpEnactResult suspendDelivery() { + try { + delegate.suspendDelivery(isBasalBeepsEnabled()); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + return new PumpEnactResult().success(true).enacted(true); + } + + public PumpEnactResult resumeDelivery() { + try { + delegate.resumeDelivery(isBasalBeepsEnabled()); + } catch (Exception ex) { + String comment = handleAndTranslateException(ex); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + return new PumpEnactResult().success(true).enacted(true); + } + + // TODO should we add this to the OmnipodCommunicationManager interface? + // Updates the pods current time based on the device timezone and the pod's time zone + public PumpEnactResult setTime() { + long time = System.currentTimeMillis(); + try { + delegate.setTime(isBasalBeepsEnabled()); + // Because set time actually suspends and then resumes delivery, TBR is implicitly cancelled + reportImplicitlyCanceledTbr(); + addSuccessToHistory(time, PodHistoryEntryType.SetTime, null); + } catch (Exception ex) { + if ((ex instanceof OmnipodException) && !((OmnipodException) ex).isCertainFailure()) { + reportImplicitlyCanceledTbr(); + addFailureToHistory(time, PodHistoryEntryType.SetTime, "Uncertain failure"); + return new PumpEnactResult().success(false).enacted(false).comment(getStringResource(R.string.omnipod_error_set_time_failed_uncertain)); + } + String comment = handleAndTranslateException(ex); + reportImplicitlyCanceledTbr(); + addFailureToHistory(time, PodHistoryEntryType.SetTime, comment); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + return new PumpEnactResult().success(true).enacted(true); + } + + public PodInfoRecentPulseLog readPulseLog() { + PodInfoResponse response = delegate.getPodInfo(PodInfoType.RECENT_PULSE_LOG); + return response.getPodInfo(); + } + + public OmnipodCommunicationService getCommunicationService() { + return delegate.getCommunicationService(); + } + + public DateTime getTime() { + return delegate.getTime(); + } + + public boolean isInitialized() { + return delegate.isReadyForDelivery(); + } + + public String getPodStateAsString() { + return delegate.getPodStateAsString(); + } + + private void reportImplicitlyCanceledTbr() { + TreatmentsPlugin plugin = TreatmentsPlugin.getPlugin(); + if (plugin.isTempBasalInProgress()) { + if (isLoggingEnabled()) { + LOG.debug("Reporting implicitly cancelled TBR to Treatments plugin"); + } + + long time = System.currentTimeMillis() - 1000; + + addSuccessToHistory(time, PodHistoryEntryType.CancelTemporaryBasal, null); + + TemporaryBasal temporaryBasal = new TemporaryBasal() // + .date(time) // + .duration(0) // + .pumpId(pumpStatus.tempBasalPumpId) + .source(Source.PUMP); + + plugin.addToHistoryTempBasal(temporaryBasal); + } + } + + + public long addSuccessToHistory(long requestTime, PodHistoryEntryType entryType, Object data) { + return addToHistory(requestTime, entryType, data, true); + } + + public long addFailureToHistory(long requestTime, PodHistoryEntryType entryType, Object data) { + return addToHistory(requestTime, entryType, data, false); + } + + + public long addToHistory(long requestTime, PodHistoryEntryType entryType, Object data, boolean success) { + + PodHistory podHistory = new PodHistory(requestTime, entryType); + + if (data != null) { + if (data instanceof String) { + podHistory.setData((String) data); + } else { + podHistory.setData(OmnipodUtil.getGsonInstance().toJson(data)); + } + } + + podHistory.setSuccess(success); + podHistory.setPodSerial(pumpStatus.podNumber); + + MainApp.getDbHelper().createOrUpdate(podHistory); + + return podHistory.getPumpId(); + + } + + private void handleSetupActionResult(PodInitActionType podInitActionType, PodInitReceiver podInitReceiver, SetupActionResult res, long time, Profile profile) { + String comment = null; + switch (res.getResultType()) { + case FAILURE: + if (isLoggingEnabled()) { + LOG.error("Setup action failed: illegal setup progress: {}", res.getSetupProgress()); + } + comment = getStringResource(R.string.omnipod_driver_error_invalid_progress_state, res.getSetupProgress()); + break; + case VERIFICATION_FAILURE: + if (isLoggingEnabled()) { + LOG.error("Setup action verification failed: caught exception", res.getException()); + } + comment = getStringResource(R.string.omnipod_driver_error_setup_action_verification_failed); + break; + } + + if (podInitActionType == PodInitActionType.PairAndPrimeWizardStep) { + addToHistory(time, PodHistoryEntryType.PairAndPrime, comment, res.getResultType().isSuccess()); + } else { + addToHistory(time, PodHistoryEntryType.FillCannulaSetBasalProfile, res.getResultType().isSuccess() ? profile.getBasalValues() : comment, res.getResultType().isSuccess()); + } + + podInitReceiver.returnInitTaskStatus(podInitActionType, res.getResultType().isSuccess(), comment); + } + + private String handleAndTranslateException(Exception ex) { + String comment; + + if (ex instanceof OmnipodException) { + if (ex instanceof ActionInitializationException || ex instanceof CommandInitializationException) { + comment = getStringResource(R.string.omnipod_driver_error_invalid_parameters); + } else if (ex instanceof CommunicationException) { + comment = getStringResource(R.string.omnipod_driver_error_communication_failed); + } else if (ex instanceof CrcMismatchException) { + comment = getStringResource(R.string.omnipod_driver_error_crc_mismatch); + } else if (ex instanceof IllegalPacketTypeException) { + comment = getStringResource(R.string.omnipod_driver_error_invalid_packet_type); + } else if (ex instanceof IllegalPodProgressException || ex instanceof IllegalSetupProgressException + || ex instanceof IllegalDeliveryStatusException) { + comment = getStringResource(R.string.omnipod_driver_error_invalid_progress_state); + } else if (ex instanceof IllegalResponseException) { + comment = getStringResource(R.string.omnipod_driver_error_invalid_response); + } else if (ex instanceof MessageDecodingException) { + comment = getStringResource(R.string.omnipod_driver_error_message_decoding_failed); + } else if (ex instanceof NonceOutOfSyncException) { + comment = getStringResource(R.string.omnipod_driver_error_nonce_out_of_sync); + } else if (ex instanceof NonceResyncException) { + comment = getStringResource(R.string.omnipod_driver_error_nonce_resync_failed); + } else if (ex instanceof NotEnoughDataException) { + comment = getStringResource(R.string.omnipod_driver_error_not_enough_data); + } else if (ex instanceof PodFaultException) { + FaultEventType faultEventType = ((PodFaultException) ex).getFaultEvent().getFaultEventType(); + showPodFaultErrorDialog(faultEventType, R.raw.urgentalarm); + comment = createPodFaultErrorMessage(faultEventType); + } else if (ex instanceof PodReturnedErrorResponseException) { + comment = getStringResource(R.string.omnipod_driver_error_pod_returned_error_response); + } else { + // Shouldn't be reachable + comment = getStringResource(R.string.omnipod_driver_error_unexpected_exception_type, ex.getClass().getName()); + } + if (isLoggingEnabled()) { + LOG.error(String.format("Caught OmnipodException[certainFailure=%s] from OmnipodManager (user-friendly error message: %s)", ((OmnipodException) ex).isCertainFailure(), comment), ex); + } + } else { + comment = getStringResource(R.string.omnipod_driver_error_unexpected_exception_type, ex.getClass().getName()); + if (isLoggingEnabled()) { + LOG.error(String.format("Caught unexpected exception type[certainFailure=false] from OmnipodManager (user-friendly error message: %s)", comment), ex); + } + } + + return comment; + } + + private String createPodFaultErrorMessage(FaultEventType faultEventType) { + String comment; + comment = getStringResource(R.string.omnipod_driver_error_pod_fault, + ByteUtil.convertUnsignedByteToInt(faultEventType.getValue()), faultEventType.name()); + return comment; + } + + private void sendEvent(Event event) { + RxBus.INSTANCE.send(event); + } + + private void showPodFaultErrorDialog(FaultEventType faultEventType, Integer sound) { + showErrorDialog(createPodFaultErrorMessage(faultEventType), sound); + } + + private void showErrorDialog(String message, Integer sound) { + Intent intent = new Intent(MainApp.instance(), ErrorHelperActivity.class); + intent.putExtra("soundid", sound == null ? 0 : sound); + intent.putExtra("status", message); + intent.putExtra("title", MainApp.gs(R.string.treatmentdeliveryerror)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(intent); + } + + private void showNotification(String message, int urgency, Integer sound) { + Notification notification = new Notification( // + Notification.OMNIPOD_PUMP_ALARM, // + message, // + urgency); + if (sound != null) { + notification.soundId = sound; + } + sendEvent(new EventNewNotification(notification)); + } + + private String translateAlertType(AlertType alertType) { + if (alertType == null) { + return getStringResource(R.string.omnipod_alert_unknown_alert); + } + switch (alertType) { + case FINISH_PAIRING_REMINDER: + return getStringResource(R.string.omnipod_alert_finish_pairing_reminder); + case FINISH_SETUP_REMINDER: + return getStringResource(R.string.omnipod_alert_finish_setup_reminder_reminder); + case EXPIRATION_ALERT: + return getStringResource(R.string.omnipod_alert_expiration); + case EXPIRATION_ADVISORY_ALERT: + return getStringResource(R.string.omnipod_alert_expiration_advisory); + case SHUTDOWN_IMMINENT_ALARM: + return getStringResource(R.string.omnipod_alert_shutdown_imminent); + case LOW_RESERVOIR_ALERT: + return getStringResource(R.string.omnipod_alert_low_reservoir); + default: + return alertType.name(); + } + } + + private boolean isBolusBeepsEnabled() { + return this.pumpStatus.beepBolusEnabled; + } + + private boolean isSmbBeepsEnabled() { + return this.pumpStatus.beepSMBEnabled; + } + + private boolean isBasalBeepsEnabled() { + return this.pumpStatus.beepBasalEnabled; + } + + private boolean isTempBasalBeepsEnabled() { + return this.pumpStatus.beepTBREnabled; + } + + private String getStringResource(int id, Object... args) { + return MainApp.gs(id, args); + } + + private boolean isLoggingEnabled() { + return L.isEnabled(L.PUMP); + } + + static BasalSchedule mapProfileToBasalSchedule(Profile profile) { + if (profile == null) { + throw new IllegalArgumentException("Profile can not be null"); + } + Profile.ProfileValue[] basalValues = profile.getBasalValues(); + if (basalValues == null) { + throw new IllegalArgumentException("Basal values can not be null"); + } + List entries = new ArrayList<>(); + for (Profile.ProfileValue basalValue : basalValues) { + entries.add(new BasalScheduleEntry(PumpType.Insulet_Omnipod.determineCorrectBasalSize(basalValue.value), + Duration.standardSeconds(basalValue.timeAsSeconds))); + } + + return new BasalSchedule(entries); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistory.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistory.java new file mode 100644 index 00000000000..f77c9f54be6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistory.java @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.db; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.db.DatabaseHelper; +import info.nightscout.androidaps.db.DbObjectBase; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.utils.DateTimeUtil; + +/** + * Created by andy on 30.11.2019. + */ +@DatabaseTable(tableName = DatabaseHelper.DATABASE_POD_HISTORY) +public class PodHistory implements DbObjectBase, Comparable { + + private static Logger log = LoggerFactory.getLogger(L.DATABASE); + + @DatabaseField(id = true) + public long date; + + private PodHistoryEntryType podHistoryEntryType; + + @DatabaseField + private long podEntryTypeCode; + + @DatabaseField + private String data; + + @DatabaseField + private boolean success; + + @DatabaseField + private long pumpId; + + @DatabaseField + private String podSerial; + + @DatabaseField + private Boolean successConfirmed; + + public PodHistory() { + generatePumpId(); + } + + + public PodHistory(PodHistoryEntryType podDbEntryType) { + this.date = System.currentTimeMillis(); + this.podHistoryEntryType = podDbEntryType; + this.podEntryTypeCode = podDbEntryType.getCode(); + generatePumpId(); + } + + + public PodHistory(long dateTimeInMillis, PodHistoryEntryType podDbEntryType) { + this.date = dateTimeInMillis; + this.podHistoryEntryType = podDbEntryType; + this.podEntryTypeCode = podDbEntryType.getCode(); + generatePumpId(); + } + + + @Override + public long getDate() { + return this.date; + } + + public void setDate(long date) { + this.date = date; + } + + public String getDateTimeString() { + return DateTimeUtil.toStringFromTimeInMillis(this.date); + } + + public PodHistoryEntryType getPodDbEntryType() { + return PodHistoryEntryType.getByCode((int) this.podEntryTypeCode); + } + + public void setPodDbEntryType(PodHistoryEntryType podDbEntryType) { + //this.podHistoryEntryType = podDbEntryType; + this.podEntryTypeCode = podDbEntryType.getCode(); + } + + public long getPodEntryTypeCode() { + return podEntryTypeCode; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public void setPumpId(long pumpId) { + this.pumpId = pumpId; + } + + public Boolean getSuccessConfirmed() { + return successConfirmed; + } + + public void setSuccessConfirmed(Boolean successConfirmed) { + this.successConfirmed = successConfirmed; + } + + @Override + public long getPumpId() { + return pumpId; + } + + private void generatePumpId() { + this.pumpId = (DateTimeUtil.toATechDate(this.date) * 100L) + podEntryTypeCode; + } + + + public String getPodSerial() { + return podSerial; + } + + public void setPodSerial(String podSerial) { + this.podSerial = podSerial; + } + + @Override + public int compareTo(PodHistory otherOne) { + return (int) (otherOne.date - this.date); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistoryEntryType.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistoryEntryType.java new file mode 100644 index 00000000000..e776de681fe --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/db/PodHistoryEntryType.java @@ -0,0 +1,93 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.db; + +import androidx.annotation.IdRes; +import androidx.annotation.StringRes; + +import java.util.HashMap; +import java.util.Map; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpHistoryEntryGroup; + +/** + * Created by andy on 24.11.2019 + */ +public enum PodHistoryEntryType { + + PairAndPrime(1, R.string.omnipod_init_pod_wizard_step2_title, PumpHistoryEntryGroup.Prime), + FillCannulaSetBasalProfile(2, R.string.omnipod_init_pod_wizard_step4_title, PumpHistoryEntryGroup.Prime), + DeactivatePod(3, R.string.omnipod_cmd_deactivate_pod, PumpHistoryEntryGroup.Prime), + ResetPodState(4, R.string.omnipod_cmd_reset_pod, PumpHistoryEntryGroup.Prime), + + SetTemporaryBasal(10, R.string.omnipod_cmd_set_tbr, PumpHistoryEntryGroup.Basal), + CancelTemporaryBasal(11, R.string.omnipod_cmd_cancel_tbr, PumpHistoryEntryGroup.Basal), + CancelTemporaryBasalForce(12, R.string.omnipod_cmd_cancel_tbr_forced, PumpHistoryEntryGroup.Basal), + + SetBasalSchedule(20, R.string.omnipod_cmd_set_basal_schedule, PumpHistoryEntryGroup.Basal), + + GetPodStatus(30, R.string.omnipod_cmd_get_pod_status, PumpHistoryEntryGroup.Configuration), + GetPodInfo(31, R.string.omnipod_cmd_get_pod_info, PumpHistoryEntryGroup.Configuration), + SetTime(32, R.string.omnipod_cmd_set_time, PumpHistoryEntryGroup.Configuration), + + SetBolus(40, R.string.omnipod_cmd_set_bolus, PumpHistoryEntryGroup.Bolus), + CancelBolus(41, R.string.omnipod_cmd_cancel_bolus, PumpHistoryEntryGroup.Bolus), + + ConfigureAlerts(50, R.string.omnipod_cmd_configure_alerts, PumpHistoryEntryGroup.Alarm), + AcknowledgeAlerts(51, R.string.omnipod_cmd_acknowledge_alerts, PumpHistoryEntryGroup.Alarm), + + SuspendDelivery(60, R.string.omnipod_cmd_suspend_delivery, PumpHistoryEntryGroup.Basal), + ResumeDelivery(61, R.string.omnipod_cmd_resume_delivery, PumpHistoryEntryGroup.Basal), + + UnknownEntryType(99, R.string.omnipod_cmd_unknown_entry) + ; + + private int code; + private static Map instanceMap; + + @StringRes + private int resourceId; + + private PumpHistoryEntryGroup group; + + + static { + instanceMap = new HashMap<>(); + + for (PodHistoryEntryType value : values()) { + instanceMap.put(value.code, value); + } + } + + + PodHistoryEntryType(int code, @StringRes int resourceId) { + this.code = code; + this.resourceId = resourceId; + } + + PodHistoryEntryType(int code, @StringRes int resourceId, PumpHistoryEntryGroup group) { + this.code = code; + this.resourceId = resourceId; + this.group = group; + } + + public int getCode() { + return code; + } + + public PumpHistoryEntryGroup getGroup() { + return this.group; + } + + + public static PodHistoryEntryType getByCode(int code) { + if (instanceMap.containsKey(code)) { + return instanceMap.get(code); + } else { + return UnknownEntryType; + } + } + + public int getResourceId() { + return resourceId; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIComm.java new file mode 100644 index 00000000000..bc8e6f557af --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIComm.java @@ -0,0 +1,86 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodUIComm { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + OmnipodCommunicationManagerInterface ocmInstance = null; + OmnipodUIPostprocessor uiPostprocessor; // = new OmnipodUIPostprocessor(); + + + private OmnipodCommunicationManagerInterface getCommunicationManager() { + return ocmInstance; + } + + public OmnipodUIComm(OmnipodCommunicationManagerInterface communicationManager, OmnipodPumpPlugin plugin, OmnipodPumpStatus status) { + ocmInstance = communicationManager; + uiPostprocessor = new OmnipodUIPostprocessor(plugin, status); + } + + + public OmnipodUITask executeCommand(OmnipodCommandType commandType, Object... parameters) { + + if (isLogEnabled()) + LOG.warn("Execute Command: " + commandType.name()); + + OmnipodUITask task = new OmnipodUITask(commandType, parameters); + + OmnipodUtil.setCurrentCommand(commandType); + + // new Thread(() -> { + // LOG.warn("@@@ Start Thread"); + // + // task.execute(getCommunicationManager()); + // + // LOG.warn("@@@ End Thread"); + // }); + + task.execute(getCommunicationManager()); + + // for (int i = 0; i < getMaxWaitTime(commandType); i++) { + // synchronized (task) { + // // try { + // // + // // //task.wait(1000); + // // } catch (InterruptedException e) { + // // LOG.error("executeCommand InterruptedException", e); + // // } + // + // + // SystemClock.sleep(1000); + // } + // + // if (task.isReceived()) { + // break; + // } + // } + + if (!task.isReceived() && isLogEnabled()) { + LOG.warn("Reply not received for " + commandType); + } + + task.postProcess(uiPostprocessor); + + return task; + + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIPostprocessor.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIPostprocessor.java new file mode 100644 index 00000000000..c86db544b80 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUIPostprocessor.java @@ -0,0 +1,108 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCustomActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodResponseType; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodUIPostprocessor { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + private OmnipodPumpStatus pumpStatus; + private OmnipodPumpPlugin omnipodPumpPlugin; + + + public OmnipodUIPostprocessor(OmnipodPumpPlugin plugin, OmnipodPumpStatus pumpStatus) { + this.pumpStatus = pumpStatus; + this.omnipodPumpPlugin = plugin; + } + + + // this is mostly intended for command that return certain statuses (Remaining Insulin, ...), and + // where responses won't be directly used + public void postProcessData(OmnipodUITask uiTask) { + + switch (uiTask.commandType) { + + case SetBolus: { + if (uiTask.returnData!=null) { + + PumpEnactResult result = uiTask.returnData; + + DetailedBolusInfo detailedBolusInfo = (DetailedBolusInfo)uiTask.getObjectFromParameters(0); + + if (result.success) { + boolean isSmb = detailedBolusInfo.isSMB; + + if (!isSmb) { + pumpStatus.lastBolusAmount = detailedBolusInfo.insulin; + pumpStatus.lastBolusTime = new Date(); + } + } + } + } break; + + case CancelTemporaryBasal: { + pumpStatus.tempBasalStart = 0; + pumpStatus.tempBasalEnd = 0; + pumpStatus.tempBasalAmount = null; + pumpStatus.tempBasalLength = null; + } + break; + +// case PairAndPrimePod: { +// if (uiTask.returnData.success) { +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.PairAndPrime, false); +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.FillCanulaSetBasalProfile, true); +// } +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.DeactivatePod, true); +// } +// break; +// +// case FillCanulaAndSetBasalProfile: { +// if (uiTask.returnData.success) { +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.FillCanulaSetBasalProfile, false); +// } +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.DeactivatePod, true); +// } +// break; +// +// case DeactivatePod: +// case ResetPodStatus: { +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.PairAndPrime, true); +// omnipodPumpPlugin.setEnableCustomAction(OmnipodCustomActionType.DeactivatePod, false); +// } +// break; + + + default: + if (isLogEnabled()) + LOG.trace("Post-processing not implemented for {}.", uiTask.commandType.name()); + + } + + + + + + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUITask.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUITask.java new file mode 100644 index 00000000000..3749a80ec2a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/ui/OmnipodUITask.java @@ -0,0 +1,243 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.driver.ui; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodResponseType; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodUITask { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + public OmnipodCommandType commandType; + public PumpEnactResult returnData; + private String errorDescription; + private Object[] parameters; + public PodResponseType responseType; + public Object returnDataObject; + + + public OmnipodUITask(OmnipodCommandType commandType) { + this.commandType = commandType; + } + + + public OmnipodUITask(OmnipodCommandType commandType, Object... parameters) { + this.commandType = commandType; + this.parameters = parameters; + } + + + public void execute(OmnipodCommunicationManagerInterface communicationManager) { + + if (isLogEnabled()) + LOG.debug("OmnipodUITask: @@@ In execute. {}", commandType); + + switch (commandType) { + + case PairAndPrimePod: + returnData = communicationManager.initPod((PodInitActionType) parameters[0], (PodInitReceiver) parameters[1], null); + break; + + case FillCanulaAndSetBasalProfile: + returnData = communicationManager.initPod((PodInitActionType) parameters[0], (PodInitReceiver) parameters[1], (Profile) parameters[2]); + break; + + case DeactivatePod: + returnData = communicationManager.deactivatePod((PodInitReceiver) parameters[0]); + break; + + case ResetPodStatus: + returnData = communicationManager.resetPodStatus(); + break; + + case SetBasalProfile: + returnData = communicationManager.setBasalProfile((Profile) parameters[0]); + break; + + case SetBolus: { + DetailedBolusInfo detailedBolusInfo = (DetailedBolusInfo) parameters[0]; + + if (detailedBolusInfo != null) + returnData = communicationManager.setBolus(detailedBolusInfo); + } + break; + + case GetPodPulseLog: + // This command is very error prone, so retry a few times if it fails + // Can take some time, but that's ok since this is a very specific feature for experts + // And will not be used by normal users + for (int i = 0; 3 > i; i++) { + try { + returnDataObject = communicationManager.readPulseLog(); + responseType = PodResponseType.Acknowledgment; + break; + } catch (Exception ex) { + if (isLogEnabled()) { + LOG.warn("Failed to retrieve pulse log", ex); + } + returnDataObject = null; + responseType = PodResponseType.Error; + } + } + break; + + case GetPodStatus: + returnData = communicationManager.getPodStatus(); + break; + + case CancelBolus: + returnData = communicationManager.cancelBolus(); + break; + + case SetTemporaryBasal: { + TempBasalPair tbr = getTBRSettings(); + if (tbr != null) { + returnData = communicationManager.setTemporaryBasal(tbr); + } + } + break; + + case CancelTemporaryBasal: + returnData = communicationManager.cancelTemporaryBasal(); + break; + + case AcknowledgeAlerts: + returnData = communicationManager.acknowledgeAlerts(); + break; + + case SetTime: + returnData = communicationManager.setTime(); + break; + + default: { + LOG.warn("This commandType is not supported (yet) - {}.", commandType); + responseType = PodResponseType.Error; + } + + } + + if (returnData != null) { + responseType = returnData.success ? PodResponseType.Acknowledgment : PodResponseType.Error; + } + + } + + + private TempBasalPair getTBRSettings() { + return new TempBasalPair(getDoubleFromParameters(0), // + false, // + getIntegerFromParameters(1)); + } + + + private Float getFloatFromParameters(int index) { + return (Float) parameters[index]; + } + + public Object getObjectFromParameters(int index) { + return parameters[index]; + } + + public Double getDoubleFromParameters(int index) { + return (Double) parameters[index]; + } + + public boolean getBooleanFromParameters(int index) { + return (boolean) parameters[index]; + } + + public Integer getIntegerFromParameters(int index) { + return (Integer) parameters[index]; + } + + + public T getResult() { + return (T) returnData; + } + + + public boolean isReceived() { + return (returnData != null || errorDescription != null); + } + + + public void postProcess(OmnipodUIPostprocessor postprocessor) { + + EventOmnipodDeviceStatusChange statusChange; + if (isLogEnabled()) + LOG.debug("OmnipodUITask: @@@ postProcess. {}", commandType); + + LOG.debug("OmnipodUITask: @@@ postProcess. responseType={}", responseType); + + if (responseType == PodResponseType.Data || responseType == PodResponseType.Acknowledgment) { + postprocessor.postProcessData(this); + } + + LOG.debug("OmnipodUITask: @@@ postProcess. responseType={}", responseType); + + if (responseType == PodResponseType.Invalid) { + statusChange = new EventOmnipodDeviceStatusChange(PodDeviceState.ErrorWhenCommunicating, + "Unsupported command in OmnipodUITask"); + OmnipodUtil.getPumpStatus().setLastFailedCommunicationToNow(); + RxBus.INSTANCE.send(statusChange); + } else if (responseType == PodResponseType.Error) { + statusChange = new EventOmnipodDeviceStatusChange(PodDeviceState.ErrorWhenCommunicating, + errorDescription); + OmnipodUtil.getPumpStatus().setLastFailedCommunicationToNow(); + RxBus.INSTANCE.send(statusChange); + } else { + OmnipodUtil.getPumpStatus().setLastCommunicationToNow(); + RxBus.INSTANCE.send(new EventOmnipodPumpValuesChanged()); + } + + OmnipodUtil.setPodDeviceState(PodDeviceState.Sleeping); + } + + + public boolean hasData() { + return (responseType == PodResponseType.Data || responseType == PodResponseType.Acknowledgment); + } + + + public Object getParameter(int index) { + return parameters[index]; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMP); + } + + + public PodResponseType getResponseType() { + return this.responseType; + } + + public boolean wasCommandSuccessful() { + if (returnData == null) { + return false; + } + return returnData.success; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodAcknowledgeAlertsChanged.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodAcknowledgeAlertsChanged.kt new file mode 100644 index 00000000000..99383198583 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodAcknowledgeAlertsChanged.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events + +import info.nightscout.androidaps.events.Event + +/** + * Created by andy on 04.06.2018. + */ +class EventOmnipodAcknowledgeAlertsChanged : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.kt new file mode 100644 index 00000000000..db46550a05b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodDeviceStatusChange.kt @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events + +import info.nightscout.androidaps.events.Event +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkError +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState + +/** + * Created by andy on 4.8.2019 + */ +class EventOmnipodDeviceStatusChange : Event { + + var rileyLinkServiceState: RileyLinkServiceState? = null + var rileyLinkError: RileyLinkError? = null + var podSessionState: PodSessionState? = null + var errorDescription: String? = null + var podDeviceState: PodDeviceState? = null + + @JvmOverloads + constructor(rileyLinkServiceState: RileyLinkServiceState?, rileyLinkError: RileyLinkError? = null) { + this.rileyLinkServiceState = rileyLinkServiceState + this.rileyLinkError = rileyLinkError + } + + constructor(podSessionState: PodSessionState?) { + this.podSessionState = podSessionState + } + + constructor(errorDescription: String?) { + this.errorDescription = errorDescription + } + + constructor(podDeviceState: PodDeviceState?, errorDescription: String?) { + this.podDeviceState = podDeviceState + this.errorDescription = errorDescription + } + + override fun toString(): String { + return ("EventOmnipodDeviceStatusChange [" // + + "rileyLinkServiceState=" + rileyLinkServiceState + + ", rileyLinkError=" + rileyLinkError // + + ", podSessionState=" + podSessionState // + + ", podDeviceState=" + podDeviceState + "]") + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.kt new file mode 100644 index 00000000000..5b0f03a241c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodPumpValuesChanged.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events + +import info.nightscout.androidaps.events.Event + +/** + * Created by andy on 04.06.2018. + */ +class EventOmnipodPumpValuesChanged : Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodRefreshButtonState.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodRefreshButtonState.kt new file mode 100644 index 00000000000..8bbbfd33e63 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/events/EventOmnipodRefreshButtonState.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.events + +import info.nightscout.androidaps.events.Event + +class EventOmnipodRefreshButtonState (val newState : Boolean): Event() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/ActionInitializationException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/ActionInitializationException.java new file mode 100644 index 00000000000..f4511925105 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/ActionInitializationException.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class ActionInitializationException extends OmnipodException { + public ActionInitializationException(String message) { + super(message, true); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java new file mode 100644 index 00000000000..79e54934ca5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommandInitializationException.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class CommandInitializationException extends OmnipodException { + public CommandInitializationException(String message) { + super(message, true); + } + + public CommandInitializationException(String message, Throwable cause) { + super(message, cause, true); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommunicationException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommunicationException.java new file mode 100644 index 00000000000..d33da5a3e15 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CommunicationException.java @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class CommunicationException extends OmnipodException { + private final Type type; + + public CommunicationException(Type type) { + super(type.getDescription(), false); + this.type = type; + } + + public CommunicationException(Type type, Throwable cause) { + super(type.getDescription() + ": "+ cause, cause, false); + this.type = type; + } + + public Type getType() { + return type; + } + + public enum Type { + TIMEOUT("Communication timeout"), + UNEXPECTED_EXCEPTION("Caught an unexpected Exception"); + + private final String description; + + Type(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CrcMismatchException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CrcMismatchException.java new file mode 100644 index 00000000000..c75a173c440 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/CrcMismatchException.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +public class CrcMismatchException extends OmnipodException { + private final int expected; + private final int actual; + + public CrcMismatchException(int expected, int actual) { + super(String.format(Locale.getDefault(), "CRC mismatch: expected %d, got %d", expected, actual), false); + this.expected = expected; + this.actual = actual; + } + + public int getExpected() { + return expected; + } + + public int getActual() { + return actual; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalDeliveryStatusException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalDeliveryStatusException.java new file mode 100644 index 00000000000..f8aa00586d6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalDeliveryStatusException.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.DeliveryStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; + +public class IllegalDeliveryStatusException extends OmnipodException { + private final DeliveryStatus expected; + private final DeliveryStatus actual; + + public IllegalDeliveryStatusException(DeliveryStatus expected, DeliveryStatus actual) { + super(String.format(Locale.getDefault(), "Illegal delivery status: %s, expected: %s", actual, expected), true); + this.expected = expected; + this.actual = actual; + } + + public DeliveryStatus getExpected() { + return expected; + } + + public DeliveryStatus getActual() { + return actual; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPacketTypeException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPacketTypeException.java new file mode 100644 index 00000000000..0e4fb1da2ef --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPacketTypeException.java @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PacketType; + +public class IllegalPacketTypeException extends OmnipodException { + private final PacketType expected; + private final PacketType actual; + + public IllegalPacketTypeException(PacketType expected, PacketType actual) { + super(String.format(Locale.getDefault(), "Illegal packet type: %s, expected %s", + actual, expected), false); + this.expected = expected; + this.actual = actual; + } + + public PacketType getExpected() { + return expected; + } + + public PacketType getActual() { + return actual; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPodProgressException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPodProgressException.java new file mode 100644 index 00000000000..0b88427d763 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalPodProgressException.java @@ -0,0 +1,24 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodProgressStatus; + +public class IllegalPodProgressException extends OmnipodException { + private final PodProgressStatus expected; + private final PodProgressStatus actual; + + public IllegalPodProgressException(PodProgressStatus expected, PodProgressStatus actual) { + super(String.format(Locale.getDefault(), "Illegal setup state: %s, expected: %s", actual, expected), true); + this.expected = expected; + this.actual = actual; + } + + public PodProgressStatus getExpected() { + return expected; + } + + public PodProgressStatus getActual() { + return actual; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalResponseException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalResponseException.java new file mode 100644 index 00000000000..fc38ef92f31 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalResponseException.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.MessageBlockType; + +public class IllegalResponseException extends OmnipodException { + private final String actualClass; + private final MessageBlockType expectedType; + + public IllegalResponseException(String actualClass, MessageBlockType expectedType) { + super(String.format(Locale.getDefault(), "Illegal response type: got class of type %s " + + "for message block type %s", actualClass, expectedType), false); + this.actualClass = actualClass; + this.expectedType = expectedType; + } + + public String getActualClass() { + return actualClass; + } + + public MessageBlockType getExpectedType() { + return expectedType; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalSetupProgressException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalSetupProgressException.java new file mode 100644 index 00000000000..402c40820d3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/IllegalSetupProgressException.java @@ -0,0 +1,25 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import java.util.Locale; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.SetupProgress; + +public class IllegalSetupProgressException extends OmnipodException { + private final SetupProgress expected; + private final SetupProgress actual; + + public IllegalSetupProgressException(SetupProgress expected, SetupProgress actual) { + super(String.format(Locale.getDefault(), "Illegal setup progress: %s, expected: %s", actual, expected), true); + this.expected = expected; + this.actual = actual; + } + + public SetupProgress getExpected() { + return expected; + } + + public SetupProgress getActual() { + return actual; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/MessageDecodingException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/MessageDecodingException.java new file mode 100644 index 00000000000..aff582cfcfd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/MessageDecodingException.java @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class MessageDecodingException extends OmnipodException { + public MessageDecodingException(String message) { + super(message, false); + } + + public MessageDecodingException(String message, Throwable cause) { + super(message, cause, false); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceOutOfSyncException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceOutOfSyncException.java new file mode 100644 index 00000000000..79e63bd05d3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceOutOfSyncException.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class NonceOutOfSyncException extends OmnipodException { + public NonceOutOfSyncException() { + super("Nonce out of sync", true); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceResyncException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceResyncException.java new file mode 100644 index 00000000000..fd37149c751 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NonceResyncException.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public class NonceResyncException extends OmnipodException { + public NonceResyncException() { + super("Nonce resync failed", true); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NotEnoughDataException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NotEnoughDataException.java new file mode 100644 index 00000000000..b067a5590d9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/NotEnoughDataException.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; + +public class NotEnoughDataException extends OmnipodException { + private final byte[] data; + + public NotEnoughDataException(byte[] data) { + super("Not enough data: " + ByteUtil.shortHexString(data), false); + this.data = data; + } + + public byte[] getData() { + return data; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/OmnipodException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/OmnipodException.java new file mode 100644 index 00000000000..22cc9cd0209 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/OmnipodException.java @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +public abstract class OmnipodException extends RuntimeException { + private boolean certainFailure; + + public OmnipodException(String message, boolean certainFailure) { + super(message); + this.certainFailure = certainFailure; + } + + public OmnipodException(String message, Throwable cause, boolean certainFailure) { + super(message, cause); + this.certainFailure = certainFailure; + } + + public boolean isCertainFailure() { + return certainFailure; + } + + public void setCertainFailure(boolean certainFailure) { + this.certainFailure = certainFailure; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodFaultException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodFaultException.java new file mode 100644 index 00000000000..77e6a9c900e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodFaultException.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoFaultEvent; + +public class PodFaultException extends OmnipodException { + private final PodInfoFaultEvent faultEvent; + + public PodFaultException(PodInfoFaultEvent faultEvent) { + super(faultEvent.getFaultEventType().toString(), true); + this.faultEvent = faultEvent; + } + + public PodInfoFaultEvent getFaultEvent() { + return faultEvent; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodReturnedErrorResponseException.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodReturnedErrorResponseException.java new file mode 100644 index 00000000000..2f1d083a670 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/exception/PodReturnedErrorResponseException.java @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.exception; + +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.ErrorResponse; + +public class PodReturnedErrorResponseException extends OmnipodException { + private final ErrorResponse errorResponse; + + public PodReturnedErrorResponseException(ErrorResponse errorResponse) { + super("Pod returned error response: " + errorResponse.getErrorResponseType(), true); + this.errorResponse = errorResponse; + } + + public ErrorResponse getErrorResponse() { + return errorResponse; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java new file mode 100644 index 00000000000..6288d47a397 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/service/RileyLinkOmnipodService.java @@ -0,0 +1,191 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.service; + +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Configuration; +import android.os.Binder; +import android.os.IBinder; + +import com.google.gson.Gson; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkCommunicationManager; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RileyLinkBLE; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.defs.RileyLinkEncodingType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkServiceState; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.defs.RileyLinkTargetDevice; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.OmnipodCommunicationService; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.comm.AapsOmnipodManager; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + * RileyLinkOmnipodService is intended to stay running when the gui-app is closed. + */ +public class RileyLinkOmnipodService extends RileyLinkService { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static RileyLinkOmnipodService instance; + + OmnipodCommunicationManagerInterface omnipodCommunicationManager; + OmnipodPumpStatus pumpStatus = null; + private IBinder mBinder = new LocalBinder(); + + + public RileyLinkOmnipodService() { + super(MainApp.instance().getApplicationContext()); + instance = this; + if (isLogEnabled()) + LOG.debug("RileyLinkOmnipodService newly constructed"); + OmnipodUtil.setOmnipodService(this); + pumpStatus = (OmnipodPumpStatus) OmnipodPumpPlugin.getPlugin().getPumpStatusData(); + //LOG.debug("RRRRRRRRRR: " + pumpStatus); + } + + + public static RileyLinkOmnipodService getInstance() { + return instance; + } + + +// public static MedtronicCommunicationManager getCommunicationManager() { +// return instance.medtronicCommunicationManager; +// } + + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (isLogEnabled()) + LOG.warn("onConfigurationChanged"); + super.onConfigurationChanged(newConfig); + } + + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + + @Override + public RileyLinkEncodingType getEncoding() { + return RileyLinkEncodingType.Manchester; + } + + + /** + * If you have customized RileyLinkServiceData you need to override this + */ + public void initRileyLinkServiceData() { + + rileyLinkServiceData = new RileyLinkServiceData(RileyLinkTargetDevice.Omnipod); + + RileyLinkUtil.setRileyLinkServiceData(rileyLinkServiceData); + RileyLinkUtil.setTargetDevice(RileyLinkTargetDevice.Omnipod); + + // get most recently used RileyLink address + rileyLinkServiceData.rileylinkAddress = SP.getString(RileyLinkConst.Prefs.RileyLinkAddress, ""); + + rileyLinkBLE = new RileyLinkBLE(this.context); // or this + rfspy = new RFSpy(rileyLinkBLE); + rfspy.startReader(); + + RileyLinkUtil.setRileyLinkBLE(rileyLinkBLE); + + // init rileyLinkCommunicationManager + initializeErosOmnipodManager(); + // TODO Dash + } + + private void initializeErosOmnipodManager() { + if (AapsOmnipodManager.getInstance() == null) { + PodSessionState podState = null; + if (SP.contains(OmnipodConst.Prefs.PodState)) { + try { + Gson gson = OmnipodUtil.getGsonInstance(); + String storedPodState = SP.getString(OmnipodConst.Prefs.PodState, null); + LOG.info("PodSessionState-SP: loaded from SharedPreferences: " + storedPodState); + podState = gson.fromJson(storedPodState, PodSessionState.class); + OmnipodUtil.setPodSessionState(podState); + } catch (Exception ex) { + LOG.error("Could not deserialize Pod state", ex); + } + } + OmnipodCommunicationService omnipodCommunicationService = new OmnipodCommunicationService(rfspy); + omnipodCommunicationService.setPumpStatus(pumpStatus); + + omnipodCommunicationManager = new AapsOmnipodManager(omnipodCommunicationService, podState, pumpStatus); + } else { + omnipodCommunicationManager = AapsOmnipodManager.getInstance(); + } + } + + + public void resetRileyLinkConfiguration() { + rfspy.resetRileyLinkConfiguration(); + } + + + @Override + public RileyLinkCommunicationManager getDeviceCommunicationManager() { + if (omnipodCommunicationManager instanceof AapsOmnipodManager) { // Eros + return ((AapsOmnipodManager) omnipodCommunicationManager).getCommunicationService(); + } + // FIXME is this correct for Dash? + return (RileyLinkCommunicationManager) omnipodCommunicationManager; + } + + + public class LocalBinder extends Binder { + + public RileyLinkOmnipodService getServiceInstance() { + return RileyLinkOmnipodService.this; + } + } + + + /* private functions */ + + // PumpInterface - REMOVE + + public boolean isInitialized() { + return RileyLinkServiceState.isReady(RileyLinkUtil.getRileyLinkServiceData().serviceState); + } + + + @Override + public String getDeviceSpecificBroadcastsIdentifierPrefix() { + return null; + } + + + public boolean handleDeviceSpecificBroadcasts(Intent intent) { + return false; + } + + + @Override + public void registerDeviceSpecificBroadcasts(IntentFilter intentFilter) { + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/LogReceiver.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/LogReceiver.java new file mode 100644 index 00000000000..71e19a2efae --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/LogReceiver.java @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; + +public class LogReceiver implements PodInitReceiver { + + private static final Logger LOG = LoggerFactory.getLogger(LogReceiver.class); + + @Override + public void returnInitTaskStatus(PodInitActionType podInitActionType, boolean isSuccess, String errorMessage) { + + if (errorMessage != null) { + LOG.error(podInitActionType.name() + " - Success: " + isSuccess + ", Error Message: " + errorMessage); + } else { + if (isSuccess) { + LOG.info(podInitActionType.name() + " - Success: " + isSuccess); + } else { + LOG.error(podInitActionType.name() + " - NOT Succesful"); + } + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmniCRC.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmniCRC.java new file mode 100644 index 00000000000..ba5c91c0b59 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmniCRC.java @@ -0,0 +1,74 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +public class OmniCRC { + public static final int[] crc16lookup = new int[] { + 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, + 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, + 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, + 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, + 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, + 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, + 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, + 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, + 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, + 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, + 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, + 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, + 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, + 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, + 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, + 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, + 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, + 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, + 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, + 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, + 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, + 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, + 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, + 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, + 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, + 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, + 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, + 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, + 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, + 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, + 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, + 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 + }; + public static final int[] crc8lookup = new int[]{ + 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 + }; + + + public static int crc16(byte[] bytes) { + int crc = 0x0000; + for (byte b : bytes) { + crc = (crc >> 8) ^ crc16lookup[(crc ^ b) & 0xff]; + } + return crc; + } + + public static byte crc8(byte[] bytes) { + byte crc = 0x00; + for (byte b : bytes) { + crc = (byte) crc8lookup[(crc ^ b) & 0xff]; + } + return crc; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java new file mode 100644 index 00000000000..f7b78250c37 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java @@ -0,0 +1,56 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +import org.joda.time.Duration; + +import info.nightscout.androidaps.R; + +/** + * Created by andy on 4.8.2019 + */ + +public class OmnipodConst { + + static final String Prefix = "AAPS.Omnipod."; + + public class Prefs { + public static final String PodState = Prefix + "pod_state"; + public static final int BeepBasalEnabled = R.string.key_omnipod_beep_basal_enabled; + public static final int BeepBolusEnabled = R.string.key_omnipod_beep_bolus_enabled; + public static final int BeepSMBEnabled = R.string.key_omnipod_beep_smb_enabled; + public static final int BeepTBREnabled = R.string.key_omnipod_beep_tbr_enabled; + public static final int PodDebuggingOptionsEnabled = R.string.key_omnipod_pod_debugging_options_enabled; + public static final int TimeChangeEventEnabled = R.string.key_omnipod_timechange_enabled; + } + + public class Statistics { + public static final String StatsPrefix = "omnipod_"; + public static final String FirstPumpStart = Prefix + "first_pump_use"; + public static final String LastGoodPumpCommunicationTime = Prefix + "lastGoodPumpCommunicationTime"; + //public static final String LastGoodPumpFrequency = Prefix + "LastGoodPumpFrequency"; + public static final String TBRsSet = StatsPrefix + "tbrs_set"; + public static final String StandardBoluses = StatsPrefix + "std_boluses_delivered"; + public static final String SMBBoluses = StatsPrefix + "smb_boluses_delivered"; + //public static final String LastPumpHistoryEntry = StatsPrefix + "pump_history_entry"; + } + + public static final double POD_PULSE_SIZE = 0.05; + public static final double POD_BOLUS_DELIVERY_RATE = 0.025; // units per second + public static final double POD_PRIMING_DELIVERY_RATE = 0.05; // units per second + public static final double POD_CANNULA_INSERTION_DELIVERY_RATE = 0.05; // units per second + public static final double MAX_RESERVOIR_READING = 50.0; + public static final double MAX_BOLUS = 30.0; + public static final double MAX_BASAL_RATE = 30.0; + public static final Duration MAX_TEMP_BASAL_DURATION = Duration.standardHours(12); + public static final int DEFAULT_ADDRESS = 0xffffffff; + + public static final Duration AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION = Duration.millis(1500); + + public static final Duration SERVICE_DURATION = Duration.standardHours(80); + public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(9); + public static final Duration END_OF_SERVICE_IMMINENT_WINDOW = Duration.standardHours(1); + public static final Duration NOMINAL_POD_LIFE = Duration.standardHours(72); + public static final double LOW_RESERVOIR_ALERT = 20.0; + + public static final double POD_PRIME_BOLUS_UNITS = 2.6; + public static final double POD_CANNULA_INSERTION_BOLUS_UNITS = 0.5; +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java new file mode 100644 index 00000000000..a7035ac4e55 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodUtil.java @@ -0,0 +1,250 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.util; + +import android.content.Context; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializer; + +import org.jetbrains.annotations.NotNull; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.ISODateTimeFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkUtil; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.data.RLHistoryItem; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommandType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPodType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPumpPluginInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodDeviceState; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodDriverState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodDeviceStatusChange; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.plugins.pump.omnipod_dash.OmnipodDashPumpPlugin; +import info.nightscout.androidaps.utils.OKDialog; + +/** + * Created by andy on 4/8/19. + */ +// FIXME +public class OmnipodUtil extends RileyLinkUtil { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static boolean lowLevelDebug = true; + private static RileyLinkOmnipodService omnipodService; + private static OmnipodPumpStatus omnipodPumpStatus; + private static OmnipodCommandType currentCommand; + private static Gson gsonInstance = createGson(); + //private static PodSessionState podSessionState; + //private static PodDeviceState podDeviceState; + private static OmnipodPumpPluginInterface omnipodPumpPlugin; + private static OmnipodPodType omnipodPodType; + private static OmnipodDriverState driverState = OmnipodDriverState.NotInitalized; + private static PumpType pumpType; + + public static Gson getGsonInstance() { + return gsonInstance; + } + + public static int makeUnsignedShort(int b2, int b1) { + int k = (b2 & 0xff) << 8 | b1 & 0xff; + return k; + } + + public static byte[] getByteArrayFromUnsignedShort(int shortValue, boolean returnFixedSize) { + byte highByte = (byte) (shortValue >> 8 & 0xFF); + byte lowByte = (byte) (shortValue & 0xFF); + + if (highByte > 0) { + return createByteArray(highByte, lowByte); + } else { + return returnFixedSize ? createByteArray(highByte, lowByte) : createByteArray(lowByte); + } + + } + + + public static byte[] createByteArray(byte... data) { + return data; + } + + + public static byte[] createByteArray(List data) { + + byte[] array = new byte[data.size()]; + + for (int i = 0; i < data.size(); i++) { + array[i] = data.get(i); + } + + return array; + } + + + public static boolean isLowLevelDebug() { + return lowLevelDebug; + } + + + public static void setLowLevelDebug(boolean lowLevelDebug) { + OmnipodUtil.lowLevelDebug = lowLevelDebug; + } + + + public static OmnipodCommunicationManagerInterface getOmnipodCommunicationManager() { + return (OmnipodCommunicationManagerInterface) RileyLinkUtil.rileyLinkCommunicationManager; + } + + + public static RileyLinkOmnipodService getOmnipodService() { + return OmnipodUtil.omnipodService; + } + + + public static void setOmnipodService(RileyLinkOmnipodService medtronicService) { + OmnipodUtil.omnipodService = medtronicService; + } + + public static OmnipodCommandType getCurrentCommand() { + return OmnipodUtil.currentCommand; + } + + + // FIXME + public static void setCurrentCommand(OmnipodCommandType currentCommand) { + OmnipodUtil.currentCommand = currentCommand; + + if (currentCommand != null) + historyRileyLink.add(new RLHistoryItem(currentCommand)); + + } + + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + + + public static void displayNotConfiguredDialog(Context context) { + OKDialog.show(context, MainApp.gs(R.string.combo_warning), + MainApp.gs(R.string.omnipod_error_operation_not_possible_no_configuration), null); + } + + public static OmnipodPumpStatus getPumpStatus() { + return omnipodPumpStatus; + } + + public static OmnipodDriverState getDriverState() { + return OmnipodUtil.driverState; + } + + public static void setDriverState(OmnipodDriverState state) { + if (OmnipodUtil.driverState == state) + return; + + OmnipodUtil.driverState = state; + + // TODO maybe remove +// if (OmnipodUtil.omnipodPumpStatus != null) { +// OmnipodUtil.omnipodPumpStatus.driverState = state; +// } +// +// if (OmnipodUtil.omnipodPumpPlugin != null) { +// OmnipodUtil.omnipodPumpPlugin.setDriverState(state); +// } + } + + public static void setPumpStatus(OmnipodPumpStatus omnipodPumpStatus) { + OmnipodUtil.omnipodPumpStatus = omnipodPumpStatus; + } + + private static Gson createGson() { + GsonBuilder gsonBuilder = new GsonBuilder() + .registerTypeAdapter(DateTime.class, (JsonSerializer) (dateTime, typeOfSrc, context) -> + new JsonPrimitive(ISODateTimeFormat.dateTime().print(dateTime))) + .registerTypeAdapter(DateTime.class, (JsonDeserializer) (json, typeOfT, context) -> + ISODateTimeFormat.dateTime().parseDateTime(json.getAsString())) + .registerTypeAdapter(DateTimeZone.class, (JsonSerializer) (timeZone, typeOfSrc, context) -> + new JsonPrimitive(timeZone.getID())) + .registerTypeAdapter(DateTimeZone.class, (JsonDeserializer) (json, typeOfT, context) -> + DateTimeZone.forID(json.getAsString())); + + return gsonBuilder.create(); + } + + public static void setPodSessionState(PodSessionState podSessionState) { + omnipodPumpStatus.podSessionState = podSessionState; + RxBus.INSTANCE.send(new EventOmnipodDeviceStatusChange(podSessionState)); + } + + + public static void setPodDeviceState(PodDeviceState podDeviceState) { + omnipodPumpStatus.podDeviceState = podDeviceState; + } + + + @NotNull + public static OmnipodPumpPluginInterface getPlugin() { + return OmnipodUtil.omnipodPumpPlugin; + } + + + @NotNull + public static void setPlugin(OmnipodPumpPluginInterface pumpPlugin) { + OmnipodUtil.omnipodPumpPlugin = pumpPlugin; + } + + + public static void setOmnipodPodType(OmnipodPodType omnipodPodType) { + OmnipodUtil.omnipodPodType = omnipodPodType; + } + + public static OmnipodPodType getOmnipodPodType() { + return omnipodPodType; + } + + public static PodDeviceState getPodDeviceState() { + return omnipodPumpStatus.podDeviceState; + } + + + public static PodSessionState getPodSessionState() { + return omnipodPumpStatus.podSessionState; + } + + public static boolean isOmnipodEros() { + return OmnipodPumpPlugin.getPlugin().isEnabled(PluginType.PUMP); + } + + public static boolean isOmnipodDash() { + return OmnipodDashPumpPlugin.getPlugin().isEnabled(PluginType.PUMP); + } + + + public static void setPumpType(PumpType pumpType) { + OmnipodUtil.pumpType = pumpType; + } + + public static PumpType getPumpType() { + return pumpType; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/OmnipodDashPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/OmnipodDashPumpPlugin.java new file mode 100644 index 00000000000..afecf93b9f5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/OmnipodDashPumpPlugin.java @@ -0,0 +1,297 @@ +package info.nightscout.androidaps.plugins.pump.omnipod_dash; + +import android.os.Bundle; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.List; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.interfaces.PluginDescription; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpDriverState; +import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodFragment; +import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.ui.OmnipodUIComm; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPodType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodPumpPluginInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodPumpValuesChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.events.EventOmnipodRefreshButtonState; +import info.nightscout.androidaps.plugins.pump.omnipod.service.RileyLinkOmnipodService; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.plugins.pump.omnipod_dash.comm.OmnipodDashCommunicationManager; +import info.nightscout.androidaps.utils.TimeChangeType; + +/** + * Created by andy on 23.04.18. + * + * @author Andy Rozman (andy.rozman@gmail.com) + */ +public class OmnipodDashPumpPlugin extends OmnipodPumpPlugin implements OmnipodPumpPluginInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMP); + + protected static OmnipodDashPumpPlugin plugin = null; + //private RileyLinkOmnipodService omnipodService; + //private OmnipodPumpStatus pumpStatusLocal = null; + + // variables for handling statuses and history + private boolean firstRun = true; + private boolean isRefresh = false; + private boolean isBasalProfileInvalid = false; + private boolean basalProfileChanged = false; + private boolean isInitialized = false; + + public static boolean isBusy = false; + //private List busyTimestamps = new ArrayList<>(); + //private boolean sentIdToFirebase = false; + //private boolean hasTimeDateOrTimeZoneChanged = false; + + private Profile currentProfile; + + + private OmnipodDashPumpPlugin() { + super(new PluginDescription() // + .mainType(PluginType.PUMP) // + .fragmentClass(OmnipodFragment.class.getName()) // + .pluginName(R.string.omnipod_dash_name) // + .shortName(R.string.omnipod_dash_name_short) // + .preferencesId(R.xml.pref_omnipod) // + .description(R.string.description_pump_omnipod_dash), // + PumpType.Insulet_Omnipod_Dash + ); + + displayConnectionMessages = false; + + //OmnipodUtil.setOmnipodPodType(OmnipodPodType.Dash); + + if (omnipodCommunicationManager == null) { + omnipodCommunicationManager = OmnipodDashCommunicationManager.getInstance(); + } + + omnipodUIComm = new OmnipodUIComm(omnipodCommunicationManager, this, this.pumpStatusLocal); + + OmnipodUtil.setPlugin(this); + + // FIXME +// serviceConnection = new ServiceConnection() { +// +// public void onServiceDisconnected(ComponentName name) { +// if (isLoggingEnabled()) +// LOG.debug("RileyLinkOmnipodService is disconnected"); +// omnipodService = null; +// } +// +// public void onServiceConnected(ComponentName name, IBinder service) { +// if (isLoggingEnabled()) +// LOG.debug("RileyLinkOmnipodService is connected"); +// RileyLinkOmnipodService.LocalBinder mLocalBinder = (RileyLinkOmnipodService.LocalBinder) service; +// omnipodService = mLocalBinder.getServiceInstance(); +// +// new Thread(() -> { +// +// for (int i = 0; i < 20; i++) { +// SystemClock.sleep(5000); +// +// if (OmnipodUtil.getPumpStatus() != null) { +// if (isLoggingEnabled()) +// LOG.debug("Starting OmniPod-RileyLink service"); +// if (OmnipodUtil.getPumpStatus().setNotInPreInit()) { +// break; +// } +// } +// } +// }).start(); +// } +// }; + } + + + public static OmnipodDashPumpPlugin getPlugin() { + if (plugin == null) + plugin = new OmnipodDashPumpPlugin(); + return plugin; + } + + + private String getLogPrefix() { + return "OmnipodPlugin::"; + } + + + // FIXME + public Class getServiceClass() { + return RileyLinkOmnipodService.class; + } + + + @Override + public String deviceID() { + return "Omnipod Dash"; + } + + + // Pump Plugin + + private boolean isServiceSet() { + return true; //omnipodService != null; + } + + private boolean isServiceInitialized() { + return true; + } + + + @Override + public boolean isInitialized() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isInitialized"); + return isServiceSet() && isInitialized; + } + + + @Override + public boolean isConnected() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnected"); + return isServiceSet() && isServiceInitialized(); + } + + + @Override + public boolean isConnecting() { + if (isLoggingEnabled() && displayConnectionMessages) + LOG.debug(getLogPrefix() + "isConnecting"); + return !isServiceSet() || !isServiceInitialized(); + } + + + @Override + public void getPumpStatus() { + + if (firstRun) { + initializePump(!isRefresh); + } + +// getPodPumpStatus(); +// +// if (firstRun) { +// initializePump(!isRefresh); +// } else { +// refreshAnyStatusThatNeedsToBeRefreshed(); +// } +// +// MainApp.bus().post(new EventMedtronicPumpValuesChanged()); + } + + + public void resetStatusState() { + firstRun = true; + isRefresh = true; + } + + + private void setRefreshButtonEnabled(boolean enabled) { + RxBus.INSTANCE.send(new EventOmnipodRefreshButtonState(enabled)); + } + + + private void initializePump(boolean realInit) { + + if (isLoggingEnabled()) + LOG.info(getLogPrefix() + "initializePump - start"); + + if (omnipodCommunicationManager == null) { + omnipodCommunicationManager = OmnipodDashCommunicationManager.getInstance(); + } + +// setRefreshButtonEnabled(false); +// +// getPodPumpStatus(); +// +// if (isRefresh) { +// if (isPumpNotReachable()) { +// if (isLoggingEnabled()) +// LOG.error(getLogPrefix() + "initializePump::Pump unreachable."); +// MedtronicUtil.sendNotification(MedtronicNotificationType.PumpUnreachable); +// +// setRefreshButtonEnabled(true); +// +// return; +// } +// +// MedtronicUtil.dismissNotification(MedtronicNotificationType.PumpUnreachable); +// } +// +// this.pumpState = PumpDriverState.Connected; +// +// pumpStatusLocal.setLastCommunicationToNow(); +// setRefreshButtonEnabled(true); + + // TODO need to read status and BasalProfile if pod inited and pod status and set correct commands enabled + + if (!isRefresh) { + pumpState = PumpDriverState.Initialized; + } + + if (!sentIdToFirebase) { + Bundle params = new Bundle(); + params.putString("version", BuildConfig.VERSION); + MainApp.getFirebaseAnalytics().logEvent("OmnipodPumpInit", params); + + sentIdToFirebase = true; + } + + isInitialized = true; + // this.pumpState = PumpDriverState.Initialized; + + this.firstRun = false; + } + + + protected void triggerUIChange() { + RxBus.INSTANCE.send(new EventOmnipodPumpValuesChanged()); + } + + + // OPERATIONS not supported by Pump or Plugin + + //private List customActions = null; + + + @Override + public List getCustomActions() { + + if (customActions == null) { + this.customActions = Arrays.asList( +// customActionPairAndPrime, // +// customActionFillCanullaSetBasalProfile, // +// customActionDeactivatePod, // +// customActionResetPod + ); + } + + return this.customActions; + } + + + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + +// if (isLoggingEnabled()) +// LOG.warn(getLogPrefix() + "Time, Date and/or TimeZone changed. "); +// +// this.hasTimeDateOrTimeZoneChanged = true; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java new file mode 100644 index 00000000000..40105b11268 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod_dash/comm/OmnipodDashCommunicationManager.java @@ -0,0 +1,131 @@ +package info.nightscout.androidaps.plugins.pump.omnipod_dash.comm; + +import android.content.Context; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; +import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.ble.RFSpy; +import info.nightscout.androidaps.plugins.pump.omnipod.comm.message.response.podinfo.PodInfoRecentPulseLog; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.OmnipodCommunicationManagerInterface; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitActionType; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.PodInitReceiver; +import info.nightscout.androidaps.plugins.pump.omnipod.defs.state.PodSessionState; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.OmnipodPumpStatus; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.SP; + +/** + * Created by andy on 4.8.2019 + */ +public class OmnipodDashCommunicationManager implements OmnipodCommunicationManagerInterface { + + private static final Logger LOG = LoggerFactory.getLogger(L.PUMPCOMM); + + private static OmnipodDashCommunicationManager omnipodCommunicationManager; + private String errorMessage; + + + public OmnipodDashCommunicationManager(Context context, RFSpy rfspy) { + omnipodCommunicationManager = this; + OmnipodUtil.getPumpStatus().previousConnection = SP.getLong( + RileyLinkConst.Prefs.LastGoodDeviceCommunicationTime, 0L); + } + + + private PodSessionState getPodSessionState() { + return null; + } + + + public static OmnipodDashCommunicationManager getInstance() { + return omnipodCommunicationManager; + } + + + //@Override + protected void configurePumpSpecificSettings() { + //pumpStatus = OmnipodUtil.getPumpStatus(); + } + + + public String getErrorResponse() { + return this.errorMessage; + } + + + private boolean isLogEnabled() { + return L.isEnabled(L.PUMPCOMM); + } + + + @Override + public PumpEnactResult initPod(PodInitActionType podInitActionType, PodInitReceiver podInitReceiver, Profile profile) { + return null; + } + + public PumpEnactResult getPodStatus() { + return null; + } + + + public PumpEnactResult deactivatePod(PodInitReceiver podInitReceiver) { + return null; + } + + public PumpEnactResult setBasalProfile(Profile profile) { + return null; + } + + public PumpEnactResult resetPodStatus() { + return null; + } + + @Override + public PumpEnactResult setBolus(DetailedBolusInfo detailedBolusInfo) { + return null; + } + + public PumpEnactResult setBolus(Double parameter, boolean isSmb) { + return null; + } + + public PumpEnactResult cancelBolus() { + return null; + } + + public PumpEnactResult setTemporaryBasal(TempBasalPair tbr) { + return null; + } + + public PumpEnactResult cancelTemporaryBasal() { + return null; + } + + @Override + public PumpEnactResult acknowledgeAlerts() { + return null; + } + + @Override + public PumpEnactResult setTime() { + return null; + } + + @Override + public void setPumpStatus(OmnipodPumpStatus pumpStatusLocal) { + + } + + @Override + public PodInfoRecentPulseLog readPulseLog() { + return null; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java deleted file mode 100644 index 0b764f5382c..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.java +++ /dev/null @@ -1,127 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.virtual; - - -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.TextView; - -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.general.actions.defs.CustomAction; -import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; -import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; -import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.FabricPrivacy; - - -public class VirtualPumpFragment extends SubscriberFragment { - private static Logger log = LoggerFactory.getLogger(VirtualPumpFragment.class); - - TextView basaBasalRateView; - TextView tempBasalView; - TextView extendedBolusView; - TextView batteryView; - TextView reservoirView; - TextView pumpTypeView; - TextView pumpSettingsView; - - - private static Handler sLoopHandler = new Handler(); - private static Runnable sRefreshLoop = null; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (sRefreshLoop == null) { - sRefreshLoop = new Runnable() { - @Override - public void run() { - updateGUI(); - sLoopHandler.postDelayed(sRefreshLoop, 60 * 1000L); - } - }; - sLoopHandler.postDelayed(sRefreshLoop, 60 * 1000L); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.virtualpump_fragment, container, false); - basaBasalRateView = (TextView) view.findViewById(R.id.virtualpump_basabasalrate); - tempBasalView = (TextView) view.findViewById(R.id.virtualpump_tempbasal); - extendedBolusView = (TextView) view.findViewById(R.id.virtualpump_extendedbolus); - batteryView = (TextView) view.findViewById(R.id.virtualpump_battery); - reservoirView = (TextView) view.findViewById(R.id.virtualpump_reservoir); - pumpTypeView = (TextView) view.findViewById(R.id.virtualpump_type); - pumpSettingsView = (TextView) view.findViewById(R.id.virtualpump_type_def); - - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } - - return null; - } - - @Subscribe - public void onStatusEvent(final EventVirtualPumpUpdateGui ev) { - updateGUI(); - } - - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null && basaBasalRateView != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - VirtualPumpPlugin virtualPump = VirtualPumpPlugin.getPlugin(); - basaBasalRateView.setText(virtualPump.getBaseBasalRate() + "U"); - TemporaryBasal activeTemp = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis()); - if (activeTemp != null) { - tempBasalView.setText(activeTemp.toStringFull()); - } else { - tempBasalView.setText(""); - } - ExtendedBolus activeExtendedBolus = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (activeExtendedBolus != null) { - extendedBolusView.setText(activeExtendedBolus.toString()); - } else { - extendedBolusView.setText(""); - } - batteryView.setText(virtualPump.batteryPercent + "%"); - reservoirView.setText(virtualPump.reservoirInUnits + "U"); - - virtualPump.refreshConfiguration(); - - PumpType pumpType = virtualPump.getPumpType(); - - pumpTypeView.setText(pumpType.getDescription()); - - String template = MainApp.gs(R.string.virtualpump_pump_def); - - - pumpSettingsView.setText(pumpType.getFullDescription(template, pumpType.hasExtendedBasals())); - - } - }); - } - - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt new file mode 100644 index 00000000000..08402520c24 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpFragment.kt @@ -0,0 +1,85 @@ +package info.nightscout.androidaps.plugins.pump.virtual + +import android.os.Bundle +import android.os.Handler +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventExtendedBolusChange +import info.nightscout.androidaps.events.EventTempBasalChange +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.T +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.virtualpump_fragment.* +import org.slf4j.LoggerFactory + +class VirtualPumpFragment : Fragment() { + private val disposable = CompositeDisposable() + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + init { + refreshLoop = Runnable { + activity?.runOnUiThread { updateGui() } + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.virtualpump_fragment, container, false) + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(RxBus + .toObservable(EventVirtualPumpUpdateGui::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGui() }, { FabricPrivacy.logException(it) }) + ) + disposable.add(RxBus + .toObservable(EventTempBasalChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGui() }, { FabricPrivacy.logException(it) }) + ) + disposable.add(RxBus + .toObservable(EventExtendedBolusChange::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGui() }, { FabricPrivacy.logException(it) }) + ) + loopHandler.postDelayed(refreshLoop, T.mins(1).msecs()) + updateGui() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + loopHandler.removeCallbacks(refreshLoop) + } + + @Synchronized + private fun updateGui() { + val virtualPump = VirtualPumpPlugin.getPlugin() + virtualpump_basabasalrate?.text = MainApp.gs(R.string.pump_basebasalrate, virtualPump.baseBasalRate) + virtualpump_tempbasal?.text = TreatmentsPlugin.getPlugin().getTempBasalFromHistory(System.currentTimeMillis())?.toStringFull() + ?: "" + virtualpump_extendedbolus?.text = TreatmentsPlugin.getPlugin().getExtendedBolusFromHistory(System.currentTimeMillis())?.toString() ?: "" + virtualpump_battery?.text = MainApp.gs(R.string.format_percent, virtualPump.batteryPercent) + virtualpump_reservoir?.text = MainApp.gs(R.string.formatinsulinunits, virtualPump.reservoirInUnits.toDouble()) + + virtualPump.refreshConfiguration() + val pumpType = virtualPump.pumpType + + virtualpump_type?.text = pumpType.description + virtualpump_type_def?.text = pumpType.getFullDescription(MainApp.gs(R.string.virtualpump_pump_def), pumpType.hasExtendedBasals()) + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.java index 461f71c31d8..b583f63cf8f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.java @@ -2,8 +2,6 @@ import android.os.SystemClock; -import com.squareup.otto.Subscribe; - import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -28,6 +26,8 @@ import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; @@ -39,7 +39,12 @@ import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.InstanceId; import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.TimeChangeType; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** @@ -47,6 +52,7 @@ */ public class VirtualPumpPlugin extends PluginBase implements PumpInterface { private Logger log = LoggerFactory.getLogger(L.PUMP); + private CompositeDisposable disposable = new CompositeDisposable(); Integer batteryPercent = 50; Integer reservoirInUnits = 50; @@ -119,19 +125,21 @@ public void setFakingStatus(boolean newStatus) { @Override protected void onStart() { super.onStart(); - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(EventPreferenceChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (event.isChanged(R.string.key_virtualpump_type)) + refreshConfiguration(); + }, FabricPrivacy::logException) + ); refreshConfiguration(); } @Override protected void onStop() { - MainApp.bus().unregister(this); - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange s) { - if (s.isChanged(R.string.key_virtualpump_type)) - refreshConfiguration(); + disposable.clear(); + super.onStop(); } @Override @@ -216,7 +224,7 @@ public PumpEnactResult setNewBasalProfile(Profile profile) { PumpEnactResult result = new PumpEnactResult(); result.success = true; Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.gs(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); return result; } @@ -241,10 +249,14 @@ public double getBaseBasalRate() { @Override - public double getReservoirLevel() { return reservoirInUnits; } + public double getReservoirLevel() { + return reservoirInUnits; + } @Override - public int getBatteryLevel() { return batteryPercent; } + public int getBatteryLevel() { + return batteryPercent; + } @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { @@ -260,21 +272,21 @@ public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { while (delivering < detailedBolusInfo.insulin) { SystemClock.sleep(200); - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.status = String.format(MainApp.gs(R.string.bolusdelivering), delivering); - bolusingEvent.percent = Math.min((int) (delivering / detailedBolusInfo.insulin * 100), 100); - MainApp.bus().post(bolusingEvent); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.bolusdelivering), delivering)); + bolusingEvent.setPercent(Math.min((int) (delivering / detailedBolusInfo.insulin * 100), 100)); + RxBus.INSTANCE.send(bolusingEvent); delivering += 0.1d; } SystemClock.sleep(200); - EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); - bolusingEvent.status = String.format(MainApp.gs(R.string.bolusdelivered), detailedBolusInfo.insulin); - bolusingEvent.percent = 100; - MainApp.bus().post(bolusingEvent); + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.INSTANCE; + bolusingEvent.setStatus(String.format(MainApp.gs(R.string.bolusdelivered), detailedBolusInfo.insulin)); + bolusingEvent.setPercent(100); + RxBus.INSTANCE.send(bolusingEvent); SystemClock.sleep(1000); if (L.isEnabled(L.PUMPCOMM)) log.debug("Delivering treatment insulin: " + detailedBolusInfo.insulin + "U carbs: " + detailedBolusInfo.carbs + "g " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); lastDataTime = System.currentTimeMillis(); TreatmentsPlugin.getPlugin().addToHistoryTreatment(detailedBolusInfo, false); return result; @@ -302,7 +314,7 @@ public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer duratio TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); if (L.isEnabled(L.PUMPCOMM)) log.debug("Setting temp basal absolute: " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); lastDataTime = System.currentTimeMillis(); return result; } @@ -325,7 +337,7 @@ public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMi TreatmentsPlugin.getPlugin().addToHistoryTempBasal(tempBasal); if (L.isEnabled(L.PUMPCOMM)) log.debug("Settings temp basal percent: " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); lastDataTime = System.currentTimeMillis(); return result; } @@ -350,7 +362,7 @@ public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinute TreatmentsPlugin.getPlugin().addToHistoryExtendedBolus(extendedBolus); if (L.isEnabled(L.PUMPCOMM)) log.debug("Setting extended bolus: " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); lastDataTime = System.currentTimeMillis(); return result; } @@ -368,7 +380,7 @@ public PumpEnactResult cancelTempBasal(boolean force) { //tempBasal = null; if (L.isEnabled(L.PUMPCOMM)) log.debug("Canceling temp basal: " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); } lastDataTime = System.currentTimeMillis(); return result; @@ -388,7 +400,7 @@ public PumpEnactResult cancelExtendedBolus() { result.comment = MainApp.gs(R.string.virtualpump_resultok); if (L.isEnabled(L.PUMPCOMM)) log.debug("Canceling extended bolus: " + result); - MainApp.bus().post(new EventVirtualPumpUpdateGui()); + RxBus.INSTANCE.send(new EventVirtualPumpUpdateGui()); lastDataTime = System.currentTimeMillis(); return result; } @@ -437,8 +449,18 @@ public JSONObject getJSONStatus(Profile profile, String profileName) { } @Override - public String deviceID() { - return "VirtualPump"; + public ManufacturerType manufacturer() { + return pumpDescription.pumpType.getManufacturer(); + } + + @Override + public PumpType model() { + return pumpDescription.pumpType; + } + + @Override + public String serialNumber() { + return InstanceId.INSTANCE.instanceId(); } @Override @@ -481,4 +503,11 @@ public void refreshConfiguration() { } -} + + @Override + public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.java deleted file mode 100644 index 795134997a5..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.virtual.events; - -import info.nightscout.androidaps.events.EventUpdateGui; - -/** - * Created by mike on 05.08.2016. - */ -public class EventVirtualPumpUpdateGui extends EventUpdateGui { -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.kt new file mode 100644 index 00000000000..a0498aa14d5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/events/EventVirtualPumpUpdateGui.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.virtual.events + +import info.nightscout.androidaps.events.EventUpdateGui + +class EventVirtualPumpUpdateGui : EventUpdateGui() \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java index 8e33b16302f..8cfa903fb87 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityAAPSPlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -137,7 +137,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP Double[] deviations = new Double[deviationsArray.size()]; deviations = deviationsArray.toArray(deviations); - double sens = profile.getIsf(); + double sens = profile.getIsfMgdl(); String ratioLimit = ""; String sensResult = ""; @@ -148,7 +148,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP Arrays.sort(deviations); double percentile = IobCobCalculatorPlugin.percentile(deviations, 0.50); - double basalOff = percentile * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + double basalOff = percentile * (60 / 5) / sens; double ratio = 1 + (basalOff / profile.getMaxDailyBasal()); if (percentile < 0) { // sensitive diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java index 83800e17219..62987245141 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref0Plugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -131,7 +131,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP Double[] deviations = new Double[deviationsArray.size()]; deviations = deviationsArray.toArray(deviations); - double sens = profile.getIsf(); + double sens = profile.getIsfMgdl(); double ratio = 1; String ratioLimit = ""; @@ -154,10 +154,10 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP double basalOff = 0; if (pSensitive < 0) { // sensitive - basalOff = pSensitive * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pSensitive * (60 / 5.0) / sens; sensResult = "Excess insulin sensitivity detected"; } else if (pResistant > 0) { // resistant - basalOff = pResistant * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pResistant * (60 / 5.0) / sens; sensResult = "Excess insulin resistance detected"; } else { sensResult = "Sensitivity normal"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java index 3ce5c4861ce..131cafe89cc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityOref1Plugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -134,7 +134,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP if (L.isEnabled(L.AUTOSENS)) log.debug("Using most recent " + deviationsArray.size() + " deviations"); if (deviationsArray.size() < 96) { - int pad = Math.round((1 - deviationsArray.size() / 96) * 18); + int pad = (int) Math.round((1 - (double) deviationsArray.size() / 96) * 18); if (L.isEnabled(L.AUTOSENS)) log.debug("Adding " + pad + " more zero deviations"); for (int d = 0; d < pad; d++) { @@ -146,7 +146,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP Double[] deviations = new Double[deviationsArray.size()]; deviations = deviationsArray.toArray(deviations); - double sens = profile.getIsf(); + double sens = profile.getIsfMgdl(); double ratio = 1; String ratioLimit = ""; @@ -156,7 +156,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP log.debug("Records: " + index + " " + pastSensitivity); Arrays.sort(deviations); - + /* Not used in calculation for (double i = 0.9; i > 0.1; i = i - 0.01) { if (IobCobCalculatorPlugin.percentile(deviations, (i + 0.01)) >= 0 && IobCobCalculatorPlugin.percentile(deviations, i) < 0) { if (L.isEnabled(L.AUTOSENS)) @@ -164,19 +164,20 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP } if (IobCobCalculatorPlugin.percentile(deviations, (i + 0.01)) > 0 && IobCobCalculatorPlugin.percentile(deviations, i) <= 0) { if (L.isEnabled(L.AUTOSENS)) - log.debug(Math.round(100 * i) + "% of non-meal deviations negative (>50% = resistance)"); + log.debug(Math.round(100 * i) + "% of non-meal deviations positive (>50% = resistance)"); } } + */ double pSensitive = IobCobCalculatorPlugin.percentile(deviations, 0.50); double pResistant = IobCobCalculatorPlugin.percentile(deviations, 0.50); double basalOff = 0; if (pSensitive < 0) { // sensitive - basalOff = pSensitive * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pSensitive * (60 / 5.0) / sens; sensResult = "Excess insulin sensitivity detected"; } else if (pResistant > 0) { // resistant - basalOff = pResistant * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + basalOff = pResistant * (60 / 5.0) / sens; sensResult = "Excess insulin resistance detected"; } else { sensResult = "Sensitivity normal"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java index 5b7268b5608..5d0aac4d4cb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/sensitivity/SensitivityWeightedAveragePlugin.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.plugins.sensitivity; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -164,7 +164,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP return new AutosensResult(); } - double sens = profile.getIsf(); + double sens = profile.getIsfMgdl(); String ratioLimit = ""; String sensResult; @@ -173,7 +173,7 @@ public AutosensResult detectSensitivity(IobCobCalculatorPlugin iobCobCalculatorP log.debug("Records: " + index + " " + pastSensitivity); double average = weightedsum / weights; - double basalOff = average * (60 / 5) / Profile.toMgdl(sens, profile.getUnits()); + double basalOff = average * (60 / 5.0) / sens; double ratio = 1 + (basalOff / profile.getMaxDailyBasal()); if (average < 0) { // sensitive diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java index 211cbb0bb0d..b67cadfed41 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/BGSourceFragment.java @@ -1,52 +1,50 @@ package info.nightscout.androidaps.plugins.source; -import android.app.Activity; -import android.content.DialogInterface; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.T; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; /** * Created by mike on 16.10.2017. */ -public class BGSourceFragment extends SubscriberFragment { - RecyclerView recyclerView; +public class BGSourceFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); + private RecyclerView recyclerView; - String units = Constants.MGDL; - - final long MILLS_TO_THE_PAST = T.hours(12).msecs(); + private final long MILLS_TO_THE_PAST = T.hours(12).msecs(); @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.bgsource_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.bgsource_recyclerview); + recyclerView = view.findViewById(R.id.bgsource_recyclerview); recyclerView.setHasFixedSize(true); LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); @@ -55,9 +53,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)); recyclerView.setAdapter(adapter); - if (ConfigBuilderPlugin.getPlugin().getActiveProfileInterface() != null && ConfigBuilderPlugin.getPlugin().getActiveProfileInterface().getProfile() != null && ConfigBuilderPlugin.getPlugin().getActiveProfileInterface().getProfile().getDefaultProfile() != null) - units = ConfigBuilderPlugin.getPlugin().getActiveProfileInterface().getProfile().getDefaultProfile().getUnits(); - return view; } catch (Exception e) { FabricPrivacy.logException(e); @@ -66,19 +61,25 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return null; } - @Subscribe - public void onStatusEvent(final EventAutosensCalculationFinished unused) { - updateGUI(); + @Override + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGUI(), FabricPrivacy::logException) + ); } @Override + public synchronized void onPause() { + disposable.clear(); + super.onPause(); + } + protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - long now = System.currentTimeMillis(); - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)), true); - }); + long now = System.currentTimeMillis(); + recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getAllBgreadingsDataFromTime(now - MILLS_TO_THE_PAST, false)), true); } public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -89,6 +90,7 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final BgReading bgReading = (BgReading) v.getTag(); + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(bgReading.date) + "\n" + bgReading.valueToUnitsToString(ProfileFunctions.getSystemUnits()), () -> { + bgReading.isValid = false; + MainApp.getDbHelper().update(bgReading); + updateGUI(); + }); + }); remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } - - @Override - public void onClick(View v) { - final BgReading bgReading = (BgReading) v.getTag(); - switch (v.getId()) { - - case R.id.bgsource_remove: - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(bgReading.date) + "\n" + bgReading.valueToUnitsToString(units)); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { -/* final String _id = bgReading._id; - if (NSUpload.isIdValid(_id)) { - NSUpload.removeFoodFromNS(_id); - } else { - UploadQueue.removeID("dbAdd", _id); - } -*/ - bgReading.isValid = false; - MainApp.getDbHelper().update(bgReading); - updateGUI(); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - - } - } } } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt new file mode 100644 index 00000000000..1f79b82bc6f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/RandomBgPlugin.kt @@ -0,0 +1,77 @@ +package info.nightscout.androidaps.plugins.source + +import android.content.Intent +import android.os.Handler +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.interfaces.BgSourceInterface +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.isRunningTest +import org.slf4j.LoggerFactory +import java.util.* +import kotlin.math.PI +import kotlin.math.sin + +object RandomBgPlugin : PluginBase(PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + .pluginName(R.string.randombg) + .shortName(R.string.randombg_short) + .description(R.string.description_source_randombg)), BgSourceInterface { + + private val log = LoggerFactory.getLogger(L.BGSOURCE) + + private val loopHandler = Handler() + private lateinit var refreshLoop: Runnable + + const val interval = 1L // minutes + + init { + refreshLoop = Runnable { + handleNewData(Intent()) + loopHandler.postDelayed(refreshLoop, T.mins(interval).msecs()) + } + } + + override fun advancedFilteringSupported(): Boolean { + return false + } + + override fun onStart() { + super.onStart() + loopHandler.postDelayed(refreshLoop, T.mins(interval).msecs()) + } + + override fun onStop() { + super.onStop() + loopHandler.removeCallbacks(refreshLoop) + } + + override fun specialEnableCondition(): Boolean { + return isRunningTest() || VirtualPumpPlugin.getPlugin().isEnabled(PluginType.PUMP) && MainApp.engineeringMode + } + + override fun handleNewData(intent: Intent) { + if (!isEnabled(PluginType.BGSOURCE)) return + val min = 70 + val max = 190 + + val cal = GregorianCalendar() + val currentMinute = cal.get(Calendar.MINUTE) + (cal.get(Calendar.HOUR_OF_DAY) % 2) * 60 + val bgMgdl = min + (max - min) + (max - min) * sin(currentMinute / 120.0 * 2 * PI) + + val bgReading = BgReading() + bgReading.value = bgMgdl + bgReading.date = DateUtil.now() + bgReading.raw = bgMgdl + MainApp.getDbHelper().createIfNotExists(bgReading, "RandomBG") + log.debug("Generated BG: $bgReading") + } +} 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 deleted file mode 100644 index 3ba10564e66..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomG5Plugin.java +++ /dev/null @@ -1,232 +0,0 @@ -package info.nightscout.androidaps.plugins.source; - -import android.content.Intent; -import android.os.Bundle; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.services.Intents; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.SP; - -/** - * Created by mike on 28.11.2017. - */ - -public class SourceDexcomG5Plugin extends PluginBase implements BgSourceInterface { - private static Logger log = LoggerFactory.getLogger(L.BGSOURCE); - - 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) - .preferencesId(R.xml.pref_bgsource) - .description(R.string.description_source_dexcom_g5) - ); - } - - @Override - public boolean advancedFilteringSupported() { - return true; - } - - @Override - public void handleNewData(Intent intent) { - // onHandleIntent Bundle{ data => [{"m_time":1511939180,"m_trend":"NotComputable","m_value":335}]; android.support.content.wakelockid => 95; }Bundle - - if (!isEnabled(PluginType.BGSOURCE)) return; - - if (intent.getAction().equals(Intents.DEXCOMG5_BG)) - handleNewDataOld(intent); - - if (intent.getAction().equals(Intents.DEXCOMG5_BG_NEW)) - handleNewDataNew(intent); - } - - public void handleNewDataOld(Intent intent) { - // onHandleIntent Bundle{ data => [{"m_time":1511939180,"m_trend":"NotComputable","m_value":335}]; android.support.content.wakelockid => 95; }Bundle - - Bundle bundle = intent.getExtras(); - if (bundle == null) return; - - BgReading bgReading = new BgReading(); - - String data = bundle.getString("data"); - if (L.isEnabled(L.BGSOURCE)) - log.debug("Received Dexcom Data", data); - - if (data == null) return; - - try { - JSONArray jsonArray = new JSONArray(data); - if (L.isEnabled(L.BGSOURCE)) - log.debug("Received Dexcom Data size:" + jsonArray.length()); - 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"); - bgReading.date = json.getLong("m_time") * 1000L; - bgReading.raw = 0; - boolean isNew = MainApp.getDbHelper().createIfNotExists(bgReading, "DexcomG5"); - if (isNew && SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { - NSUpload.uploadBg(bgReading, "AndroidAPS-DexcomG5"); - } - if (isNew && SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { - NSUpload.sendToXdrip(bgReading); - } - } - - } catch (JSONException e) { - log.error("Exception: ", e); - } - } - - public void handleNewDataNew(Intent intent) { - - Bundle bundle = intent.getExtras(); - if (bundle == null) return; - - if (L.isEnabled(L.BGSOURCE)) { - if (bundle.containsKey("transmitterSystemTime")) - log.debug("transmitterSystemTime: " + DateUtil.dateAndTimeFullString(bundle.getLong("transmitterSystemTime"))); - if (bundle.containsKey("transmitterRemainingTime")) - log.debug("transmitterRemainingTime: " + DateUtil.dateAndTimeFullString(bundle.getLong("transmitterRemainingTime"))); - log.debug("transmitterId: " + bundle.getString("transmitterId")); - if (bundle.containsKey("transmitterActivatedOn")) - log.debug("transmitterActivatedOn: " + DateUtil.dateAndTimeFullString(bundle.getLong("transmitterActivatedOn"))); - log.debug("transmitterVersion: " + bundle.getString("transmitterVersion")); - log.debug("transmitterSoftwareNumber: " + bundle.getString("transmitterSoftwareNumber")); - log.debug("transmitterStorageTimeDays: " + bundle.getInt("transmitterStorageTimeDays")); - log.debug("transmitterApiVersion: " + bundle.getInt("transmitterApiVersion")); - log.debug("transmitterMaxRuntimeDays: " + bundle.getInt("transmitterMaxRuntimeDays")); - log.debug("transmitterMaxStorageTimeDays: " + bundle.getInt("transmitterMaxStorageTimeDays")); - log.debug("transmitterCGMProcessorFirmwareVersion: " + bundle.getString("transmitterCGMProcessorFirmwareVersion")); - log.debug("transmitterBleRadioFirmwareVersion: " + bundle.getString("transmitterBleRadioFirmwareVersion")); - log.debug("transmitterHardwareVersion: " + bundle.getInt("transmitterHardwareVersion")); - log.debug("transmitterBleSoftDeviceVersion: " + bundle.getString("transmitterBleSoftDeviceVersion")); - log.debug("transmitterNordicAsicHwID: " + bundle.getInt("transmitterNordicAsicHwID")); - log.debug("transmitterSessionTimeDays: " + bundle.getInt("transmitterSessionTimeDays")); - log.debug("transmitterFeatureFlags: " + bundle.getInt("transmitterFeatureFlags")); - } - - if (bundle.containsKey("sensorInsertionTime")) { - long sensorInsertionTime = bundle.getLong("sensorInsertionTime"); - if (L.isEnabled(L.BGSOURCE)) - log.debug("sensorInsertionTime: " + DateUtil.dateAndTimeFullString(sensorInsertionTime)); - if (SP.getBoolean(R.string.key_dexcom_lognssensorchange, false)) { - try { - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(sensorInsertionTime) == null) { - JSONObject data = new JSONObject(); - data.put("enteredBy", "AndroidAPS-DexcomG5"); - data.put("created_at", DateUtil.toISOString(sensorInsertionTime)); - data.put("eventType", CareportalEvent.SENSORCHANGE); - NSUpload.uploadCareportalEntryToNS(data); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - } - - if (bundle.containsKey("glucoseValues")) { - int[] glucoseValues = bundle.getIntArray("glucoseValues"); - int[] glucoseRecordIDs = bundle.getIntArray("glucoseRecordIDs"); - long[] glucoseRecordedTimestamps = bundle.getLongArray("glucoseRecordedTimestamps"); - long[] glucoseSessionStartTimes = bundle.getLongArray("glucoseSessionStartTimes"); - long[] glucoseSystemTimestamps = bundle.getLongArray("glucoseSystemTimestamps"); - String[] glucoseTransmitterIDS = bundle.getStringArray("glucoseTransmitterIDS"); - long[] glucoseTransmitterTimestamps = bundle.getLongArray("glucoseTransmitterTimestamps"); - String[] glucoseTrendsArrows = bundle.getStringArray("glucoseTrendsArrows"); - boolean[] glucoseWasBackfilled = bundle.getBooleanArray("glucoseWasBackfilled"); - - if (L.isEnabled(L.BGSOURCE)) { - log.debug("glucoseValues", Arrays.toString(glucoseValues)); - log.debug("glucoseRecordIDs", Arrays.toString(glucoseRecordIDs)); - log.debug("glucoseRecordedTimestamps", Arrays.toString(glucoseRecordedTimestamps)); - log.debug("glucoseSessionStartTimes", Arrays.toString(glucoseSessionStartTimes)); - log.debug("glucoseSystemTimestamps", Arrays.toString(glucoseSystemTimestamps)); - log.debug("glucoseTransmitterIDS", Arrays.toString(glucoseTransmitterIDS)); - log.debug("glucoseTransmitterTimestamps", Arrays.toString(glucoseTransmitterTimestamps)); - log.debug("glucoseTrendsArrows", Arrays.toString(glucoseTrendsArrows)); - log.debug("glucoseWasBackfilled", Arrays.toString(glucoseWasBackfilled)); - } - - for (int i = 0; i < glucoseValues.length; i++) { - BgReading bgReading = new BgReading(); - bgReading.value = glucoseValues[i]; - bgReading.direction = glucoseTrendsArrows[i]; - bgReading.date = glucoseTransmitterTimestamps[i]; - bgReading.raw = 0; - boolean isNew = MainApp.getDbHelper().createIfNotExists(bgReading, "DexcomG5"); - if (isNew && SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { - NSUpload.uploadBg(bgReading, "AndroidAPS-DexcomG5"); - } - if (isNew && SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { - NSUpload.sendToXdrip(bgReading); - } - } - } - - if (bundle.containsKey("meterValues")) { - String[] meterEntryTypes = bundle.getStringArray("meterEntryTypes"); - long[] meterTimestamps = bundle.getLongArray("meterTimestamps"); - int[] meterValues = bundle.getIntArray("meterValues"); - long[] meterRecordedTimestamps = bundle.getLongArray("meterRecordedTimestamps"); - int[] meterTransmitterIDs = bundle.getIntArray("meterTransmitterIDs"); - long[] meterTransmitterTimestamps = bundle.getLongArray("meterTransmitterTimestamps"); - - if (L.isEnabled(L.BGSOURCE)) { - log.debug("meterValues", Arrays.toString(meterValues)); - log.debug("meterEntryTypes", Arrays.toString(meterEntryTypes)); - log.debug("meterTimestamps", Arrays.toString(meterTimestamps)); - log.debug("meterTransmitterTimestamps", Arrays.toString(meterTransmitterTimestamps)); - log.debug("meterRecordedTimestamps", Arrays.toString(meterRecordedTimestamps)); - log.debug("meterTransmitterIDs", Arrays.toString(meterTransmitterIDs)); - } - - for (int i = 0; i < meterValues.length; i++) { - try { - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(meterTimestamps[i]) == null) { - JSONObject data = new JSONObject(); - data.put("enteredBy", "AndroidAPS-DexcomG5"); - data.put("created_at", DateUtil.toISOString(meterTimestamps[i])); - data.put("eventType", CareportalEvent.BGCHECK); - data.put("glucoseType", "Finger"); - data.put("glucose", meterValues[i]); - data.put("units", Constants.MGDL); - NSUpload.uploadCareportalEntryToNS(data); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomG6Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomG6Plugin.java deleted file mode 100644 index ad63a9b9ae1..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomG6Plugin.java +++ /dev/null @@ -1,174 +0,0 @@ -package info.nightscout.androidaps.plugins.source; - -import android.content.Intent; -import android.os.Bundle; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.interfaces.BgSourceInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.PluginDescription; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.SP; - -/** - * Created by mike on 30.11.2018. - */ - -public class SourceDexcomG6Plugin extends PluginBase implements BgSourceInterface { - private static Logger log = LoggerFactory.getLogger(L.BGSOURCE); - - private static SourceDexcomG6Plugin plugin = null; - - public static SourceDexcomG6Plugin getPlugin() { - if (plugin == null) - plugin = new SourceDexcomG6Plugin(); - return plugin; - } - - private SourceDexcomG6Plugin() { - super(new PluginDescription() - .mainType(PluginType.BGSOURCE) - .fragmentClass(BGSourceFragment.class.getName()) - .pluginName(R.string.DexcomG6) - .shortName(R.string.dexcomG6_shortname) - .preferencesId(R.xml.pref_bgsource) - .description(R.string.description_source_dexcom_g6) - ); - } - - @Override - public boolean advancedFilteringSupported() { - return true; - } - - @Override - public void handleNewData(Intent intent) { - if (!isEnabled(PluginType.BGSOURCE)) return; - - Bundle bundle = intent.getExtras(); - if (bundle == null) return; - - if (L.isEnabled(L.BGSOURCE)) { - if (bundle.containsKey("transmitterSystemTime")) - log.debug("transmitterSystemTime: " + DateUtil.dateAndTimeFullString(bundle.getLong("transmitterSystemTime"))); - log.debug("transmitterId: " + bundle.getString("transmitterId")); - if (bundle.containsKey("transmitterActivatedOn")) - log.debug("transmitterActivatedOn: " + DateUtil.dateAndTimeFullString(bundle.getLong("transmitterActivatedOn"))); - log.debug("transmitterVersion: " + bundle.getString("transmitterVersion")); - log.debug("transmitterSoftwareNumber: " + bundle.getString("transmitterSoftwareNumber")); - log.debug("transmitterStorageTimeDays: " + bundle.getInt("transmitterStorageTimeDays")); - log.debug("transmitterApiVersion: " + bundle.getInt("transmitterApiVersion")); - log.debug("transmitterMaxRuntimeDays: " + bundle.getInt("transmitterMaxRuntimeDays")); - log.debug("transmitterMaxStorageTimeDays: " + bundle.getInt("transmitterMaxStorageTimeDays")); - log.debug("transmitterCGMProcessorFirmwareVersion: " + bundle.getString("transmitterCGMProcessorFirmwareVersion")); - log.debug("transmitterBleRadioFirmwareVersion: " + bundle.getString("transmitterBleRadioFirmwareVersion")); - log.debug("transmitterHardwareVersion: " + bundle.getInt("transmitterHardwareVersion")); - log.debug("transmitterBleSoftDeviceVersion: " + bundle.getString("transmitterBleSoftDeviceVersion")); - log.debug("transmitterNordicAsicHwID: " + bundle.getInt("transmitterNordicAsicHwID")); - log.debug("transmitterSessionTimeDays: " + bundle.getInt("transmitterSessionTimeDays")); - log.debug("transmitterFeatureFlags: " + bundle.getInt("transmitterFeatureFlags")); - - if (bundle.containsKey("sensorCode")) - log.debug("sensorCode: " + bundle.getString("sensorCode")); - } - - if (bundle.containsKey("sensorInsertionTime")) { - long sensorInsertionTime = bundle.getLong("sensorInsertionTime"); - if (L.isEnabled(L.BGSOURCE)) - log.debug("sensorInsertionTime: " + DateUtil.dateAndTimeFullString(sensorInsertionTime)); - if (SP.getBoolean(R.string.key_dexcom_lognssensorchange, false)) { - try { - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(sensorInsertionTime) == null) { - JSONObject data = new JSONObject(); - data.put("enteredBy", "AndroidAPS-DexcomG6"); - data.put("created_at", DateUtil.toISOString(sensorInsertionTime)); - data.put("eventType", CareportalEvent.SENSORCHANGE); - NSUpload.uploadCareportalEntryToNS(data); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - } - - if (bundle.containsKey("evgTimestamps")) { - long[] timestamps = bundle.getLongArray("evgTimestamps"); - long[] transmitterTimes = bundle.getLongArray("transmitterTimes"); - int[] evgs = bundle.getIntArray("evgs"); - int[] predictiveEVGs = bundle.getIntArray("predictiveEVGs"); - String[] trendArrows = bundle.getStringArray("trendArrows"); - - if (L.isEnabled(L.BGSOURCE)) { - log.debug("timestamps", Arrays.toString(timestamps)); - log.debug("transmitterTimes", Arrays.toString(transmitterTimes)); - log.debug("evgs", Arrays.toString(evgs)); - log.debug("predictiveEVGs", Arrays.toString(predictiveEVGs)); - log.debug("trendArrows", Arrays.toString(trendArrows)); - } - - for (int i = 0; i < transmitterTimes.length; i++) { - BgReading bgReading = new BgReading(); - bgReading.value = evgs[i]; - bgReading.direction = trendArrows[i]; - bgReading.date = timestamps[i]; - bgReading.raw = 0; - boolean isNew = MainApp.getDbHelper().createIfNotExists(bgReading, "DexcomG6"); - if (isNew && SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { - NSUpload.uploadBg(bgReading, "AndroidAPS-DexcomG6"); - } - if (isNew && SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { - NSUpload.sendToXdrip(bgReading); - } - } - } - - if (bundle.containsKey("meterValues")) { - int[] meterValues = bundle.getIntArray("meterValues"); - String[] meterEntryTypes = bundle.getStringArray("meterEntryTypes"); - long[] meterTimestamps = bundle.getLongArray("meterTimestamps"); - long[] meterTransmitterTimestamps = bundle.getLongArray("meterTransmitterTimestamps"); - long[] meterRecordedTimestamps = bundle.getLongArray("meterRecordedTimestamps"); - int[] meterRecordIDs = bundle.getIntArray("meterRecordIDs"); - - if (L.isEnabled(L.BGSOURCE)) { - log.debug("meterValues", Arrays.toString(meterValues)); - log.debug("meterEntryTypes", Arrays.toString(meterEntryTypes)); - log.debug("meterTimestamps", Arrays.toString(meterTimestamps)); - log.debug("meterTransmitterTimestamps", Arrays.toString(meterTransmitterTimestamps)); - log.debug("meterRecordedTimestamps", Arrays.toString(meterRecordedTimestamps)); - log.debug("meterRecordIDs", Arrays.toString(meterRecordIDs)); - } - - for (int i = 0; i < meterValues.length; i++) { - try { - if (MainApp.getDbHelper().getCareportalEventFromTimestamp(meterTimestamps[i]) == null) { - JSONObject data = new JSONObject(); - data.put("enteredBy", "AndroidAPS-DexcomG6"); - data.put("created_at", DateUtil.toISOString(meterTimestamps[i])); - data.put("eventType", CareportalEvent.BGCHECK); - data.put("glucoseType", "Finger"); - data.put("glucose", meterValues[i]); - data.put("units", Constants.MGDL); - NSUpload.uploadCareportalEntryToNS(data); - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - } - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt new file mode 100644 index 00000000000..bf5661ad005 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceDexcomPlugin.kt @@ -0,0 +1,136 @@ +package info.nightscout.androidaps.plugins.source + +import android.content.Intent +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.RequestDexcomPermissionActivity +import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.db.CareportalEvent +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.dialogs.CareDialog +import info.nightscout.androidaps.interfaces.BgSourceInterface +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.interfaces.PluginDescription +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.utils.T +import org.json.JSONObject +import org.slf4j.LoggerFactory + +object SourceDexcomPlugin : PluginBase(PluginDescription() + .mainType(PluginType.BGSOURCE) + .fragmentClass(BGSourceFragment::class.java.name) + .pluginName(R.string.dexcom_app_patched) + .shortName(R.string.dexcom_short) + .preferencesId(R.xml.pref_bgsource) + .description(R.string.description_source_dexcom)), BgSourceInterface { + + private val log = LoggerFactory.getLogger(L.BGSOURCE) + + private val PACKAGE_NAMES = arrayOf("com.dexcom.cgm.region1.mgdl", "com.dexcom.cgm.region1.mmol", + "com.dexcom.cgm.region2.mgdl", "com.dexcom.cgm.region2.mmol", + "com.dexcom.g6.region1.mmol", "com.dexcom.g6.region2.mgdl", + "com.dexcom.g6.region3.mgdl", "com.dexcom.g6.region3.mmol") + + const val PERMISSION = "com.dexcom.cgm.EXTERNAL_PERMISSION" + + override fun advancedFilteringSupported(): Boolean { + return true + } + + override fun onStart() { + super.onStart() + if (ContextCompat.checkSelfPermission(MainApp.instance(), PERMISSION) != PackageManager.PERMISSION_GRANTED) { + val intent = Intent(MainApp.instance(), RequestDexcomPermissionActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + MainApp.instance().startActivity(intent) + } + } + + fun findDexcomPackageName(): String? { + val packageManager = MainApp.instance().packageManager + for (packageInfo in packageManager.getInstalledPackages(0)) { + if (PACKAGE_NAMES.contains(packageInfo.packageName)) return packageInfo.packageName + } + return null + } + + override fun handleNewData(intent: Intent) { + if (!isEnabled(PluginType.BGSOURCE)) return + try { + val sensorType = intent.getStringExtra("sensorType") ?: "" + val glucoseValues = intent.getBundleExtra("glucoseValues") + for (i in 0 until glucoseValues.size()) { + glucoseValues.getBundle(i.toString())?.let { glucoseValue -> + val bgReading = BgReading() + bgReading.value = glucoseValue.getInt("glucoseValue").toDouble() + bgReading.direction = glucoseValue.getString("trendArrow") + bgReading.date = glucoseValue.getLong("timestamp") * 1000 + bgReading.raw = 0.0 + if (MainApp.getDbHelper().createIfNotExists(bgReading, "Dexcom$sensorType")) { + if (SP.getBoolean(R.string.key_dexcomg5_nsupload, false)) { + NSUpload.uploadBg(bgReading, "AndroidAPS-Dexcom$sensorType") + } + if (SP.getBoolean(R.string.key_dexcomg5_xdripupload, false)) { + NSUpload.sendToXdrip(bgReading) + } + } + } + } + val meters = intent.getBundleExtra("meters") + for (i in 0 until meters.size()) { + val meter = meters.getBundle(i.toString()) + meter?.let { + val timestamp = it.getLong("timestamp") * 1000 + val now = DateUtil.now() + if (timestamp > now - T.months(1).msecs() && timestamp < now) + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(timestamp) == null) { + val jsonObject = JSONObject() + jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") + jsonObject.put("created_at", DateUtil.toISOString(timestamp)) + jsonObject.put("eventType", CareportalEvent.BGCHECK) + jsonObject.put("glucoseType", "Finger") + jsonObject.put("glucose", meter.getInt("meterValue")) + jsonObject.put("units", Constants.MGDL) + + val careportalEvent = CareportalEvent() + careportalEvent.date = timestamp + careportalEvent.source = Source.USER + careportalEvent.eventType = CareportalEvent.BGCHECK + careportalEvent.json = jsonObject.toString() + MainApp.getDbHelper().createOrUpdate(careportalEvent) + NSUpload.uploadCareportalEntryToNS(jsonObject) + } + } + } + if (SP.getBoolean(R.string.key_dexcom_lognssensorchange, false) && intent.hasExtra("sensorInsertionTime")) { + intent.extras?.let { + val sensorInsertionTime = it.getLong("sensorInsertionTime") * 1000 + val now = DateUtil.now() + if (sensorInsertionTime > now - T.months(1).msecs() && sensorInsertionTime < now) + if (MainApp.getDbHelper().getCareportalEventFromTimestamp(sensorInsertionTime) == null) { + val jsonObject = JSONObject() + jsonObject.put("enteredBy", "AndroidAPS-Dexcom$sensorType") + jsonObject.put("created_at", DateUtil.toISOString(sensorInsertionTime)) + jsonObject.put("eventType", CareportalEvent.SENSORCHANGE) + val careportalEvent = CareportalEvent() + careportalEvent.date = sensorInsertionTime + careportalEvent.source = Source.USER + careportalEvent.eventType = CareportalEvent.SENSORCHANGE + careportalEvent.json = jsonObject.toString() + MainApp.getDbHelper().createOrUpdate(careportalEvent) + NSUpload.uploadCareportalEntryToNS(jsonObject) + } + } + } + } catch (e: Exception) { + log.error("Error while processing intent from Dexcom App", e) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceEversensePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceEversensePlugin.java index 6f39e3d7627..6d0067414ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceEversensePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceEversensePlugin.java @@ -15,6 +15,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; +import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.interfaces.BgSourceInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginDescription; @@ -143,6 +144,12 @@ public void handleNewData(Intent intent) { data.put("glucoseType", "Finger"); data.put("glucose", calibrationGlucoseLevels[i]); data.put("units", Constants.MGDL); + CareportalEvent careportalEvent = new CareportalEvent(); + careportalEvent.date = calibrationTimestamps[i]; + careportalEvent.source = Source.USER; + careportalEvent.eventType = CareportalEvent.BGCHECK; + careportalEvent.json = data.toString(); + MainApp.getDbHelper().createOrUpdate(careportalEvent); NSUpload.uploadCareportalEntryToNS(data); } } catch (JSONException e) { 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 index 328f054aad4..dd0a2c5e88a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceNSClientPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/source/SourceNSClientPlugin.java @@ -16,7 +16,6 @@ import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSgv; import info.nightscout.androidaps.utils.JsonHelper; import info.nightscout.androidaps.utils.SP; @@ -85,8 +84,7 @@ public void handleNewData(Intent intent) { } // Objectives 0 - ObjectivesPlugin.getPlugin().bgIsAvailableInNS = true; - ObjectivesPlugin.getPlugin().saveProgress(); + SP.putBoolean(R.string.key_ObjectivesbgIsAvailableInNS, true); } private void storeSgv(JSONObject sgvJson) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java index ac9be50a231..f22523160d1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/CarbsGenerator.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.content.Intent; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -9,7 +9,7 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.T; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java index dbf1a89c376..19b41a0944f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/Treatment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.plugins.treatments; import android.graphics.Color; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; @@ -16,9 +16,12 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Iob; +import info.nightscout.androidaps.db.DbObjectBase; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; @@ -27,7 +30,7 @@ import info.nightscout.androidaps.utils.JsonHelper; @DatabaseTable(tableName = Treatment.TABLE_TREATMENTS) -public class Treatment implements DataPointWithLabelInterface { +public class Treatment implements DataPointWithLabelInterface, DbObjectBase { public static final String TABLE_TREATMENTS = "Treatments"; @DatabaseField(id = true) @@ -101,15 +104,16 @@ public String toString() { ", insulin= " + insulin + ", carbs= " + carbs + ", mealBolus= " + mealBolus + + ", source= " + source + "}"; } public boolean isDataChanging(Treatment other) { if (date != other.date) return true; - if (insulin != other.insulin) + if (!isSame(insulin, other.insulin)) return true; - if (carbs != other.carbs) + if (!isSame(carbs, other.carbs)) return true; return false; } @@ -117,9 +121,9 @@ public boolean isDataChanging(Treatment other) { public boolean isEqual(Treatment other) { if (date != other.date) return false; - if (insulin != other.insulin) + if (!isSame(insulin, other.insulin)) return false; - if (carbs != other.carbs) + if (!isSame(carbs, other.carbs)) return false; if (mealBolus != other.mealBolus) return false; @@ -132,16 +136,47 @@ public boolean isEqual(Treatment other) { return true; } + public boolean isEqualWithoutPumpId(Treatment other) { + if (date != other.date) + return false; + if (!isSame(insulin, other.insulin)) + return false; + if (!isSame(carbs, other.carbs)) + return false; + if (mealBolus != other.mealBolus) + return false; + if (isSMB != other.isSMB) + return false; + if (!Objects.equals(_id, other._id)) + return false; + return true; + } + + public boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + @Nullable public JSONObject getBoluscalc() { try { if (boluscalc != null) - return new JSONObject(boluscalc); + return new JSONObject(boluscalc); } catch (JSONException ignored) { } return null; } + public double getIc() { + JSONObject bw = getBoluscalc(); + if (bw == null || !bw.has("ic")) { + Profile profile = ProfileFunctions.getInstance().getProfile(date); + return profile.getIc(date); + } + return JsonHelper.safeGetDouble(bw, "ic"); + } + /* * mealBolus, _id and isSMB cannot be known coming from pump. Only compare rest * TODO: remove debug toasts @@ -150,12 +185,13 @@ public boolean equalsRePumpHistory(Treatment other) { if (date != other.date) { return false; } - if (insulin != other.insulin) { + + if (!isSame(insulin, other.insulin)) return false; - } - if (carbs != other.carbs) { + + if (!isSame(carbs, other.carbs)) return false; - } + return true; } @@ -188,7 +224,7 @@ public double getX() { @Override public double getY() { - return isSMB ? OverviewPlugin.getPlugin().determineLowLine() : yValue; + return isSMB ? OverviewPlugin.INSTANCE.determineLowLine() : yValue; } @Override @@ -242,4 +278,14 @@ public Iob iobCalc(long time, double dia) { InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); return insulinInterface.iobCalcForTreatment(this, time, dia); } -} + + @Override + public long getDate() { + return this.date; + } + + @Override + public long getPumpId() { + return this.pumpId; + } +} \ No newline at end of file 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 index 7bb47749110..1331c8eedf7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentService.java @@ -2,7 +2,8 @@ import android.content.Intent; import android.os.IBinder; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.android.apptools.OrmLiteBaseService; @@ -13,8 +14,8 @@ 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.apache.commons.lang3.StringUtils; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -28,7 +29,6 @@ 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; @@ -37,8 +37,14 @@ import info.nightscout.androidaps.events.EventReloadTreatmentData; import info.nightscout.androidaps.events.EventTreatmentChange; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.JsonHelper; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** @@ -47,6 +53,7 @@ public class TreatmentService extends OrmLiteBaseService { private static Logger log = LoggerFactory.getLogger(L.DATATREATMENTS); + private CompositeDisposable disposable = new CompositeDisposable(); private static final ScheduledExecutorService treatmentEventWorker = Executors.newSingleThreadScheduledExecutor(); private static ScheduledFuture scheduledTreatmentEventPost = null; @@ -54,7 +61,20 @@ public class TreatmentService extends OrmLiteBaseService { public TreatmentService() { onCreate(); dbInitialize(); - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(EventNsTreatment.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + int mode = event.getMode(); + JSONObject payload = event.getPayload(); + + if (mode == EventNsTreatment.Companion.getADD() || mode == EventNsTreatment.Companion.getUPDATE()) { + this.createTreatmentFromJsonIfNotExists(payload); + } else { // EventNsTreatment.REMOVE + this.deleteNS(payload); + } + }, FabricPrivacy::logException) + ); } /** @@ -85,19 +105,6 @@ public Dao getDao() { 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(); @@ -126,7 +133,7 @@ public void onUpgrade(ConnectionSource connectionSource, int oldVersion, int new try { getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` ADD COLUMN boluscalc STRING;"); } catch (SQLException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } else { if (L.isEnabled(L.DATATREATMENTS)) @@ -140,7 +147,7 @@ public void onDowngrade(ConnectionSource connectionSource, int oldVersion, int n try { getDao().executeRaw("ALTER TABLE `" + Treatment.TABLE_TREATMENTS + "` DROP COLUMN boluscalc STRING;"); } catch (SQLException e) { - e.printStackTrace(); + log.error("Unhandled exception", e); } } } @@ -178,11 +185,11 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.DATATREATMENTS)) log.debug("Firing EventReloadTreatmentData"); - MainApp.bus().post(event); + RxBus.INSTANCE.send(event); if (DatabaseHelper.earliestDataChange != null) { if (L.isEnabled(L.DATATREATMENTS)) log.debug("Firing EventNewHistoryData"); - MainApp.bus().post(new EventNewHistoryData(DatabaseHelper.earliestDataChange)); + RxBus.INSTANCE.send(new EventNewHistoryData(DatabaseHelper.earliestDataChange)); } DatabaseHelper.earliestDataChange = null; callback.setPost(null); @@ -190,8 +197,9 @@ public void run() { } // prepare task for execution in 1 sec // cancel waiting task to prevent sending multiple posts - if (callback.getPost() != null) - callback.getPost().cancel(false); + ScheduledFuture scheduledFuture = callback.getPost(); + if (scheduledFuture != null) + scheduledFuture.cancel(false); Runnable task = new PostRunnable(); final int sec = 1; callback.setPost(eventWorker.schedule(task, sec, TimeUnit.SECONDS)); @@ -241,15 +249,23 @@ public List getTreatmentData() { public void createTreatmentFromJsonIfNotExists(JSONObject json) { try { Treatment treatment = Treatment.createFromJson(json); - if (treatment != null) - createOrUpdate(treatment); - else + if (treatment != null) { + + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: createTreatmentFromJsonIfNotExists:: medtronicPump={}", MedtronicUtil.isMedtronicPump()); + + if (!MedtronicUtil.isMedtronicPump()) + createOrUpdate(treatment); + else + createOrUpdateMedtronic(treatment, true); + } else log.error("Date is null: " + treatment.toString()); } catch (JSONException e) { log.error("Unhandled exception", e); } } + // return true if new record is created public UpdateReturn createOrUpdate(Treatment treatment) { try { @@ -379,6 +395,196 @@ public UpdateReturn createOrUpdate(Treatment treatment) { return new UpdateReturn(false, false); } + + public UpdateReturn createOrUpdateMedtronic(Treatment treatment, boolean fromNightScout) { + + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: createOrUpdateMedtronic:: originalTreatment={}, fromNightScout={}", treatment, fromNightScout); + + try { + treatment.date = DatabaseHelper.roundDateToSec(treatment.date); + + Treatment existingTreatment = getRecord(treatment.pumpId, treatment.date); + + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: createOrUpdateMedtronic:: existingTreatment={}", treatment); + + if (existingTreatment == null) { + getDao().create(treatment); + if (L.isEnabled(L.DATATREATMENTS)) + log.debug("New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); + DatabaseHelper.updateEarliestDataChange(treatment.date); + scheduleTreatmentChange(treatment); + return new UpdateReturn(true, true); + } else { + + if (existingTreatment.date == treatment.date) { + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: createOrUpdateMedtronic::(existingTreatment.date==treatment.date)"); + + // we will do update only, if entry changed + if (!optionalTreatmentCopy(existingTreatment, treatment, fromNightScout)) { + return new UpdateReturn(true, false); + } + getDao().update(existingTreatment); + DatabaseHelper.updateEarliestDataChange(existingTreatment.date); + scheduleTreatmentChange(treatment); + return new UpdateReturn(true, false); + } else { + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: createOrUpdateMedtronic::(existingTreatment.date != treatment.date)"); + + // date is different, we need to remove entry + getDao().delete(existingTreatment); + optionalTreatmentCopy(existingTreatment, treatment, fromNightScout); + getDao().create(existingTreatment); + DatabaseHelper.updateEarliestDataChange(existingTreatment.date); + scheduleTreatmentChange(treatment); + return new UpdateReturn(true, false); //updating a pump treatment with another one from the pump is not counted as clash + } + } + + } catch (SQLException e) { + log.error("Unhandled SQL exception: {}", e.getMessage(), e); + } + return new UpdateReturn(false, false); + } + + + private boolean optionalTreatmentCopy(Treatment oldTreatment, Treatment newTreatment, boolean fromNightScout) { + + log.debug("optionalTreatmentCopy [old={}, new={}]", oldTreatment.toString(), newTreatment.toString()); + + boolean changed = false; + + if (oldTreatment.date != newTreatment.date) { + oldTreatment.date = newTreatment.date; + changed = true; + } + + if (oldTreatment.isSMB != newTreatment.isSMB) { + if (!oldTreatment.isSMB) { + oldTreatment.isSMB = newTreatment.isSMB; + changed = true; + } + } + + if (!isSame(oldTreatment.carbs, newTreatment.carbs)) { + if (isSame(oldTreatment.carbs, 0.0d)) { + oldTreatment.carbs = newTreatment.carbs; + changed = true; + } + } + + if (oldTreatment.mealBolus != (oldTreatment.carbs > 0)) { + oldTreatment.mealBolus = (oldTreatment.carbs > 0); + changed = true; + } + + if (!isSame(oldTreatment.insulin, newTreatment.insulin)) { + if (!fromNightScout) { + oldTreatment.insulin = newTreatment.insulin; + changed = true; + } + } + + if (!StringUtils.equals(oldTreatment._id, newTreatment._id)) { + if (StringUtils.isBlank(oldTreatment._id)) { + oldTreatment._id = newTreatment._id; + changed = true; + } + } + + int source = Source.NONE; + + if (oldTreatment.pumpId == 0) { + if (newTreatment.pumpId > 0) { + oldTreatment.pumpId = newTreatment.pumpId; + source = Source.PUMP; + changed = true; + } + } + + if (source == Source.NONE) { + + if (oldTreatment.source == newTreatment.source) { + source = oldTreatment.source; + } else { + source = (oldTreatment.source == Source.NIGHTSCOUT || newTreatment.source == Source.NIGHTSCOUT) ? Source.NIGHTSCOUT : Source.USER; + } + } + + if (oldTreatment.source != source) { + oldTreatment.source = source; + changed = true; + } + + log.debug("optionalTreatmentCopy [changed={}, newAfterChange={}]", changed, oldTreatment.toString()); + return changed; + } + + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.00001); + } + + @Deprecated + private void treatmentCopy(Treatment oldTreatment, Treatment newTreatment, boolean fromNightScout) { + + log.debug("treatmentCopy [old={}, new={}]", oldTreatment.toString(), newTreatment.toString()); + + + if (fromNightScout) { + long pumpId_old = oldTreatment.pumpId; + boolean isSMB = (oldTreatment.isSMB || newTreatment.isSMB); + + oldTreatment.copyFrom(newTreatment); + + if (pumpId_old != 0) { + oldTreatment.pumpId = pumpId_old; + } + + if (oldTreatment.pumpId != 0 && oldTreatment.source != Source.PUMP) { + oldTreatment.source = Source.PUMP; + } + + oldTreatment.isSMB = isSMB; + + } else { + oldTreatment.copyFrom(newTreatment); + } + + log.debug("treatmentCopy [newAfterChange={}]", oldTreatment.toString()); + + } + + + public Treatment getRecord(long pumpId, long date) { + + Treatment record = null; + + if (pumpId > 0) { + + record = getPumpRecordById(pumpId); + + if (record != null) { + return record; + } + } + + try { + record = getDao().queryForId(date); + } catch (SQLException ex) { + log.error("Error getting entry by id ({}", date); + } + + return record; + + } + + /** * Returns the record for the given id, null if none, throws RuntimeException * if multiple records with the same pump id exist. @@ -499,6 +705,23 @@ public List getTreatmentDataFromTime(long mills, boolean ascending) { return new ArrayList<>(); } + public List getTreatmentDataFromTime(long from, long to, boolean ascending) { + try { + Dao daoTreatments = getDao(); + List treatments; + QueryBuilder queryBuilder = daoTreatments.queryBuilder(); + queryBuilder.orderBy("date", ascending); + Where where = queryBuilder.where(); + where.between("date", from, to); + 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) { @@ -513,6 +736,14 @@ public UpdateReturn(boolean success, boolean newRecord) { boolean newRecord; boolean success; + + @Override + public String toString() { + return "UpdateReturn [" + + "newRecord=" + newRecord + + ", success=" + success + + ']'; + } } } 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 d30565b4a7b..731042c2375 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 @@ -1,19 +1,18 @@ package info.nightscout.androidaps.plugins.treatments; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventExtendedBolusChange; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsBolusFragment; import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsCareportalFragment; @@ -22,8 +21,12 @@ import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTempTargetFragment; import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTemporaryBasalsFragment; import info.nightscout.androidaps.utils.FabricPrivacy; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; + +public class TreatmentsFragment extends Fragment implements View.OnClickListener { + private CompositeDisposable disposable = new CompositeDisposable(); -public class TreatmentsFragment extends SubscriberFragment implements View.OnClickListener { TextView treatmentsTab; TextView extendedBolusesTab; TextView tempBasalsTab; @@ -34,32 +37,42 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - try { - View view = inflater.inflate(R.layout.treatments_fragment, container, false); - - treatmentsTab = (TextView) view.findViewById(R.id.treatments_treatments); - extendedBolusesTab = (TextView) view.findViewById(R.id.treatments_extendedboluses); - tempBasalsTab = (TextView) view.findViewById(R.id.treatments_tempbasals); - tempTargetTab = (TextView) view.findViewById(R.id.treatments_temptargets); - profileSwitchTab = (TextView) view.findViewById(R.id.treatments_profileswitches); - careportalTab = (TextView) view.findViewById(R.id.treatments_careportal); - treatmentsTab.setOnClickListener(this); - extendedBolusesTab.setOnClickListener(this); - tempBasalsTab.setOnClickListener(this); - tempTargetTab.setOnClickListener(this); - profileSwitchTab.setOnClickListener(this); - careportalTab.setOnClickListener(this); - - setFragment(new TreatmentsBolusFragment()); - setBackgroundColorOnSelected(treatmentsTab); - - return view; - } catch (Exception e) { - FabricPrivacy.logException(e); - } + View view = inflater.inflate(R.layout.treatments_fragment, container, false); + + treatmentsTab = (TextView) view.findViewById(R.id.treatments_treatments); + extendedBolusesTab = (TextView) view.findViewById(R.id.treatments_extendedboluses); + tempBasalsTab = (TextView) view.findViewById(R.id.treatments_tempbasals); + tempTargetTab = (TextView) view.findViewById(R.id.treatments_temptargets); + profileSwitchTab = (TextView) view.findViewById(R.id.treatments_profileswitches); + careportalTab = (TextView) view.findViewById(R.id.treatments_careportal); + treatmentsTab.setOnClickListener(this); + extendedBolusesTab.setOnClickListener(this); + tempBasalsTab.setOnClickListener(this); + tempTargetTab.setOnClickListener(this); + profileSwitchTab.setOnClickListener(this); + careportalTab.setOnClickListener(this); + + setFragment(new TreatmentsBolusFragment()); + setBackgroundColorOnSelected(treatmentsTab); + + return view; + } - return null; + @Override + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventExtendedBolusChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); + } + @Override + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } @Override @@ -111,13 +124,7 @@ private void setBackgroundColorOnSelected(TextView selected) { selected.setBackgroundColor(MainApp.gc(R.color.tabBgColorSelected)); } - @Subscribe - public void onStatusEvent(final EventExtendedBolusChange ev) { - updateGUI(); - } - - @Override - protected void updateGUI() { + private void updateGui() { if (ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().isExtendedBolusCapable || TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory().size() > 0) { extendedBolusesTab.setVisibility(View.VISIBLE); 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 2c94d6713d2..8ec0b48ffac 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 @@ -2,10 +2,10 @@ import android.content.Intent; import android.os.Bundle; -import android.support.annotation.Nullable; + +import androidx.annotation.Nullable; import com.google.firebase.analytics.FirebaseAnalytics; -import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,20 +41,26 @@ import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.dialogs.ErrorHelperActivity; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; +import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; /** * Created by mike on 05.08.2016. @@ -62,6 +68,8 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface { private Logger log = LoggerFactory.getLogger(L.DATATREATMENTS); + private CompositeDisposable disposable = new CompositeDisposable(); + private static TreatmentsPlugin treatmentsPlugin; public static TreatmentsPlugin getPlugin() { @@ -95,79 +103,111 @@ public TreatmentsPlugin() { @Override protected void onStart() { - MainApp.bus().register(this); - initializeTempBasalData(); - initializeTreatmentData(); - initializeExtendedBolusData(); - initializeTempTargetData(); - initializeProfileSwitchData(); + initializeData(range()); super.onStart(); + disposable.add(RxBus.INSTANCE + .toObservable(EventReloadTreatmentData.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.DATATREATMENTS)) + log.debug("EventReloadTreatmentData"); + initializeTreatmentData(range()); + initializeExtendedBolusData(range()); + updateTotalIOBTreatments(); + RxBus.INSTANCE.send(event.getNext()); + }, + FabricPrivacy::logException + )); + disposable.add(RxBus.INSTANCE + .toObservable(EventReloadProfileSwitchData.class) + .observeOn(Schedulers.io()) + .subscribe(event -> initializeProfileSwitchData(range()), + FabricPrivacy::logException + )); + disposable.add(RxBus.INSTANCE + .toObservable(EventTempTargetChange.class) + .observeOn(Schedulers.io()) + .subscribe(event -> initializeTempTargetData(range()), + FabricPrivacy::logException + )); + disposable.add(RxBus.INSTANCE + .toObservable(EventReloadTempBasalData.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.DATATREATMENTS)) + log.debug("EventReloadTempBasalData"); + initializeTempBasalData(range()); + updateTotalIOBTempBasals(); + }, + FabricPrivacy::logException + )); } @Override protected void onStop() { - MainApp.bus().register(this); + disposable.clear(); + super.onStop(); } public TreatmentService getService() { return this.service; } - private void initializeTreatmentData() { - if (L.isEnabled(L.DATATREATMENTS)) - log.debug("initializeTreatmentData"); + private long range() { double dia = Constants.defaultDIA; if (ConfigBuilderPlugin.getPlugin() != null && ProfileFunctions.getInstance().getProfile() != null) dia = ProfileFunctions.getInstance().getProfile().getDia(); - long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); + return (long) (60 * 60 * 1000L * (24 + dia)); + } + + public void initializeData(long range) { + initializeTempBasalData(range); + initializeTreatmentData(range); + initializeExtendedBolusData(range); + initializeTempTargetData(range); + initializeProfileSwitchData(range); + } + + private void initializeTreatmentData(long range) { + if (L.isEnabled(L.DATATREATMENTS)) + log.debug("initializeTreatmentData"); synchronized (treatments) { treatments.clear(); - treatments.addAll(getService().getTreatmentDataFromTime(fromMills, false)); + treatments.addAll(getService().getTreatmentDataFromTime(DateUtil.now() - range, false)); } } - private void initializeTempBasalData() { + private void initializeTempBasalData(long range) { if (L.isEnabled(L.DATATREATMENTS)) log.debug("initializeTempBasalData"); - double dia = Constants.defaultDIA; - if (ConfigBuilderPlugin.getPlugin() != null && ProfileFunctions.getInstance().getProfile() != null) - dia = ProfileFunctions.getInstance().getProfile().getDia(); - long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); - synchronized (tempBasals) { - tempBasals.reset().add(MainApp.getDbHelper().getTemporaryBasalsDataFromTime(fromMills, false)); + tempBasals.reset().add(MainApp.getDbHelper().getTemporaryBasalsDataFromTime(DateUtil.now() - range, false)); } } - private void initializeExtendedBolusData() { + private void initializeExtendedBolusData(long range) { if (L.isEnabled(L.DATATREATMENTS)) log.debug("initializeExtendedBolusData"); - double dia = Constants.defaultDIA; - if (ConfigBuilderPlugin.getPlugin() != null && ProfileFunctions.getInstance().getProfile() != null) - dia = ProfileFunctions.getInstance().getProfile().getDia(); - long fromMills = (long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)); - synchronized (extendedBoluses) { - extendedBoluses.reset().add(MainApp.getDbHelper().getExtendedBolusDataFromTime(fromMills, false)); + extendedBoluses.reset().add(MainApp.getDbHelper().getExtendedBolusDataFromTime(DateUtil.now() - range, false)); } } - private void initializeTempTargetData() { + private void initializeTempTargetData(long range) { if (L.isEnabled(L.DATATREATMENTS)) log.debug("initializeTempTargetData"); synchronized (tempTargets) { - long fromMills = System.currentTimeMillis() - 60 * 60 * 1000L * 24; - tempTargets.reset().add(MainApp.getDbHelper().getTemptargetsDataFromTime(fromMills, false)); + tempTargets.reset().add(MainApp.getDbHelper().getTemptargetsDataFromTime(DateUtil.now() - range, false)); } } - private void initializeProfileSwitchData() { + private void initializeProfileSwitchData(long range) { if (L.isEnabled(L.DATATREATMENTS)) log.debug("initializeProfileSwitchData"); synchronized (profiles) { - profiles.reset().add(MainApp.getDbHelper().getProfileSwitchData(false)); + profiles.reset().add(MainApp.getDbHelper().getProfileSwitchData(DateUtil.now() - range, false)); } } @@ -217,7 +257,7 @@ public IobTotal getCalculationToTimeTreatments(long time) { if (!pumpInterface.isFakingTempsByExtendedBoluses()) synchronized (extendedBoluses) { - for (Integer pos = 0; pos < extendedBoluses.size(); pos++) { + for (int pos = 0; pos < extendedBoluses.size(); pos++) { ExtendedBolus e = extendedBoluses.get(pos); if (e.date > time) continue; IobTotal calc = e.iobCalc(time); @@ -290,12 +330,41 @@ public List getTreatmentsFromHistory() { } } + + /** + * Returns all Treatments after specified timestamp. Also returns invalid entries (required to + * map "Fill Canulla" entries to history (and not to add double bolus for it) + * + * @param fromTimestamp + * @return + */ @Override - public List getTreatments5MinBackFromHistory(long time) { + public List getTreatmentsFromHistoryAfterTimestamp(long fromTimestamp) { List in5minback = new ArrayList<>(); + + long time = System.currentTimeMillis(); synchronized (treatments) { - for (Integer pos = 0; pos < treatments.size(); pos++) { - Treatment t = treatments.get(pos); + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: AllTreatmentsInDb: {}", MedtronicUtil.getGsonInstanceCore().toJson(treatments)); + + for (Treatment t : treatments) { + if (t.date <= time && t.date >= fromTimestamp) + in5minback.add(t); + } + + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: FilteredTreatments: AfterTime={}, Items={}", fromTimestamp, MedtronicUtil.getGsonInstanceCore().toJson(in5minback)); + + return in5minback; + } + } + + + @Override + public List getCarbTreatments5MinBackFromHistory(long time) { + List in5minback = new ArrayList<>(); + synchronized (treatments) { + for (Treatment t : treatments) { if (!t.isValid) continue; if (t.date <= time && t.date > time - 5 * 60 * 1000 && t.carbs > 0) @@ -322,6 +391,22 @@ public long getLastBolusTime() { return last; } + public long getLastBolusTime(boolean isSMB) { + long now = System.currentTimeMillis(); + long last = 0; + synchronized (treatments) { + for (Treatment t : treatments) { + if (!t.isValid) + continue; + if (t.date > last && t.insulin > 0 && t.isValid && t.date <= now && isSMB == t.isSMB) + last = t.date; + } + } + if (L.isEnabled(L.DATATREATMENTS)) + log.debug("Last manual bolus time: " + new Date(last).toLocaleString()); + return last; + } + @Override public boolean isInHistoryRealTempBasalInProgress() { return getRealTempBasalFromHistory(System.currentTimeMillis()) != null; @@ -344,36 +429,17 @@ public boolean isInHistoryExtendedBoluslInProgress() { return getExtendedBolusFromHistory(System.currentTimeMillis()) != null; //TODO: crosscheck here } - @Subscribe - public void onStatusEvent(final EventReloadTreatmentData ev) { - if (L.isEnabled(L.DATATREATMENTS)) - log.debug("EventReloadTreatmentData"); - initializeTreatmentData(); - initializeExtendedBolusData(); - updateTotalIOBTreatments(); - MainApp.bus().post(ev.next); - } - - @Subscribe - @SuppressWarnings("unused") - public void onStatusEvent(final EventReloadTempBasalData ev) { - if (L.isEnabled(L.DATATREATMENTS)) - log.debug("EventReloadTempBasalData"); - initializeTempBasalData(); - updateTotalIOBTempBasals(); - } - @Override public IobTotal getLastCalculationTempBasals() { return lastTempBasalsCalculation; } @Override - public IobTotal getCalculationToTimeTempBasals(long time, Profile profile) { - return getCalculationToTimeTempBasals(time, profile, false, 0); + public IobTotal getCalculationToTimeTempBasals(long time) { + return getCalculationToTimeTempBasals(time, false, 0); } - public IobTotal getCalculationToTimeTempBasals(long time, Profile profile, boolean truncate, long truncateTime) { + public IobTotal getCalculationToTimeTempBasals(long time, boolean truncate, long truncateTime) { IobTotal total = new IobTotal(time); InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); @@ -385,6 +451,8 @@ public IobTotal getCalculationToTimeTempBasals(long time, Profile profile, boole TemporaryBasal t = tempBasals.get(pos); if (t.date > time) continue; IobTotal calc; + Profile profile = ProfileFunctions.getInstance().getProfile(t.date); + if (profile == null) continue; if (truncate && t.end() > truncateTime) { TemporaryBasal dummyTemp = new TemporaryBasal(); dummyTemp.copyFrom(t); @@ -404,6 +472,8 @@ public IobTotal getCalculationToTimeTempBasals(long time, Profile profile, boole ExtendedBolus e = extendedBoluses.get(pos); if (e.date > time) continue; IobTotal calc; + Profile profile = ProfileFunctions.getInstance().getProfile(e.date); + if (profile == null) continue; if (truncate && e.end() > truncateTime) { ExtendedBolus dummyExt = new ExtendedBolus(); dummyExt.copyFrom(e); @@ -425,11 +495,65 @@ public IobTotal getCalculationToTimeTempBasals(long time, Profile profile, boole return total; } + public IobTotal getCalculationToTimeTempBasals(long time, long truncateTime, AutosensResult lastAutosensResult, boolean exercise_mode, int half_basal_exercise_target, boolean isTempTarget) { + IobTotal total = new IobTotal(time); + + InsulinInterface insulinInterface = ConfigBuilderPlugin.getPlugin().getActiveInsulin(); + if (insulinInterface == null) + return total; + + synchronized (tempBasals) { + for (int pos = 0; pos < tempBasals.size(); pos++) { + TemporaryBasal t = tempBasals.get(pos); + if (t.date > time) continue; + IobTotal calc; + Profile profile = ProfileFunctions.getInstance().getProfile(t.date); + if (profile == null) continue; + if (t.end() > truncateTime) { + TemporaryBasal dummyTemp = new TemporaryBasal(); + dummyTemp.copyFrom(t); + dummyTemp.cutEndTo(truncateTime); + calc = dummyTemp.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); + } else { + calc = t.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); + } + //log.debug("BasalIOB " + new Date(time) + " >>> " + calc.basaliob); + total.plus(calc); + } + } + if (ConfigBuilderPlugin.getPlugin().getActivePump().isFakingTempsByExtendedBoluses()) { + IobTotal totalExt = new IobTotal(time); + synchronized (extendedBoluses) { + for (int pos = 0; pos < extendedBoluses.size(); pos++) { + ExtendedBolus e = extendedBoluses.get(pos); + if (e.date > time) continue; + IobTotal calc; + Profile profile = ProfileFunctions.getInstance().getProfile(e.date); + if (profile == null) continue; + if (e.end() > truncateTime) { + ExtendedBolus dummyExt = new ExtendedBolus(); + dummyExt.copyFrom(e); + dummyExt.cutEndTo(truncateTime); + calc = dummyExt.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); + } else { + calc = e.iobCalc(time, profile, lastAutosensResult, exercise_mode, half_basal_exercise_target, isTempTarget); + } + totalExt.plus(calc); + } + } + // Convert to basal iob + totalExt.basaliob = totalExt.iob; + totalExt.iob = 0d; + totalExt.netbasalinsulin = totalExt.extendedBolusInsulin; + totalExt.hightempinsulin = totalExt.extendedBolusInsulin; + total.plus(totalExt); + } + return total; + } + @Override public void updateTotalIOBTempBasals() { - Profile profile = ProfileFunctions.getInstance().getProfile(); - if (profile != null) - lastTempBasalsCalculation = getCalculationToTimeTempBasals(DateUtil.now(), profile); + lastTempBasalsCalculation = getCalculationToTimeTempBasals(DateUtil.now()); } @Nullable @@ -477,7 +601,7 @@ public Intervals getExtendedBolusesFromHistory() { } @Override - public Intervals getTemporaryBasalsFromHistory() { + public NonOverlappingIntervals getTemporaryBasalsFromHistory() { synchronized (tempBasals) { return new NonOverlappingIntervals<>(tempBasals); } @@ -501,6 +625,11 @@ else if (tempBasal.isAbsolute) // return true if new record is created @Override public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolean allowUpdate) { + boolean medtronicPump = MedtronicUtil.isMedtronicPump(); + + if (MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: addToHistoryTreatment::isMedtronicPump={}", medtronicPump); + Treatment treatment = new Treatment(); treatment.date = detailedBolusInfo.date; treatment.source = detailedBolusInfo.source; @@ -513,17 +642,34 @@ public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolea treatment.source = detailedBolusInfo.source; treatment.mealBolus = treatment.carbs > 0; treatment.boluscalc = detailedBolusInfo.boluscalc != null ? detailedBolusInfo.boluscalc.toString() : null; - TreatmentService.UpdateReturn creatOrUpdateResult = getService().createOrUpdate(treatment); + TreatmentService.UpdateReturn creatOrUpdateResult; + + if (medtronicPump && MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: addToHistoryTreatment::treatment={}", treatment); + + if (!medtronicPump) + creatOrUpdateResult = getService().createOrUpdate(treatment); + else + creatOrUpdateResult = getService().createOrUpdateMedtronic(treatment, false); + boolean newRecordCreated = creatOrUpdateResult.newRecord; //log.debug("Adding new Treatment record" + treatment.toString()); if (detailedBolusInfo.carbTime != 0) { + Treatment carbsTreatment = new Treatment(); carbsTreatment.source = detailedBolusInfo.source; carbsTreatment.pumpId = detailedBolusInfo.pumpId; // but this should never happen 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; - getService().createOrUpdate(carbsTreatment); + + if (medtronicPump && MedtronicHistoryData.doubleBolusDebug) + log.debug("DoubleBolusDebug: carbTime!=0, creating second treatment. CarbsTreatment={}", carbsTreatment); + + if (!medtronicPump) + getService().createOrUpdate(carbsTreatment); + else + getService().createOrUpdateMedtronic(carbsTreatment, false); //log.debug("Adding new Treatment record" + carbsTreatment); } if (newRecordCreated && detailedBolusInfo.isValid) @@ -569,13 +715,6 @@ public long oldestDataAvailable() { return oldestTime; } - // TempTargets - @Subscribe - @SuppressWarnings("unused") - public void onStatusEvent(final EventTempTargetChange ev) { - initializeTempTargetData(); - } - @Nullable @Override public TempTarget getTempTargetFromHistory() { @@ -606,14 +745,8 @@ public void addToHistoryTempTarget(TempTarget tempTarget) { NSUpload.uploadTempTarget(tempTarget); } - // Profile Switch - @Subscribe - @SuppressWarnings("unused") - public void onStatusEvent(final EventReloadProfileSwitchData ev) { - initializeProfileSwitchData(); - } - @Override + @Nullable public ProfileSwitch getProfileSwitchFromHistory(long time) { synchronized (profiles) { return (ProfileSwitch) profiles.getValueToTime(time); @@ -630,7 +763,7 @@ public ProfileIntervals getProfileSwitchesFromHistory() { @Override public void addToHistoryProfileSwitch(ProfileSwitch profileSwitch) { //log.debug("Adding new TemporaryBasal record" + profileSwitch.log()); - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_SWITCH_MISSING)); + RxBus.INSTANCE.send(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/dialogs/WizardInfoDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java deleted file mode 100644 index c38672311b9..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.java +++ /dev/null @@ -1,86 +0,0 @@ -package info.nightscout.androidaps.plugins.treatments.dialogs; - -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.TextView; - -import org.json.JSONObject; - -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.JsonHelper; - -public class WizardInfoDialog extends DialogFragment implements OnClickListener { - JSONObject json; - - public WizardInfoDialog() { - super(); - } - - public void setData(JSONObject json) { - this.json = json; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.treatments_wizardinfo_dialog, container, false); - - getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); - - view.findViewById(R.id.ok).setOnClickListener(this); - - // BG - ((TextView) view.findViewById(R.id.treatments_wizard_bg)).setText(DecimalFormatter.to1Decimal(JsonHelper.safeGetDouble(json, "bg")) + " ISF: " + DecimalFormatter.to1Decimal(JsonHelper.safeGetDouble(json, "isf"))); - ((TextView) view.findViewById(R.id.treatments_wizard_bginsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulinbg")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_bgcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "insulinbgused")); - ((CheckBox) view.findViewById(R.id.treatments_wizard_ttcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "ttused")); - // Trend - ((TextView) view.findViewById(R.id.treatments_wizard_bgtrend)).setText(JsonHelper.safeGetString(json, "trend")); - ((TextView) view.findViewById(R.id.treatments_wizard_bgtrendinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulintrend")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_bgtrendcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "trendused")); - // COB - ((TextView) view.findViewById(R.id.treatments_wizard_cob)).setText(DecimalFormatter.to0Decimal(JsonHelper.safeGetDouble(json, "cob")) + "g IC: " + DecimalFormatter.to1Decimal(JsonHelper.safeGetDouble(json, "ic"))); - ((TextView) view.findViewById(R.id.treatments_wizard_cobinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulincob")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_cobcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "cobused")); - // Bolus IOB - ((TextView) view.findViewById(R.id.treatments_wizard_bolusiobinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "bolusiob")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_bolusiobcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "bolusiobused")); - // Basal IOB - ((TextView) view.findViewById(R.id.treatments_wizard_basaliobinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "basaliob")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_basaliobcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "basaliobused")); - // Superbolus - ((TextView) view.findViewById(R.id.treatments_wizard_sbinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulinsuperbolus")) + "U"); - ((CheckBox) view.findViewById(R.id.treatments_wizard_sbcheckbox)).setChecked(JsonHelper.safeGetBoolean(json, "superbolusused")); - // Carbs - ((TextView) view.findViewById(R.id.treatments_wizard_carbs)).setText(DecimalFormatter.to0Decimal(JsonHelper.safeGetDouble(json, "carbs")) + "g IC: " + DecimalFormatter.to1Decimal(JsonHelper.safeGetDouble(json, "ic"))); - ((TextView) view.findViewById(R.id.treatments_wizard_carbsinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulincarbs")) + "U"); - // Correction - ((TextView) view.findViewById(R.id.treatments_wizard_correctioninsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "othercorrection")) + "U"); - // Profile - ((TextView) view.findViewById(R.id.treatments_wizard_profile)).setText(JsonHelper.safeGetString(json, "profile")); - // Notes - ((TextView) view.findViewById(R.id.treatments_wizard_notes)).setText(JsonHelper.safeGetString(json, "notes")); - // Total - ((TextView) view.findViewById(R.id.treatments_wizard_totalinsulin)).setText(DecimalFormatter.to2Decimal(JsonHelper.safeGetDouble(json, "insulin")) + "U"); - - setCancelable(true); - return view; - } - - @Override - public synchronized void onClick(View view) { - switch (view.getId()) { - case R.id.ok: - dismiss(); - break; - } - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.kt b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.kt new file mode 100644 index 00000000000..63f3b57f65f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/dialogs/WizardInfoDialog.kt @@ -0,0 +1,76 @@ +package info.nightscout.androidaps.plugins.treatments.dialogs + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.fragment.app.DialogFragment +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.JsonHelper +import info.nightscout.androidaps.utils.StringUtils +import kotlinx.android.synthetic.main.treatments_wizardinfo_dialog.* +import org.json.JSONObject + +class WizardInfoDialog : DialogFragment() { + private var json: JSONObject? = null + + fun setData(json: JSONObject) { + this.json = json + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + isCancelable = true + return inflater.inflate(R.layout.treatments_wizardinfo_dialog, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + close.setOnClickListener { dismiss() } + val units = ProfileFunctions.getSystemUnits() + val bg_string: String + if (units.equals(Constants.MGDL)) { bg_string = DecimalFormatter.to0Decimal(JsonHelper.safeGetDouble(json, "bg"))} else { bg_string = DecimalFormatter.to1Decimal(JsonHelper.safeGetDouble(json, "bg"))} + // BG + treatments_wizard_bg.text = MainApp.gs(R.string.format_bg_isf, bg_string , JsonHelper.safeGetDouble(json, "isf")) + treatments_wizard_bginsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulinbg")) + treatments_wizard_bgcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "insulinbgused") + treatments_wizard_ttcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "ttused") + // Trend + treatments_wizard_bgtrend.text = JsonHelper.safeGetString(json, "trend") + treatments_wizard_bgtrendinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulintrend")) + treatments_wizard_bgtrendcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "trendused") + // COB + treatments_wizard_cob.text = MainApp.gs(R.string.format_cob_ic, JsonHelper.safeGetDouble(json, "cob"), JsonHelper.safeGetDouble(json, "ic")) + treatments_wizard_cobinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulincob")) + treatments_wizard_cobcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "cobused") + // Bolus IOB + treatments_wizard_bolusiobinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "bolusiob")) + treatments_wizard_bolusiobcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "bolusiobused") + // Basal IOB + treatments_wizard_basaliobinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "basaliob")) + treatments_wizard_basaliobcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "basaliobused") + // Superbolus + treatments_wizard_sbinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulinsuperbolus")) + treatments_wizard_sbcheckbox.isChecked = JsonHelper.safeGetBoolean(json, "superbolusused") + // Carbs + treatments_wizard_carbs.text = MainApp.gs(R.string.format_carbs_ic, JsonHelper.safeGetDouble(json, "carbs"), JsonHelper.safeGetDouble(json, "ic")) + treatments_wizard_carbsinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulincarbs")) + // Correction + treatments_wizard_correctioninsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "othercorrection")) + // Profile + treatments_wizard_profile.text = JsonHelper.safeGetString(json, "profile") + // Notes + treatments_wizard_notes.text = JsonHelper.safeGetString(json, "notes") + // Percentage + treatments_wizard_percent_used.text = DecimalFormatter.to0Decimal(JsonHelper.safeGetDouble(json, "percentageCorrection", 100.0)) + "%" + // Total + treatments_wizard_totalinsulin.text = StringUtils.formatInsulin(JsonHelper.safeGetDouble(json, "insulin")) + } +} 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 deleted file mode 100644 index c8db29b617a..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/ProfileViewerDialog.java +++ /dev/null @@ -1,135 +0,0 @@ -package info.nightscout.androidaps.plugins.treatments.fragments; - -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -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 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.treatments.TreatmentsPlugin; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; - -/** - * Created by adrian on 17/08/17. - */ - -public class ProfileViewerDialog extends DialogFragment { - - private long time; - - @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(); - - Bundle args = new Bundle(); - args.putLong("time", time); - dialog.setArguments(args); - - return dialog; - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - 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 view = inflater.inflate(R.layout.profileviewer_fragment, container, false); - - unbinder = ButterKnife.bind(this, view); - - refreshButton.setVisibility(View.GONE); - dateDelimiter.setVisibility(View.VISIBLE); - dateLayout.setVisibility(View.VISIBLE); - - setContent(); - 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 = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(time); - if (profileSwitch != null && profileSwitch.profileJson != null) { - profile = profileSwitch.getProfileObject(); - } - if (profile != null) { - noProfile.setVisibility(View.GONE); - units.setText(profile.getUnits()); - dia.setText(DecimalFormatter.to2Decimal(profile.getDia()) + " h"); - activeProfile.setText(profileSwitch.getCustomizedName()); - dateTextView.setText(DateUtil.dateAndTimeString(profileSwitch.date)); - ic.setText(profile.getIcList()); - 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 26a8494dd7c..3c1d56afb76 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 @@ -1,24 +1,20 @@ package info.nightscout.androidaps.plugins.treatments.fragments; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -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 com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; @@ -28,31 +24,33 @@ import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.events.EventTreatmentChange; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.treatments.Treatment; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.plugins.treatments.dialogs.WizardInfoDialog; -import info.nightscout.androidaps.services.Intents; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; import static info.nightscout.androidaps.utils.DateUtil.now; -public class TreatmentsBolusFragment extends SubscriberFragment implements View.OnClickListener { - RecyclerView recyclerView; - LinearLayoutManager llm; +public class TreatmentsBolusFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); - TextView iobTotal; - TextView activityTotal; - Button refreshFromNS; - Button deleteFutureTreatments; + private RecyclerView recyclerView; - Context context; + private TextView iobTotal; + private TextView activityTotal; + private Button deleteFutureTreatments; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -62,6 +60,7 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final String _id = treatment._id; + if (treatment.source == Source.PUMP) { + treatment.isValid = false; + TreatmentsPlugin.getPlugin().getService().update(treatment); } else { - UploadQueue.removeID("dbAdd", _id); + if (NSUpload.isIdValid(_id)) { + NSUpload.removeCareportalEntryFromNS(_id); + } else { + UploadQueue.removeID("dbAdd", _id); + } + TreatmentsPlugin.getPlugin().getService().delete(treatment); } - TreatmentsPlugin.getPlugin().getService().delete(treatment); - } - updateGUI(); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); + updateGui(); + }, null); break; case R.id.treatments_calculation: FragmentManager manager = getFragmentManager(); @@ -190,99 +186,80 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_bolus_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.treatments_recyclerview); + recyclerView = view.findViewById(R.id.treatments_recyclerview); recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); + LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTreatmentsFromHistory()); recyclerView.setAdapter(adapter); - iobTotal = (TextView) view.findViewById(R.id.treatments_iobtotal); - activityTotal = (TextView) view.findViewById(R.id.treatments_iobactivitytotal); - - refreshFromNS = (Button) view.findViewById(R.id.treatments_reshreshfromnightscout); - refreshFromNS.setOnClickListener(this); - - deleteFutureTreatments = (Button) view.findViewById(R.id.treatments_delete_future_treatments); - deleteFutureTreatments.setOnClickListener(this); + iobTotal = view.findViewById(R.id.treatments_iobtotal); + activityTotal = view.findViewById(R.id.treatments_iobactivitytotal); + + Button refreshFromNS = view.findViewById(R.id.treatments_reshreshfromnightscout); + refreshFromNS.setOnClickListener(v -> OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.refresheventsfromnightscout) + "?", () -> { + TreatmentsPlugin.getPlugin().getService().resetTreatments(); + RxBus.INSTANCE.send(new EventNSClientRestart()); + })); + + deleteFutureTreatments = view.findViewById(R.id.treatments_delete_future_treatments); + deleteFutureTreatments.setOnClickListener(v -> { + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.overview_treatment_label), MainApp.gs(R.string.deletefuturetreatments) + "?", () -> { + final List futureTreatments = TreatmentsPlugin.getPlugin().getService() + .getTreatmentDataFromTime(now() + 1000, true); + for (Treatment treatment : futureTreatments) { + final String _id = treatment._id; + if (NSUpload.isIdValid(_id)) { + NSUpload.removeCareportalEntryFromNS(_id); + } else { + UploadQueue.removeID("dbAdd", _id); + } + TreatmentsPlugin.getPlugin().getService().delete(treatment); + } + updateGui(); + }); + }); - boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, false); + boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, true); if (nsUploadOnly) refreshFromNS.setVisibility(View.GONE); - context = getContext(); - - updateGUI(); return view; } @Override - public void onClick(View view) { - AlertDialog.Builder builder; - switch (view.getId()) { - case R.id.treatments_reshreshfromnightscout: - builder = new AlertDialog.Builder(this.getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.refresheventsfromnightscout) + "?"); - builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { - TreatmentsPlugin.getPlugin().getService().resetTreatments(); - Intent restartNSClient = new Intent(Intents.ACTION_RESTART); - MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - case R.id.treatments_delete_future_treatments: - builder = new AlertDialog.Builder(this.getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.deletefuturetreatments) + "?"); - builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { - final List futureTreatments = TreatmentsPlugin.getPlugin().getService() - .getTreatmentDataFromTime(now() + 1000, true); - for (Treatment treatment : futureTreatments) { - final String _id = treatment._id; - if (NSUpload.isIdValid(_id)) { - NSUpload.removeCareportalEntryFromNS(_id); - } else { - UploadQueue.removeID("dbAdd", _id); - } - TreatmentsPlugin.getPlugin().getService().delete(treatment); - } - updateGUI(); - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - } - - @Subscribe - public void onStatusEvent(final EventTreatmentChange ev) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventAutosensCalculationFinished ev) { - updateGUI(); + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventTreatmentChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); } @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(() -> { - recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTreatmentsFromHistory()), false); - if (TreatmentsPlugin.getPlugin().getLastCalculationTreatments() != null) { - iobTotal.setText(DecimalFormatter.to2Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().iob) + " " + MainApp.gs(R.string.insulin_unit_shortname)); - activityTotal.setText(DecimalFormatter.to3Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().activity) + " " + MainApp.gs(R.string.insulin_unit_shortname)); - } - if (!TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(now() + 1000, true).isEmpty()) { - deleteFutureTreatments.setVisibility(View.VISIBLE); - } else { - deleteFutureTreatments.setVisibility(View.GONE); - } - }); + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } + private void updateGui() { + recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTreatmentsFromHistory()), false); + if (TreatmentsPlugin.getPlugin().getLastCalculationTreatments() != null) { + iobTotal.setText(DecimalFormatter.to2Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().iob) + " " + MainApp.gs(R.string.insulin_unit_shortname)); + activityTotal.setText(DecimalFormatter.to3Decimal(TreatmentsPlugin.getPlugin().getLastCalculationTreatments().activity) + " " + MainApp.gs(R.string.insulin_unit_shortname)); + } + if (!TreatmentsPlugin.getPlugin().getService().getTreatmentDataFromTime(now() + 1000, true).isEmpty()) { + deleteFutureTreatments.setVisibility(View.VISIBLE); + } else { + deleteFutureTreatments.setVisibility(View.GONE); + } + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java index 2f45f67c3b6..11b0c3a105d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsCareportalFragment.java @@ -1,48 +1,45 @@ package info.nightscout.androidaps.plugins.treatments.fragments; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -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 com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.services.Intents; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.events.EventCareportalEventChange; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.Translator; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; /** * Created by mike on 13/01/17. */ -public class TreatmentsCareportalFragment extends SubscriberFragment implements View.OnClickListener { +public class TreatmentsCareportalFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); - RecyclerView recyclerView; - LinearLayoutManager llm; - Button refreshFromNS; - - Context context; + private RecyclerView recyclerView; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -52,11 +49,11 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final CareportalEvent careportalEvent = (CareportalEvent) v.getTag(); + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.removerecord), + "\n" + MainApp.gs(R.string.careportal_newnstreatment_eventtype) + ": " + Translator.translate(careportalEvent.eventType) + + "\n" + MainApp.gs(R.string.careportal_newnstreatment_notes_label) + ": " + careportalEvent.getNotes() + + "\n" + MainApp.gs(R.string.date) + ": " + DateUtil.dateAndTimeString(careportalEvent.date), + (dialog, id) -> { + final String _id = careportalEvent._id; + if (NSUpload.isIdValid(_id)) { + NSUpload.removeCareportalEntryFromNS(_id); + } else { + UploadQueue.removeID("dbAdd", _id); + } + MainApp.getDbHelper().delete(careportalEvent); + }, null); + }); remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } - - @Override - public void onClick(View v) { - final CareportalEvent careportalEvent = (CareportalEvent) v.getTag(); - switch (v.getId()) { - case R.id.careportal_remove: - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(careportalEvent.date)); - builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { - final String _id = careportalEvent._id; - if (NSUpload.isIdValid(_id)) { - NSUpload.removeCareportalEntryFromNS(_id); - } else { - UploadQueue.removeID("dbAdd", _id); - } - MainApp.getDbHelper().delete(careportalEvent); - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - } } } @@ -129,75 +118,50 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_careportal_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.careportal_recyclerview); + recyclerView = view.findViewById(R.id.careportal_recyclerview); recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); + LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getCareportalEvents(false)); recyclerView.setAdapter(adapter); - refreshFromNS = (Button) view.findViewById(R.id.careportal_refreshfromnightscout); - refreshFromNS.setOnClickListener(this); - - view.findViewById(R.id.careportal_removeandroidapsstartedevents).setOnClickListener(this); + Button refreshFromNS = view.findViewById(R.id.careportal_refreshfromnightscout); + refreshFromNS.setOnClickListener(v -> + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.careportal), MainApp.gs(R.string.refresheventsfromnightscout) + " ?", () -> { + MainApp.getDbHelper().resetCareportalEvents(); + RxBus.INSTANCE.send(new EventNSClientRestart()); + })); - context = getContext(); + view.findViewById(R.id.careportal_removeandroidapsstartedevents).setOnClickListener(v -> + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.careportal), MainApp.gs(R.string.careportal_removestartedevents), this::removeAndroidAPSStatedEvents)); - boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, false); + boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, true); if (nsUploadOnly) refreshFromNS.setVisibility(View.GONE); - updateGUI(); return view; } @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.careportal_refreshfromnightscout: - AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.refresheventsfromnightscout) + " ?"); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - MainApp.getDbHelper().resetCareportalEvents(); - Intent restartNSClient = new Intent(Intents.ACTION_RESTART); - MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - case R.id.careportal_removeandroidapsstartedevents: - builder = new AlertDialog.Builder(context); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.careportal_removestartedevents)); - builder.setPositiveButton(MainApp.gs(R.string.ok), (dialog, id) -> { - removeAndroidAPSStatedEvents(); - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventCareportalEventChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); } - @Subscribe - public void onStatusEvent(final EventCareportalEventChange ev) { - updateGUI(); + @Override + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getCareportalEvents(false)), false); - } - }); + private void updateGui() { + recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getCareportalEvents(false)), false); } private void removeAndroidAPSStatedEvents() { 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 766190e378d..928e2d30aa8 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 @@ -1,21 +1,18 @@ 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.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -24,20 +21,23 @@ import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.events.EventExtendedBolusChange; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class TreatmentsExtendedBolusesFragment extends SubscriberFragment { - RecyclerView recyclerView; - LinearLayoutManager llm; +public class TreatmentsExtendedBolusesFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); - Context context; + private RecyclerView recyclerView; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -47,6 +47,7 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final ExtendedBolus extendedBolus = (ExtendedBolus) v.getTag(); + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.removerecord), + MainApp.gs(R.string.extended_bolus) + + "\n" + MainApp.gs(R.string.date) + ": " + DateUtil.dateAndTimeString(extendedBolus.date), (dialog, id) -> { final String _id = extendedBolus._id; if (NSUpload.isIdValid(_id)) { NSUpload.removeCareportalEntryFromNS(_id); @@ -148,12 +140,9 @@ public void onClick(DialogInterface dialog, int id) { UploadQueue.removeID("dbAdd", _id); } MainApp.getDbHelper().delete(extendedBolus); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } + }, null); + }); + remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } } } @@ -163,40 +152,40 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_extendedbolus_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.extendedboluses_recyclerview); + recyclerView = view.findViewById(R.id.extendedboluses_recyclerview); recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); + LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory()); recyclerView.setAdapter(adapter); - context = getContext(); - - updateGUI(); return view; } - @Subscribe - public void onStatusEvent(final EventExtendedBolusChange ev) { - updateGUI(); - } - - @Subscribe - public void onStatusEvent(final EventAutosensCalculationFinished ev) { - updateGUI(); + @Override + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventExtendedBolusChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); } @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null && recyclerView != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getExtendedBolusesFromHistory()), false); - } - }); + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } + private void updateGui() { + 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 deleted file mode 100644 index c59d5e1836c..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.java +++ /dev/null @@ -1,234 +0,0 @@ -package info.nightscout.androidaps.plugins.treatments.fragments; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Paint; -import android.os.Bundle; -import android.support.v4.app.FragmentManager; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -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 com.squareup.otto.Subscribe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventProfileNeedsUpdate; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.services.Intents; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.ProfileSwitch; -import info.nightscout.androidaps.db.Source; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; -import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.utils.SP; - -/** - * Created by mike on 13/01/17. - */ - -public class TreatmentsProfileSwitchFragment extends SubscriberFragment implements View.OnClickListener { - private Logger log = LoggerFactory.getLogger(L.UI); - - RecyclerView recyclerView; - LinearLayoutManager llm; - Button refreshFromNS; - - Context context; - - public class RecyclerViewAdapter extends RecyclerView.Adapter { - - List profileSwitchList; - - RecyclerViewAdapter(List profileSwitchList) { - this.profileSwitchList = profileSwitchList; - } - - @Override - public ProfileSwitchViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { - View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.treatments_profileswitch_item, viewGroup, false); - return new ProfileSwitchViewHolder(v); - } - - @Override - public void onBindViewHolder(ProfileSwitchViewHolder holder, int position) { - Profile profile = ProfileFunctions.getInstance().getProfile(); - if (profile == null) return; - 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); - - holder.date.setText(DateUtil.dateAndTimeString(profileSwitch.date)); - if (!profileSwitch.isEndingEvent()) { - holder.duration.setText(DecimalFormatter.to0Decimal(profileSwitch.durationInMinutes) + " min"); - } else { - holder.duration.setText(""); - } - holder.name.setText(profileSwitch.getCustomizedName()); - if (profileSwitch.isInProgress()) - holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)); - else - holder.date.setTextColor(holder.duration.getCurrentTextColor()); - holder.remove.setTag(profileSwitch); - holder.name.setTag(profileSwitch); - holder.date.setTag(profileSwitch); - holder.invalid.setVisibility(profileSwitch.isValid() ? View.GONE : View.VISIBLE); - - } - - @Override - public int getItemCount() { - return profileSwitchList.size(); - } - - @Override - public void onAttachedToRecyclerView(RecyclerView recyclerView) { - super.onAttachedToRecyclerView(recyclerView); - } - - public class ProfileSwitchViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - CardView cv; - TextView date; - TextView duration; - TextView name; - TextView remove; - TextView ph; - TextView ns; - TextView invalid; - - ProfileSwitchViewHolder(View itemView) { - super(itemView); - cv = (CardView) itemView.findViewById(R.id.profileswitch_cardview); - date = (TextView) itemView.findViewById(R.id.profileswitch_date); - duration = (TextView) itemView.findViewById(R.id.profileswitch_duration); - name = (TextView) itemView.findViewById(R.id.profileswitch_name); - ph = (TextView) itemView.findViewById(R.id.pump_sign); - ns = (TextView) itemView.findViewById(R.id.ns_sign); - invalid = (TextView) itemView.findViewById(R.id.invalid_sign); - remove = (TextView) itemView.findViewById(R.id.profileswitch_remove); - remove.setOnClickListener(this); - remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); - name.setOnClickListener(this); - date.setOnClickListener(this); - - } - - @Override - public void onClick(View v) { - final ProfileSwitch profileSwitch = (ProfileSwitch) v.getTag(); - if (profileSwitch == null) { - log.error("profileSwitch == null"); - return; - } - switch (v.getId()) { - case R.id.profileswitch_remove: - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(profileSwitch.date)); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - final String _id = profileSwitch._id; - if (NSUpload.isIdValid(_id)) { - NSUpload.removeCareportalEntryFromNS(_id); - } else { - UploadQueue.removeID("dbAdd", _id); - } - MainApp.getDbHelper().delete(profileSwitch); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - case R.id.profileswitch_date: - case R.id.profileswitch_name: - long time = ((ProfileSwitch) v.getTag()).date; - ProfileViewerDialog pvd = ProfileViewerDialog.newInstance(time); - FragmentManager manager = getFragmentManager(); - pvd.show(manager, "ProfileViewDialog"); - break; - } - } - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.treatments_profileswitch_fragment, container, false); - - recyclerView = (RecyclerView) view.findViewById(R.id.profileswitch_recyclerview); - recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); - recyclerView.setLayoutManager(llm); - - RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getProfileSwitchData(false)); - recyclerView.setAdapter(adapter); - - refreshFromNS = (Button) view.findViewById(R.id.profileswitch_refreshfromnightscout); - refreshFromNS.setOnClickListener(this); - - context = getContext(); - - boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, false); - if (nsUploadOnly) - refreshFromNS.setVisibility(View.GONE); - - updateGUI(); - return view; - } - - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.profileswitch_refreshfromnightscout: - AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.refresheventsfromnightscout) + "?"); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - MainApp.getDbHelper().resetProfileSwitch(); - Intent restartNSClient = new Intent(Intents.ACTION_RESTART); - MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - } - - @Subscribe - public void onStatusEvent(final EventProfileNeedsUpdate ev) { - updateGUI(); - } - - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(MainApp.getDbHelper().getProfileSwitchData(false)), false); - } - }); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt new file mode 100644 index 00000000000..c2b16f5d132 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsProfileSwitchFragment.kt @@ -0,0 +1,163 @@ +package info.nightscout.androidaps.plugins.treatments.fragments + +import android.graphics.Paint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.db.ProfileSwitch +import info.nightscout.androidaps.db.Source +import info.nightscout.androidaps.dialogs.ProfileViewerDialog +import info.nightscout.androidaps.events.EventProfileNeedsUpdate +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.profile.local.events.EventLocalProfileChanged +import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsProfileSwitchFragment.RecyclerProfileViewAdapter.ProfileSwitchViewHolder +import info.nightscout.androidaps.utils.* +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import kotlinx.android.synthetic.main.treatments_profileswitch_fragment.* + +class TreatmentsProfileSwitchFragment : Fragment() { + private val disposable = CompositeDisposable() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.treatments_profileswitch_fragment, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + profileswitch_recyclerview.setHasFixedSize(true) + profileswitch_recyclerview.layoutManager = LinearLayoutManager(view.context) + profileswitch_recyclerview.adapter = RecyclerProfileViewAdapter(MainApp.getDbHelper().getProfileSwitchData(DateUtil.now() - T.days(30).msecs(), false)) + + profileswitch_refreshfromnightscout.setOnClickListener { + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.refresheventsfromnightscout) + "?", Runnable { + MainApp.getDbHelper().resetProfileSwitch() + RxBus.send(EventNSClientRestart()) + }) + } + } + if (SP.getBoolean(R.string.key_ns_upload_only, true)) profileswitch_refreshfromnightscout.visibility = View.GONE + + } + + @Synchronized + override fun onResume() { + super.onResume() + disposable.add(RxBus + .toObservable(EventProfileNeedsUpdate::class.java) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ updateGUI() }) { FabricPrivacy.logException(it) } + ) + updateGUI() + } + + @Synchronized + override fun onPause() { + super.onPause() + disposable.clear() + } + + fun updateGUI() = + profileswitch_recyclerview?.swapAdapter(RecyclerProfileViewAdapter(MainApp.getDbHelper().getProfileSwitchData(DateUtil.now() - T.days(30).msecs(), false)), false) + + inner class RecyclerProfileViewAdapter(private var profileSwitchList: List) : RecyclerView.Adapter() { + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ProfileSwitchViewHolder { + return ProfileSwitchViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.treatments_profileswitch_item, viewGroup, false)) + } + + override fun onBindViewHolder(holder: ProfileSwitchViewHolder, position: Int) { + val profileSwitch = profileSwitchList[position] + holder.ph.visibility = (profileSwitch.source == Source.PUMP).toVisibility() + holder.ns.visibility = NSUpload.isIdValid(profileSwitch._id).toVisibility() + holder.date.text = DateUtil.dateAndTimeString(profileSwitch.date) + if (!profileSwitch.isEndingEvent) { + holder.duration.text = DecimalFormatter.to0Decimal(profileSwitch.durationInMinutes.toDouble()) + " " + MainApp.gs(R.string.unit_minute_short) + } else { + holder.duration.text = "" + } + holder.name.text = profileSwitch.customizedName + if (profileSwitch.isInProgress) holder.date.setTextColor(ContextCompat.getColor(MainApp.instance(), R.color.colorActive)) else holder.date.setTextColor(holder.duration.currentTextColor) + holder.remove.tag = profileSwitch + holder.clone.tag = profileSwitch + holder.name.tag = profileSwitch + holder.date.tag = profileSwitch + holder.invalid.visibility = if (profileSwitch.isValid()) View.GONE else View.VISIBLE + } + + override fun getItemCount(): Int { + return profileSwitchList.size + } + + inner class ProfileSwitchViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { + var cv: CardView = itemView.findViewById(R.id.profileswitch_cardview) as CardView + var date: TextView = itemView.findViewById(R.id.profileswitch_date) as TextView + var duration: TextView = itemView.findViewById(R.id.profileswitch_duration) as TextView + var name: TextView = itemView.findViewById(R.id.profileswitch_name) as TextView + var remove: TextView = itemView.findViewById(R.id.profileswitch_remove) as TextView + var clone: TextView = itemView.findViewById(R.id.profileswitch_clone) as TextView + var ph: TextView = itemView.findViewById(R.id.pump_sign) as TextView + var ns: TextView = itemView.findViewById(R.id.ns_sign) as TextView + var invalid: TextView = itemView.findViewById(R.id.invalid_sign) as TextView + + override fun onClick(v: View) { + val profileSwitch = v.tag as ProfileSwitch + when (v.id) { + R.id.profileswitch_remove -> + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.removerecord), + MainApp.gs(R.string.careportal_profileswitch) + ": " + profileSwitch.profileName + + "\n" + MainApp.gs(R.string.date) + ": " + DateUtil.dateAndTimeString(profileSwitch.date), Runnable { + val id = profileSwitch._id + if (NSUpload.isIdValid(id)) NSUpload.removeCareportalEntryFromNS(id) + else UploadQueue.removeID("dbAdd", id) + MainApp.getDbHelper().delete(profileSwitch) + }) + } + R.id.profileswitch_clone -> + activity?.let { activity -> + OKDialog.showConfirmation(activity, MainApp.gs(R.string.careportal_profileswitch), MainApp.gs(R.string.copytolocalprofile) + "\n" + profileSwitch.customizedName + "\n" + DateUtil.dateAndTimeString(profileSwitch.date), Runnable { + profileSwitch.profileObject?.let { + val nonCustomized = it.convertToNonCustomizedProfile() + LocalProfilePlugin.addProfile(LocalProfilePlugin.SingleProfile().copyFrom(nonCustomized, profileSwitch.customizedName + " " + DateUtil.dateAndTimeString(profileSwitch.date).replace(".", "_"))) + RxBus.send(EventLocalProfileChanged()) + } + }) + } + + R.id.profileswitch_date, R.id.profileswitch_name -> { + val args = Bundle() + args.putLong("time", (v.tag as ProfileSwitch).date) + args.putInt("mode", ProfileViewerDialog.Mode.RUNNING_PROFILE.ordinal) + val pvd = ProfileViewerDialog() + pvd.arguments = args + fragmentManager?.let { pvd.show(it, "ProfileViewDialog") } + } + } + } + + init { + remove.setOnClickListener(this) + clone.setOnClickListener(this) + remove.paintFlags = remove.paintFlags or Paint.UNDERLINE_TEXT_FLAG + clone.paintFlags = clone.paintFlags or Paint.UNDERLINE_TEXT_FLAG + name.setOnClickListener(this) + date.setOnClickListener(this) + } + } + } +} \ No newline at end of file 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 eda62a83188..f42f59a527b 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 @@ -1,51 +1,48 @@ package info.nightscout.androidaps.plugins.treatments.fragments; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.graphics.Paint; import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -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 com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -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.bus.RxBus; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; +import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientRestart; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; /** * Created by mike on 13/01/17. */ -public class TreatmentsTempTargetFragment extends SubscriberFragment implements View.OnClickListener { +public class TreatmentsTempTargetFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); - RecyclerView recyclerView; - LinearLayoutManager llm; - Button refreshFromNS; - - Context context; + private RecyclerView recyclerView; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -57,16 +54,16 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final TempTarget tempTarget = (TempTarget) v.getTag(); + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.removerecord), + MainApp.gs(R.string.careportal_temporarytarget) + ": " + tempTarget.friendlyDescription(ProfileFunctions.getSystemUnits()) + + "\n" + DateUtil.dateAndTimeString(tempTarget.date), + (dialog, id) -> { final String _id = tempTarget._id; if (NSUpload.isIdValid(_id)) { NSUpload.removeCareportalEntryFromNS(_id); @@ -152,12 +141,9 @@ public void onClick(DialogInterface dialog, int id) { UploadQueue.removeID("dbAdd", _id); } MainApp.getDbHelper().delete(tempTarget); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } + }, null); + }); + remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } } } @@ -167,62 +153,46 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_temptarget_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.temptargetrange_recyclerview); + recyclerView = view.findViewById(R.id.temptargetrange_recyclerview); recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); + LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTempTargetsFromHistory()); recyclerView.setAdapter(adapter); - refreshFromNS = (Button) view.findViewById(R.id.temptargetrange_refreshfromnightscout); - refreshFromNS.setOnClickListener(this); + Button refreshFromNS = view.findViewById(R.id.temptargetrange_refreshfromnightscout); + refreshFromNS.setOnClickListener(v -> + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.refresheventsfromnightscout) + " ?", () -> { + MainApp.getDbHelper().resetTempTargets(); + RxBus.INSTANCE.send(new EventNSClientRestart()); + })); - context = getContext(); - - boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, false); + boolean nsUploadOnly = SP.getBoolean(R.string.key_ns_upload_only, true); if (nsUploadOnly) refreshFromNS.setVisibility(View.GONE); - updateGUI(); return view; } @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.temptargetrange_refreshfromnightscout: - AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext()); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(MainApp.gs(R.string.refresheventsfromnightscout) + " ?"); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - MainApp.getDbHelper().resetTempTargets(); - Intent restartNSClient = new Intent(Intents.ACTION_RESTART); - MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventTempTargetChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); } - @Subscribe - public void onStatusEvent(final EventTempTargetChange ev) { - updateGUI(); + @Override + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - recyclerView.swapAdapter(new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTempTargetsFromHistory()), false); - } - }); + private void updateGui() { + 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 7b935d354ef..22dd86e3817 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 @@ -1,21 +1,18 @@ package info.nightscout.androidaps.plugins.treatments.fragments; -import android.app.Activity; -import android.content.Context; import android.graphics.Paint; import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.widget.CardView; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.squareup.otto.Subscribe; +import androidx.annotation.NonNull; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -25,7 +22,7 @@ import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventTempBasalChange; -import info.nightscout.androidaps.plugins.common.SubscriberFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; @@ -33,15 +30,18 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.DecimalFormatter; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.OKDialog; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class TreatmentsTemporaryBasalsFragment extends SubscriberFragment { - RecyclerView recyclerView; - LinearLayoutManager llm; +public class TreatmentsTemporaryBasalsFragment extends Fragment { + private CompositeDisposable disposable = new CompositeDisposable(); - TextView tempBasalTotalView; + private RecyclerView recyclerView; - Context context; + private TextView tempBasalTotalView; public class RecyclerViewAdapter extends RecyclerView.Adapter { @@ -51,6 +51,7 @@ public class RecyclerViewAdapter extends RecyclerView.Adapter { + final TemporaryBasal tempBasal = (TemporaryBasal) v.getTag(); + OKDialog.showConfirmation(getContext(), MainApp.gs(R.string.removerecord), + MainApp.gs(R.string.pump_tempbasal_label) + ": " + tempBasal.toStringFull() + + "\n" + MainApp.gs(R.string.date) + ": " + DateUtil.dateAndTimeString(tempBasal.date), + ((dialog, id) -> { + final String _id = tempBasal._id; + if (NSUpload.isIdValid(_id)) { + NSUpload.removeCareportalEntryFromNS(_id); + } else { + UploadQueue.removeID("dbAdd", _id); + } + MainApp.getDbHelper().delete(tempBasal); + }), null); + }); remove.setPaintFlags(remove.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); } - @Override - public void onClick(View v) { - final TemporaryBasal tempBasal = (TemporaryBasal) v.getTag(); - switch (v.getId()) { - case R.id.tempbasals_remove: - AlertDialog.Builder builder = new AlertDialog.Builder(context); - 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); - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); - break; - } - } } } @@ -187,42 +180,46 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.treatments_tempbasals_fragment, container, false); - recyclerView = (RecyclerView) view.findViewById(R.id.tempbasals_recyclerview); + recyclerView = view.findViewById(R.id.tempbasals_recyclerview); recyclerView.setHasFixedSize(true); - llm = new LinearLayoutManager(view.getContext()); + LinearLayoutManager llm = new LinearLayoutManager(view.getContext()); recyclerView.setLayoutManager(llm); RecyclerViewAdapter adapter = new RecyclerViewAdapter(TreatmentsPlugin.getPlugin().getTemporaryBasalsFromHistory()); recyclerView.setAdapter(adapter); - tempBasalTotalView = (TextView) view.findViewById(R.id.tempbasals_totaltempiob); - - context = getContext(); + tempBasalTotalView = view.findViewById(R.id.tempbasals_totaltempiob); - updateGUI(); return view; } - @Subscribe - public void onStatusEvent(final EventTempBasalChange ignored) { - updateGUI(); + @Override + public synchronized void onResume() { + super.onResume(); + disposable.add(RxBus.INSTANCE + .toObservable(EventTempBasalChange.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventAutosensCalculationFinished.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateGui(), FabricPrivacy::logException) + ); + updateGui(); } - @Subscribe - public void onStatusEvent(final EventAutosensCalculationFinished ignored) { - updateGUI(); + @Override + public synchronized void onPause() { + super.onPause(); + disposable.clear(); } - @Override - protected void updateGUI() { - Activity activity = getActivity(); - if (activity != null) - 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")); - }); + private void updateGui() { + 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/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index e4fbacc43c0..05efc63aa32 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -3,10 +3,11 @@ 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; +import androidx.appcompat.app.AppCompatActivity; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,6 +15,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.activities.BolusProgressHelperActivity; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; @@ -21,11 +23,11 @@ import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressHelperActivity; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -99,16 +101,20 @@ public boolean isRunning(Command.CommandType type) { } private synchronized void removeAll(Command.CommandType type) { - for (int i = queue.size() - 1; i >= 0; i--) { - if (queue.get(i).commandType == type) { - queue.remove(i); + synchronized (queue) { + for (int i = queue.size() - 1; i >= 0; i--) { + if (queue.get(i).commandType == type) { + queue.remove(i); + } } } } private synchronized boolean isLastScheduled(Command.CommandType type) { - if (queue.size() > 0 && queue.get(queue.size() - 1).commandType == type) { - return true; + synchronized (queue) { + if (queue.size() > 0 && queue.get(queue.size() - 1).commandType == type) { + return true; + } } return false; } @@ -117,37 +123,44 @@ private synchronized void inject(Command command) { // inject as a first command if (L.isEnabled(L.PUMPQUEUE)) log.debug("Adding as first: " + command.getClass().getSimpleName() + " - " + command.status()); - queue.addFirst(command); + synchronized (queue) { + queue.addFirst(command); + } } private synchronized void add(Command command) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("Adding: " + command.getClass().getSimpleName() + " - " + command.status()); - queue.add(command); + synchronized (queue) { + queue.add(command); + } } synchronized void pickup() { - performing = queue.poll(); + synchronized (queue) { + performing = queue.poll(); + } } synchronized void clear() { performing = null; - for (int i = 0; i < queue.size(); i++) { - queue.get(i).cancel(); + synchronized (queue) { + for (int i = 0; i < queue.size(); i++) { + queue.get(i).cancel(); + } + queue.clear(); } - - queue.clear(); } public int size() { return queue.size(); } - public Command performing() { + Command performing() { return performing; } - public void resetPerforming() { + void resetPerforming() { performing = null; } @@ -179,9 +192,11 @@ public void independentConnect(String reason, Callback callback) { public synchronized boolean bolusInQueue() { if (isRunning(Command.CommandType.BOLUS)) return true; - for (int i = 0; i < queue.size(); i++) { - if (queue.get(i).commandType == Command.CommandType.BOLUS) { - return true; + synchronized (queue) { + for (int i = 0; i < queue.size(); i++) { + if (queue.get(i).commandType == Command.CommandType.BOLUS) { + return true; + } } } return false; @@ -192,7 +207,7 @@ public synchronized boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback Command.CommandType type = detailedBolusInfo.isSMB ? Command.CommandType.SMB_BOLUS : Command.CommandType.BOLUS; if (type == Command.CommandType.SMB_BOLUS) { - if (isRunning(Command.CommandType.BOLUS) || bolusInQueue()) { + if (isRunning(Command.CommandType.BOLUS) || isRunning(Command.CommandType.SMB_BOLUS) || bolusInQueue()) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("Rejecting SMB since a bolus is queue/running"); return false; @@ -202,6 +217,7 @@ public synchronized boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback log.debug("Rejecting bolus, another bolus was issued since request time"); return false; } + removeAll(Command.CommandType.SMB_BOLUS); } @@ -233,7 +249,7 @@ public synchronized boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback // 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)); + RxBus.INSTANCE.send(new EventBolusRequested(detailedBolusInfo.insulin)); } } @@ -259,11 +275,11 @@ public void setTBROverNotification(Callback callback, boolean enable) { public synchronized void cancelAllBoluses() { if (!isRunning(Command.CommandType.BOLUS)) { - MainApp.bus().post(new EventDismissBolusprogressIfRunning(new PumpEnactResult().success(true).enacted(false))); + RxBus.INSTANCE.send(new EventDismissBolusProgressIfRunning(new PumpEnactResult().success(true).enacted(false))); } removeAll(Command.CommandType.BOLUS); removeAll(Command.CommandType.SMB_BOLUS); - ConfigBuilderPlugin.getPlugin().getActivePump().stopBolusDelivering(); + new Thread(() -> ConfigBuilderPlugin.getPlugin().getActivePump().stopBolusDelivering()).run(); } // returns true if command is queued @@ -379,27 +395,27 @@ public boolean setProfile(Profile profile, Callback callback) { if (!MainApp.isEngineeringModeOrRelease()) { Notification notification = new Notification(Notification.NOT_ENG_MODE_OR_RELEASE, MainApp.gs(R.string.not_eng_mode_or_release), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); if (callback != null) callback.result(new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(R.string.not_eng_mode_or_release))).run(); return false; } // Compare with pump limits - Profile.BasalValue[] basalValues = profile.getBasalValues(); + Profile.ProfileValue[] basalValues = profile.getBasalValues(); PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); - for (Profile.BasalValue basalValue : basalValues) { + for (Profile.ProfileValue basalValue : basalValues) { if (basalValue.value < pump.getPumpDescription().basalMinimumRate) { Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.gs(R.string.basalvaluebelowminimum), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); if (callback != null) callback.result(new PumpEnactResult().success(false).enacted(false).comment(MainApp.gs(R.string.basalvaluebelowminimum))).run(); return false; } } - MainApp.bus().post(new EventDismissNotification(Notification.BASAL_VALUE_BELOW_MINIMUM)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.BASAL_VALUE_BELOW_MINIMUM)); // remove all unfinished removeAll(Command.CommandType.BASALPROFILE); @@ -433,6 +449,21 @@ public boolean readStatus(String reason, Callback callback) { return true; } + + public synchronized boolean statusInQueue() { + if (isRunning(Command.CommandType.READSTATUS)) + return true; + synchronized (queue) { + for (int i = 0; i < queue.size(); i++) { + if (queue.get(i).commandType == Command.CommandType.READSTATUS) { + return true; + } + } + } + return false; + } + + // returns true if command is queued public boolean loadHistory(byte type, Callback callback) { if (isRunning(Command.CommandType.LOADHISTORY)) { @@ -512,15 +543,18 @@ public boolean loadEvents(Callback callback) { public Spanned spannedStatus() { String s = ""; int line = 0; - if (performing != null) { - s += "" + performing.status() + ""; + Command perf = performing; + if (perf != null) { + s += "" + perf.status() + ""; line++; } - for (int i = 0; i < queue.size(); i++) { - if (line != 0) - s += "
"; - s += queue.get(i).status(); - line++; + synchronized (queue) { + for (int i = 0; i < queue.size(); i++) { + if (line != 0) + s += "
"; + s += queue.get(i).status(); + line++; + } } return Html.fromHtml(s); } 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 09629522216..85823c2c308 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -14,8 +14,9 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning; import info.nightscout.androidaps.queue.events.EventQueueChanged; import info.nightscout.androidaps.utils.SP; import info.nightscout.androidaps.utils.T; @@ -50,7 +51,7 @@ public class QueueThread extends Thread { public final void run() { if (mWakeLock != null) mWakeLock.acquire(T.mins(10).msecs()); - MainApp.bus().post(new EventQueueChanged()); + RxBus.INSTANCE.send(new EventQueueChanged()); long lastCommandTime; long connectionStartTime = lastCommandTime = System.currentTimeMillis(); @@ -60,15 +61,15 @@ public final void run() { if (pump == null) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("pump == null"); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.pumpNotInitialized))); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.pumpNotInitialized))); SystemClock.sleep(1000); continue; } long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; if (!pump.isConnected() && secondsElapsed > Constants.PUMP_MAX_CONNECTION_TIME_IN_SECONDS) { - MainApp.bus().post(new EventDismissBolusprogressIfRunning(null)); - MainApp.bus().post(new EventPumpStatusChanged(MainApp.gs(R.string.connectiontimedout))); + RxBus.INSTANCE.send(new EventDismissBolusProgressIfRunning(null)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(MainApp.gs(R.string.connectiontimedout))); if (L.isEnabled(L.PUMPQUEUE)) log.debug("timed out"); pump.stopConnecting(); @@ -93,16 +94,16 @@ public final void run() { SystemClock.sleep(1000); //start over again once after watchdog barked //Notification notification = new Notification(Notification.OLD_NSCLIENT, "Watchdog", Notification.URGENT); - //MainApp.bus().post(new EventNewNotification(notification)); + //RxBus.INSTANCE.send(new EventNewNotification(notification)); connectionStartTime = lastCommandTime = System.currentTimeMillis(); pump.connect("watchdog"); } else { queue.clear(); if (L.isEnabled(L.PUMPQUEUE)) log.debug("no connection possible"); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); pump.disconnect("Queue empty"); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); return; } } @@ -110,7 +111,7 @@ public final void run() { if (pump.isHandshakeInProgress()) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("handshaking " + secondsElapsed); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.HANDSHAKING, (int) secondsElapsed)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.HANDSHAKING, (int) secondsElapsed)); SystemClock.sleep(100); continue; } @@ -118,7 +119,7 @@ public final void run() { if (pump.isConnecting()) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("connecting " + secondsElapsed); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING, (int) secondsElapsed)); SystemClock.sleep(1000); continue; } @@ -126,7 +127,7 @@ public final void run() { if (!pump.isConnected()) { if (L.isEnabled(L.PUMPQUEUE)) log.debug("connect"); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTING, (int) secondsElapsed)); pump.connect("Connection needed"); SystemClock.sleep(1000); continue; @@ -141,15 +142,17 @@ public final void run() { // Pickup 1st command and set performing variable if (queue.size() > 0) { queue.pickup(); - if (L.isEnabled(L.PUMPQUEUE)) - log.debug("performing " + queue.performing().status()); - MainApp.bus().post(new EventQueueChanged()); - queue.performing().execute(); - queue.resetPerforming(); - MainApp.bus().post(new EventQueueChanged()); - lastCommandTime = System.currentTimeMillis(); - SystemClock.sleep(100); - continue; + if (queue.performing() != null) { + if (L.isEnabled(L.PUMPQUEUE)) + log.debug("performing " + queue.performing().status()); + RxBus.INSTANCE.send(new EventQueueChanged()); + queue.performing().execute(); + queue.resetPerforming(); + RxBus.INSTANCE.send(new EventQueueChanged()); + lastCommandTime = System.currentTimeMillis(); + SystemClock.sleep(100); + continue; + } } } @@ -159,9 +162,9 @@ public final void run() { waitingForDisconnect = true; if (L.isEnabled(L.PUMPQUEUE)) log.debug("queue empty. disconnect"); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTING)); pump.disconnect("Queue empty"); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + RxBus.INSTANCE.send(new EventPumpStatusChanged(EventPumpStatusChanged.Status.DISCONNECTED)); if (L.isEnabled(L.PUMPQUEUE)) log.debug("disconnected"); return; @@ -173,7 +176,7 @@ public final void run() { } } } finally { - if (mWakeLock != null) + if (mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release(); if (L.isEnabled(L.PUMPQUEUE)) log.debug("thread end"); 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 4dc132e1287..5a677595943 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 @@ -3,13 +3,13 @@ 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.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.general.overview.dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.dialogs.BolusProgressDialog; +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning; import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.DecimalFormatter; @@ -33,7 +33,7 @@ public void execute() { PumpEnactResult r = ConfigBuilderPlugin.getPlugin().getActivePump().deliverTreatment(detailedBolusInfo); BolusProgressDialog.bolusEnded = true; - MainApp.bus().post(new EventDismissBolusprogressIfRunning(r)); + RxBus.INSTANCE.send(new EventDismissBolusProgressIfRunning(r)); if (L.isEnabled(L.PUMPQUEUE)) log.debug("Result success: " + r.success + " enacted: " + r.enacted); 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 6b8a610fa67..b69f3f4dc76 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 @@ -50,8 +50,8 @@ public void execute() { // Send SMS notification if ProfileSwitch is comming from NS ProfileSwitch profileSwitch = TreatmentsPlugin.getPlugin().getProfileSwitchFromHistory(System.currentTimeMillis()); if (profileSwitch != null && r.enacted && profileSwitch.source == Source.NIGHTSCOUT) { - SmsCommunicatorPlugin smsCommunicatorPlugin = MainApp.getSpecificPlugin(SmsCommunicatorPlugin.class); - if (smsCommunicatorPlugin != null && smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { + SmsCommunicatorPlugin smsCommunicatorPlugin = SmsCommunicatorPlugin.INSTANCE; + if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { smsCommunicatorPlugin.sendNotificationToAllNumbers(MainApp.gs(R.string.profile_set_ok)); } } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java b/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java deleted file mode 100644 index b0a53afd13d..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.java +++ /dev/null @@ -1,8 +0,0 @@ -package info.nightscout.androidaps.queue.events; - -/** - * Created by mike on 11.11.2017. - */ - -public class EventQueueChanged { -} diff --git a/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.kt b/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.kt new file mode 100644 index 00000000000..297d443976f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/events/EventQueueChanged.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.queue.events + +import info.nightscout.androidaps.events.Event + +class EventQueueChanged : Event() diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java new file mode 100644 index 00000000000..8abfb22953b --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService; + +public class AutoStartReceiver extends BroadcastReceiver { + public AutoStartReceiver() { + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + context.startForegroundService(new Intent(context, DummyService.class)); + else + context.startService(new Intent(context, DummyService.class)); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java index b10c2e99e52..cb49d8cf4e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java @@ -5,20 +5,23 @@ import android.content.Intent; import android.os.BatteryManager; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.events.EventChargingState; +import info.nightscout.androidaps.plugins.bus.RxBus; public class ChargingStateReceiver extends BroadcastReceiver { + private static EventChargingState lastEvent; + @Override public void onReceive(Context context, Intent intent) { EventChargingState event = grabChargingState(context); if (event != null) - MainApp.bus().post(event); + RxBus.INSTANCE.send(event); + lastEvent = event; } - public EventChargingState grabChargingState(Context context) { + public static EventChargingState grabChargingState(Context context) { BatteryManager bm = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); if (bm == null) @@ -32,4 +35,11 @@ public EventChargingState grabChargingState(Context context) { return event; } + static public boolean isCharging() { + return lastEvent != null && lastEvent.isCharging(); + } + + static public EventChargingState getLastEvent() { + return lastEvent; + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java index 087cd3a5f55..6c2e7c69201 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java @@ -2,7 +2,7 @@ import android.content.Context; import android.content.Intent; -import android.support.v4.content.WakefulBroadcastReceiver; +import androidx.legacy.content.WakefulBroadcastReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java deleted file mode 100644 index 7a9aa31c46a..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.java +++ /dev/null @@ -1,109 +0,0 @@ -package info.nightscout.androidaps.receivers; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.PowerManager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.events.EventProfileNeedsUpdate; -import info.nightscout.androidaps.interfaces.PumpInterface; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.queue.commands.Command; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.androidaps.utils.FabricPrivacy; -import info.nightscout.androidaps.utils.LocalAlertUtils; -import info.nightscout.androidaps.utils.T; - - -/** - * Created by mike on 07.07.2016. - */ -public class KeepAliveReceiver extends BroadcastReceiver { - private static Logger log = LoggerFactory.getLogger(L.CORE); - public static final long STATUS_UPDATE_FREQUENCY = T.mins(15).msecs(); - private static long lastReadStatus = 0; - private static long lastRun = 0; - - public static void cancelAlarm(Context context) { - Intent intent = new Intent(context, KeepAliveReceiver.class); - PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0); - AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - alarmManager.cancel(sender); - } - - @Override - public void onReceive(Context context, Intent rIntent) { - PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:KeepAliveReciever"); - wl.acquire(); - - LocalAlertUtils.shortenSnoozeInterval(); - LocalAlertUtils.checkStaleBGAlert(); - checkPump(); - - if (L.isEnabled(L.CORE)) - log.debug("KeepAlive received"); - wl.release(); - } - - private void checkPump() { - final PumpInterface pump = ConfigBuilderPlugin.getPlugin().getActivePump(); - final Profile profile = ProfileFunctions.getInstance().getProfile(); - if (pump != null && profile != null) { - long lastConnection = pump.lastDataTime(); - boolean isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis(); - boolean isBasalOutdated = Math.abs(profile.getBasal() - pump.getBaseBasalRate()) > pump.getPumpDescription().basalStep; - - if (L.isEnabled(L.CORE)) - log.debug("Last connection: " + DateUtil.dateAndTimeString(lastConnection)); - // sometimes keepalive broadcast stops - // as as workaround test if readStatus was requested before an alarm is generated - if (lastReadStatus != 0 && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { - LocalAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated); - } - - if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().getCommandQueue().isRunning(Command.CommandType.BASALPROFILE)) { - MainApp.bus().post(new EventProfileNeedsUpdate()); - } else if (isStatusOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis(); - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("KeepAlive. Status outdated.", null); - } else if (isBasalOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis(); - ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("KeepAlive. Basal outdated.", null); - } - } - if (lastRun != 0 && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { - log.error("KeepAlive fail"); - FabricPrivacy.getInstance().logCustom("KeepAliveFail"); - } - lastRun = System.currentTimeMillis(); - } - - //called by MainApp at first app start - public void setAlarm(Context context) { - - LocalAlertUtils.shortenSnoozeInterval(); - LocalAlertUtils.presnoozeAlarms(); - - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - Intent i = new Intent(context, KeepAliveReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0); - try { - pi.send(); - } catch (PendingIntent.CanceledException e) { - } - am.cancel(pi); - am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), Constants.keepAliveMsecs, pi); - } - -} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt new file mode 100644 index 00000000000..7ca4f962aea --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt @@ -0,0 +1,121 @@ +package info.nightscout.androidaps.receivers + +import android.app.AlarmManager +import android.app.PendingIntent +import android.app.PendingIntent.CanceledException +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.PowerManager +import android.os.SystemClock +import info.nightscout.androidaps.events.EventProfileNeedsUpdate +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.bus.RxBus.send +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.LocalAlertUtils +import info.nightscout.androidaps.utils.T +import org.slf4j.LoggerFactory +import kotlin.math.abs + +class KeepAliveReceiver : BroadcastReceiver() { + private var lastReadStatus: Long = 0 + private var lastRun: Long = 0 + private var lastIobUpload: Long = 0 + + override fun onReceive(context: Context, rIntent: Intent) { + if (L.isEnabled(L.CORE)) + log.debug("KeepAlive received") + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:KeepAliveReceiver") + wl.acquire(T.mins(2).msecs()) + LocalAlertUtils.shortenSnoozeInterval() + LocalAlertUtils.checkStaleBGAlert() + checkPump() + checkAPS() + wl.release() + } + + companion object { + private val log = LoggerFactory.getLogger(L.CORE) + + private val KEEP_ALIVE_MILLISECONDS = T.mins(5).msecs() + private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() + private val IOB_UPDATE_FREQUENCY = T.mins(5).msecs() + + //called by MainApp at first app start + @JvmStatic + fun setAlarm(context: Context) { + if (L.isEnabled(L.CORE)) + log.debug("KeepAlive scheduled") + SystemClock.sleep(5000) // wait for app initialization + LocalAlertUtils.shortenSnoozeInterval() + LocalAlertUtils.presnoozeAlarms() + val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + val i = Intent(context, KeepAliveReceiver::class.java) + val pi = PendingIntent.getBroadcast(context, 0, i, 0) + try { + pi.send() + } catch (e: CanceledException) { + } + am.cancel(pi) + am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), KEEP_ALIVE_MILLISECONDS, pi) + } + + @JvmStatic + fun cancelAlarm(context: Context) { + if (L.isEnabled(L.CORE)) + log.debug("KeepAlive canceled") + val intent = Intent(context, KeepAliveReceiver::class.java) + val sender = PendingIntent.getBroadcast(context, 0, intent, 0) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.cancel(sender) + } + } + + private fun checkAPS() { + val usedAPS = ConfigBuilderPlugin.getPlugin().activeAPS + var shouldUploadStatus = false + if (usedAPS == null) shouldUploadStatus = true + else if (DateUtil.isOlderThan(usedAPS.lastAPSRun, 5)) shouldUploadStatus = true + if (DateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY) && shouldUploadStatus) { + lastIobUpload = DateUtil.now() + NSUpload.uploadDeviceStatus() + } + } + + private fun checkPump() { + val pump = ConfigBuilderPlugin.getPlugin().activePump + val profile = ProfileFunctions.getInstance().profile + if (pump != null && profile != null) { + val lastConnection = pump.lastDataTime() + val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() + val isBasalOutdated = abs(profile.basal - pump.baseBasalRate) > pump.pumpDescription.basalStep + if (L.isEnabled(L.CORE)) + log.debug("Last connection: " + DateUtil.dateAndTimeString(lastConnection)) + // sometimes keep alive broadcast stops + // as as workaround test if readStatus was requested before an alarm is generated + if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { + LocalAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated) + } + if (!pump.isThisProfileSet(profile) && !ConfigBuilderPlugin.getPlugin().commandQueue.isRunning(Command.CommandType.BASALPROFILE)) { + send(EventProfileNeedsUpdate()) + } else if (isStatusOutdated && !pump.isBusy) { + lastReadStatus = System.currentTimeMillis() + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("KeepAlive. Status outdated.", null) + } else if (isBasalOutdated && !pump.isBusy) { + lastReadStatus = System.currentTimeMillis() + ConfigBuilderPlugin.getPlugin().commandQueue.readStatus("KeepAlive. Basal outdated.", null) + } + } + if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { + log.error("KeepAlive fail") + FabricPrivacy.getInstance().logCustom("KeepAliveFail") + } + lastRun = System.currentTimeMillis() + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NSAlarmReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NSAlarmReceiver.java index 19d54b14aa2..3449bf29437 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NSAlarmReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NSAlarmReceiver.java @@ -10,8 +10,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.nsclient.data.NSAlarm; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; @@ -41,11 +41,11 @@ public void onReceive(Context context, Intent intent) { case Intents.ACTION_URGENT_ALARM: Notification notification = new Notification(nsAlarm); if (notification.isEnabled()) - MainApp.bus().post(new EventNewNotification(notification)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); break; case Intents.ACTION_CLEAR_ALARM: - MainApp.bus().post(new EventDismissNotification(Notification.NSALARM)); - MainApp.bus().post(new EventDismissNotification(Notification.NSURGENTALARM)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.NSALARM)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.NSURGENTALARM)); break; } } diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java index 9a3108e98c8..19cf12bc8a1 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java @@ -8,7 +8,7 @@ import android.net.wifi.SupplicantState; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,20 +16,25 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.events.EventNetworkChange; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; public class NetworkChangeReceiver extends BroadcastReceiver { private static Logger log = LoggerFactory.getLogger(L.CORE); + private static EventNetworkChange lastEvent = null; + + public static final NetworkChangeReceiver instance = new NetworkChangeReceiver(); + @Override public void onReceive(final Context context, final Intent intent) { EventNetworkChange event = grabNetworkStatus(context); if (event != null) - MainApp.bus().post(event); + RxBus.INSTANCE.send(event); } @Nullable - public EventNetworkChange grabNetworkStatus(final Context context) { + public static EventNetworkChange grabNetworkStatus(final Context context) { EventNetworkChange event = new EventNetworkChange(); ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -38,29 +43,42 @@ public EventNetworkChange grabNetworkStatus(final Context context) { if (activeNetwork != null) { if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected()) { - event.wifiConnected = true; + event.setWifiConnected(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(); + event.setSsid(wifiInfo.getSSID()); } if (L.isEnabled(L.CORE)) - log.debug("NETCHANGE: Wifi connected. SSID: " + event.ssid); + log.debug("NETCHANGE: Wifi connected. SSID: " + event.connectedSsid()); } } if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - event.mobileConnected = true; - event.roaming = activeNetwork.isRoaming(); + event.setMobileConnected(true); + event.setRoaming(activeNetwork.isRoaming()); if (L.isEnabled(L.CORE)) - log.debug("NETCHANGE: Mobile connected. Roaming: " + event.roaming); + log.debug("NETCHANGE: Mobile connected. Roaming: " + event.getRoaming()); } } else { if (L.isEnabled(L.CORE)) log.debug("NETCHANGE: Disconnected."); } + lastEvent = event; return event; } -} \ No newline at end of file + + public static boolean isWifiConnected() { + return lastEvent != null && lastEvent.getWifiConnected(); + } + + public static boolean isConnected() { + return lastEvent != null && (lastEvent.getWifiConnected() || lastEvent.getMobileConnected()); + } + + public static EventNetworkChange getLastEvent() { + return lastEvent; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.java new file mode 100644 index 00000000000..46f7498b8bc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.java @@ -0,0 +1,94 @@ +package info.nightscout.androidaps.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.TimeZone; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; +import info.nightscout.androidaps.utils.TimeChangeType; + +public class TimeDateOrTZChangeReceiver extends BroadcastReceiver { + + private static Logger LOG = LoggerFactory.getLogger(L.PUMP); + + private boolean isDST; + + public TimeDateOrTZChangeReceiver() { + isDST = calculateDST(); + } + + private boolean calculateDST() { + TimeZone timeZone = TimeZone.getDefault(); + Date nowDate = new Date(); + + if (timeZone.useDaylightTime()) { + return (timeZone.inDaylightTime(nowDate)); + } else { + return false; + } + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + + PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump(); + + if (activePump==null) { + LOG.debug("TimeDateOrTZChangeReceiver::Time and/or TimeZone changed. [action={}]. Pump is null, exiting.", action); + return; + } + + LOG.debug("TimeDateOrTZChangeReceiver::Date, Time and/or TimeZone changed. [action={}]", action); + LOG.debug("TimeDateOrTZChangeReceiver::Intent::{}", OmnipodUtil.getGsonInstance().toJson(intent)); + + if (action==null) { + LOG.error("TimeDateOrTZChangeReceiver::Action is null. Exiting."); + } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + LOG.info("TimeDateOrTZChangeReceiver::Timezone changed. Notifying pump driver."); + activePump.timezoneOrDSTChanged(TimeChangeType.TimezoneChange); + } else if (Intent.ACTION_TIME_CHANGED.equals(action)) { + boolean currentDst = calculateDST(); + + if (currentDst==isDST) { + LOG.info("TimeDateOrTZChangeReceiver::Time changed (manual). Notifying pump driver."); + activePump.timezoneOrDSTChanged(TimeChangeType.ManualTimeChange); + } else { + if (currentDst) { + LOG.info("TimeDateOrTZChangeReceiver::DST started. Notifying pump driver."); + activePump.timezoneOrDSTChanged(TimeChangeType.DST_Started); + } else { + LOG.info("TimeDateOrTZChangeReceiver::DST ended. Notifying pump driver."); + activePump.timezoneOrDSTChanged(TimeChangeType.DST_Ended); + } + } + + isDST = currentDst; + } else { + LOG.error("TimeDateOrTZChangeReceiver::Unknown action received [name={}]. Exiting.", action); + } + + } + + + public void registerBroadcasts(MainApp mainApp) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_CHANGED); + //filter.addAction(Intent.ACTION_DATE_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + mainApp.registerReceiver(this, filter); + } + + +} \ No newline at end of file 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 4917789db17..2e9130f7db0 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java +++ b/app/src/main/java/info/nightscout/androidaps/services/AlarmSoundService.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.services; +import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -16,6 +17,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; public class AlarmSoundService extends Service { private static Logger log = LoggerFactory.getLogger(L.CORE); @@ -28,8 +30,7 @@ public AlarmSoundService() { @Override public IBinder onBind(Intent intent) { - // TODO: Return the communication channel to the service. - throw new UnsupportedOperationException("Not yet implemented"); + return null; } @Override @@ -37,9 +38,13 @@ public void onCreate() { super.onCreate(); if (L.isEnabled(L.CORE)) log.debug("onCreate"); + Notification notification = PersistentNotificationPlugin.getPlugin().getLastNotification(); + startForeground(PersistentNotificationPlugin.ONGOING_NOTIFICATION_ID, notification); } public int onStartCommand(Intent intent, int flags, int startId) { + Notification notification = PersistentNotificationPlugin.getPlugin().getLastNotification(); + startForeground(PersistentNotificationPlugin.ONGOING_NOTIFICATION_ID, notification); if (player != null && player.isPlaying()) player.stop(); if (L.isEnabled(L.CORE)) @@ -48,28 +53,22 @@ public int onStartCommand(Intent intent, int flags, int startId) { resourceId = intent.getIntExtra("soundid", R.raw.error); player = new MediaPlayer(); - AssetFileDescriptor afd = MainApp.sResources.openRawResourceFd(resourceId); - if (afd == null) - return START_STICKY; try { + AssetFileDescriptor afd = MainApp.sResources.openRawResourceFd(resourceId); + if (afd == null) + return START_STICKY; player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); afd.close(); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - player.setLooping(true); // Set looping - AudioManager manager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); - if (manager == null || !manager.isMusicActive()) { - player.setVolume(100, 100); - } - - try { + player.setLooping(true); // Set looping + AudioManager manager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); + if (manager == null || !manager.isMusicActive()) { + player.setVolume(100, 100); + } player.prepare(); player.start(); - } catch (IOException e) { + } catch (Exception e) { log.error("Unhandled exception", e); } - return START_STICKY; } 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 b98ff6bf943..b31ef78e7dd 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/services/DataService.java @@ -16,17 +16,18 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.events.EventNsFood; import info.nightscout.androidaps.events.EventNsTreatment; +import info.nightscout.androidaps.logging.BundleLogger; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus; import info.nightscout.androidaps.plugins.general.nsclient.data.NSMbg; import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; import info.nightscout.androidaps.plugins.pump.danaR.activities.DanaRNSHistorySync; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.source.SourceDexcomG5Plugin; -import info.nightscout.androidaps.plugins.source.SourceDexcomG6Plugin; +import info.nightscout.androidaps.plugins.source.SourceDexcomPlugin; import info.nightscout.androidaps.plugins.source.SourceEversensePlugin; import info.nightscout.androidaps.plugins.source.SourceGlimpPlugin; import info.nightscout.androidaps.plugins.source.SourceMM640gPlugin; @@ -35,7 +36,6 @@ import info.nightscout.androidaps.plugins.source.SourceTomatoPlugin; import info.nightscout.androidaps.plugins.source.SourceXdripPlugin; import info.nightscout.androidaps.receivers.DataReceiver; -import info.nightscout.androidaps.logging.BundleLogger; import info.nightscout.androidaps.utils.JsonHelper; import info.nightscout.androidaps.utils.SP; @@ -45,7 +45,6 @@ public class DataService extends IntentService { public DataService() { super("DataService"); - registerBus(); } @Override @@ -55,12 +54,8 @@ protected void onHandleIntent(final Intent intent) { log.debug("onHandleIntent " + BundleLogger.log(intent.getExtras())); } - boolean acceptNSData = !SP.getBoolean(R.string.key_ns_upload_only, false); + boolean acceptNSData = !SP.getBoolean(R.string.key_ns_upload_only, true); Bundle bundles = intent.getExtras(); - if (bundles != null && bundles.containsKey("islocal")) { - acceptNSData = acceptNSData || bundles.getBoolean("islocal"); - } - final String action = intent.getAction(); if (Intents.ACTION_NEW_BG_ESTIMATE.equals(action)) { @@ -69,12 +64,8 @@ protected void onHandleIntent(final Intent intent) { SourceMM640gPlugin.getPlugin().handleNewData(intent); } else if (Intents.GLIMP_BG.equals(action)) { SourceGlimpPlugin.getPlugin().handleNewData(intent); - } else if (Intents.DEXCOMG5_BG.equals(action)) { - SourceDexcomG5Plugin.getPlugin().handleNewData(intent); - } else if (Intents.DEXCOMG5_BG_NEW.equals(action)) { - SourceDexcomG5Plugin.getPlugin().handleNewData(intent); - } else if (Intents.DEXCOMG6_BG.equals(action)) { - SourceDexcomG6Plugin.getPlugin().handleNewData(intent); + } else if (Intents.DEXCOM_BG.equals(action)) { + SourceDexcomPlugin.INSTANCE.handleNewData(intent); } else if (Intents.POCTECH_BG.equals(action)) { SourcePoctechPlugin.getPlugin().handleNewData(intent); } else if (Intents.TOMATO_BG.equals(action)) { @@ -91,24 +82,24 @@ protected void onHandleIntent(final Intent intent) { } else if (Intents.ACTION_NEW_STATUS.equals(action)) { NSSettingsStatus.getInstance().handleNewData(intent); } else if (Intents.ACTION_NEW_FOOD.equals(action)) { - EventNsFood evt = new EventNsFood(EventNsFood.ADD, bundles); - MainApp.bus().post(evt); + EventNsFood evt = new EventNsFood(EventNsFood.Companion.getADD(), bundles); + RxBus.INSTANCE.send(evt); } else if (Intents.ACTION_CHANGED_FOOD.equals(action)) { - EventNsFood evt = new EventNsFood(EventNsFood.UPDATE, bundles); - MainApp.bus().post(evt); + EventNsFood evt = new EventNsFood(EventNsFood.Companion.getUPDATE(), bundles); + RxBus.INSTANCE.send(evt); } else if (Intents.ACTION_REMOVED_FOOD.equals(action)) { - EventNsFood evt = new EventNsFood(EventNsFood.REMOVE, bundles); - MainApp.bus().post(evt); + EventNsFood evt = new EventNsFood(EventNsFood.Companion.getREMOVE(), bundles); + RxBus.INSTANCE.send(evt); } else if (acceptNSData && (Intents.ACTION_NEW_TREATMENT.equals(action) || Intents.ACTION_CHANGED_TREATMENT.equals(action) || Intents.ACTION_REMOVED_TREATMENT.equals(action) || Intents.ACTION_NEW_CAL.equals(action) || Intents.ACTION_NEW_MBG.equals(action)) - ) { + ) { handleNewDataFromNSClient(intent); } else if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action)) { - SmsCommunicatorPlugin.getPlugin().handleNewData(intent); + SmsCommunicatorPlugin.INSTANCE.handleNewData(intent); } if (L.isEnabled(L.DATASERVICE)) @@ -119,16 +110,6 @@ protected void onHandleIntent(final Intent intent) { @Override public void onDestroy() { super.onDestroy(); - MainApp.bus().unregister(this); - } - - private void registerBus() { - try { - MainApp.bus().unregister(this); - } catch (RuntimeException x) { - // Ignore - } - MainApp.bus().register(this); } private void handleNewDataFromNSClient(Intent intent) { @@ -202,8 +183,8 @@ private void handleNewDataFromNSClient(Intent intent) { private void handleRemovedTreatmentFromNS(JSONObject json) { // new DB model - EventNsTreatment evtTreatment = new EventNsTreatment(EventNsTreatment.REMOVE, json); - MainApp.bus().post(evtTreatment); + EventNsTreatment evtTreatment = new EventNsTreatment(EventNsTreatment.Companion.getREMOVE(), json); + RxBus.INSTANCE.send(evtTreatment); // old DB model String _id = JsonHelper.safeGetString(json, "_id"); MainApp.getDbHelper().deleteTempTargetById(_id); @@ -215,7 +196,7 @@ private void handleRemovedTreatmentFromNS(JSONObject json) { private void handleTreatmentFromNS(JSONObject json, Intent intent) { // new DB model - int mode = Intents.ACTION_NEW_TREATMENT.equals(intent.getAction()) ? EventNsTreatment.ADD : EventNsTreatment.UPDATE; + int mode = Intents.ACTION_NEW_TREATMENT.equals(intent.getAction()) ? EventNsTreatment.Companion.getADD() : EventNsTreatment.Companion.getUPDATE(); double insulin = JsonHelper.safeGetDouble(json, "insulin"); double carbs = JsonHelper.safeGetDouble(json, "carbs"); String eventType = JsonHelper.safeGetString(json, "eventType"); @@ -225,7 +206,7 @@ private void handleTreatmentFromNS(JSONObject json, Intent intent) { } if (insulin > 0 || carbs > 0) { EventNsTreatment evtTreatment = new EventNsTreatment(mode, json); - MainApp.bus().post(evtTreatment); + RxBus.INSTANCE.send(evtTreatment); } else if (json.has(DanaRNSHistorySync.DANARSIGNATURE)) { // old DB model MainApp.getDbHelper().updateDanaRHistoryRecordId(json); @@ -259,7 +240,7 @@ private void handleTreatmentFromNS(JSONObject json, Intent intent) { 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)); + RxBus.INSTANCE.send(new EventNewNotification(announcement)); } } } 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 5011215b45e..e4a2d0b33bd 100644 --- a/app/src/main/java/info/nightscout/androidaps/services/Intents.java +++ b/app/src/main/java/info/nightscout/androidaps/services/Intents.java @@ -20,13 +20,6 @@ public interface Intents { String ACTION_URGENT_ALARM = "info.nightscout.client.URGENT_ALARM"; String ACTION_CLEAR_ALARM = "info.nightscout.client.CLEAR_ALARM"; - - // App -> NSClient - String ACTION_DATABASE = "info.nightscout.client.DBACCESS"; - String ACTION_RESTART = "info.nightscout.client.RESTART"; - String ACTION_RESEND = "info.nightscout.client.RESEND"; - String ACTION_ACK_ALARM = "info.nightscout.client.ACK_ALARM"; - // xDrip -> App String RECEIVER_PERMISSION = "com.eveningoutpost.dexdrip.permissions.RECEIVE_BG_ESTIMATE"; @@ -48,9 +41,7 @@ public interface Intents { String GLIMP_BG = "it.ct.glicemia.ACTION_GLUCOSE_MEASURED"; - String DEXCOMG5_BG = "com.dexcom.cgm.DATA"; - String DEXCOMG5_BG_NEW = "com.dexcom.cgm.g5.AndroidAPSEVGCallback.BROADCAST"; - String DEXCOMG6_BG = "com.dexcom.cgm.AndroidAPSEVGCallback.BROADCAST"; + String DEXCOM_BG = "com.dexcom.cgm.EXTERNAL_BROADCAST"; String EVERSENSE_BG = "com.senseonics.AndroidAPSEventSubscriber.BROADCAST"; String POCTECH_BG = "com.china.poctech.data"; diff --git a/app/src/main/java/info/nightscout/androidaps/services/LocationService.java b/app/src/main/java/info/nightscout/androidaps/services/LocationService.java new file mode 100644 index 00000000000..9600515d6ff --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/services/LocationService.java @@ -0,0 +1,168 @@ +package info.nightscout.androidaps.services; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.IBinder; + +import androidx.core.app.ActivityCompat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.events.EventAppExit; +import info.nightscout.androidaps.events.EventLocationChange; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; +import info.nightscout.androidaps.utils.FabricPrivacy; +import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.T; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; + +public class LocationService extends Service { + private static Logger log = LoggerFactory.getLogger(L.LOCATION); + private CompositeDisposable disposable = new CompositeDisposable(); + + private LocationManager mLocationManager = null; + private static final float LOCATION_DISTANCE = 10f; + + private static final long LOCATION_INTERVAL_ACTIVE = T.mins(5).msecs(); + private static final long LOCATION_INTERVAL_PASSIVE = T.mins(1).msecs(); // this doesn't cost more power + + private static Location mLastLocation; + + private class LocationListener implements android.location.LocationListener { + + LocationListener(String provider) { + if (L.isEnabled(L.LOCATION)) + log.debug("LocationListener " + provider); + mLastLocation = new Location(provider); + } + + @Override + public void onLocationChanged(Location location) { + if (L.isEnabled(L.LOCATION)) + log.debug("onLocationChanged: " + location); + mLastLocation.set(location); + RxBus.INSTANCE.send(new EventLocationChange(location)); + } + + @Override + public void onProviderDisabled(String provider) { + if (L.isEnabled(L.LOCATION)) + log.debug("onProviderDisabled: " + provider); + } + + @Override + public void onProviderEnabled(String provider) { + if (L.isEnabled(L.LOCATION)) + log.debug("onProviderEnabled: " + provider); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + if (L.isEnabled(L.LOCATION)) + log.debug("onStatusChanged: " + provider); + } + } + + LocationListener mLocationListener; + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + if (L.isEnabled(L.LOCATION)) + log.debug("onStartCommand"); + startForeground(PersistentNotificationPlugin.ONGOING_NOTIFICATION_ID, PersistentNotificationPlugin.getPlugin().getLastNotification()); + return START_STICKY; + } + + @Override + public void onCreate() { + super.onCreate(); + startForeground(PersistentNotificationPlugin.ONGOING_NOTIFICATION_ID, PersistentNotificationPlugin.getPlugin().getLastNotification()); + + if (L.isEnabled(L.LOCATION)) + log.debug("onCreate"); + + initializeLocationManager(); + + try { + if (SP.getString(R.string.key_location, "NONE").equals("NETWORK")) + mLocationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + LOCATION_INTERVAL_ACTIVE, + LOCATION_DISTANCE, + mLocationListener = new LocationListener(LocationManager.NETWORK_PROVIDER) + ); + if (SP.getString(R.string.key_location, "NONE").equals("GPS")) + mLocationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + LOCATION_INTERVAL_ACTIVE, + LOCATION_DISTANCE, + mLocationListener = new LocationListener(LocationManager.GPS_PROVIDER) + ); + if (SP.getString(R.string.key_location, "NONE").equals("PASSIVE")) + mLocationManager.requestLocationUpdates( + LocationManager.PASSIVE_PROVIDER, + LOCATION_INTERVAL_PASSIVE, + LOCATION_DISTANCE, + mLocationListener = new LocationListener(LocationManager.PASSIVE_PROVIDER) + ); + } catch (java.lang.SecurityException ex) { + log.error("fail to request location update, ignore", ex); + } catch (IllegalArgumentException ex) { + log.error("network provider does not exist, " + ex.getMessage()); + } + disposable.add(RxBus.INSTANCE + .toObservable(EventAppExit.class) + .observeOn(Schedulers.io()) + .subscribe(event -> { + if (L.isEnabled(L.CORE)) log.debug("EventAppExit received"); + stopSelf(); + }, FabricPrivacy::logException) + ); + } + + @Override + public void onDestroy() { + if (L.isEnabled(L.LOCATION)) + log.debug("onDestroy"); + super.onDestroy(); + if (mLocationManager != null) { + try { + if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + return; + } + mLocationManager.removeUpdates(mLocationListener); + } catch (Exception ex) { + log.error("fail to remove location listener, ignore", ex); + } + } + disposable.clear(); + } + + private void initializeLocationManager() { + if (L.isEnabled(L.LOCATION)) + log.debug("initializeLocationManager - Provider: " + SP.getString(R.string.key_location, "NONE")); + if (mLocationManager == null) { + mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE); + } + } + + public static Location getLastLocation() { + return mLastLocation; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java index 1d143d073f0..a0dff381045 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.java @@ -2,18 +2,14 @@ import android.Manifest; import android.content.Intent; -import android.os.Build; -import android.support.v7.app.AppCompatActivity; -import com.squareup.otto.Subscribe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.List; import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.activities.PreferencesActivity; @@ -21,26 +17,24 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.plugins.general.careportal.CareportalFragment; -import info.nightscout.androidaps.plugins.general.careportal.Dialogs.NewNSTreatmentDialog; -import info.nightscout.androidaps.plugins.general.careportal.OptionsToShow; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragment; import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs; import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus; +import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService; +import info.nightscout.androidaps.dialogs.ProfileSwitchDialog; import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment; import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment; import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.profile.simple.SimpleProfileFragment; -import info.nightscout.androidaps.plugins.profile.simple.SimpleProfilePlugin; import info.nightscout.androidaps.setupwizard.elements.SWBreak; import info.nightscout.androidaps.setupwizard.elements.SWButton; +import info.nightscout.androidaps.setupwizard.elements.SWEditNumberWithUnits; import info.nightscout.androidaps.setupwizard.elements.SWEditString; import info.nightscout.androidaps.setupwizard.elements.SWEditUrl; import info.nightscout.androidaps.setupwizard.elements.SWFragment; @@ -48,16 +42,15 @@ import info.nightscout.androidaps.setupwizard.elements.SWInfotext; import info.nightscout.androidaps.setupwizard.elements.SWPlugin; import info.nightscout.androidaps.setupwizard.elements.SWRadioButton; -import info.nightscout.androidaps.setupwizard.events.EventSWLabel; import info.nightscout.androidaps.setupwizard.events.EventSWUpdate; import info.nightscout.androidaps.utils.AndroidPermission; import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.PasswordProtection; import info.nightscout.androidaps.utils.SP; -public class SWDefinition { - private static Logger log = LoggerFactory.getLogger(SWDefinition.class); +import static info.nightscout.androidaps.utils.EspressoTestHelperKt.isRunningTest; +public class SWDefinition { private AppCompatActivity activity; private List screens = new ArrayList<>(); @@ -69,570 +62,434 @@ public AppCompatActivity getActivity() { return activity; } - public List getScreens() { + List getScreens() { return screens; } - SWDefinition add(SWScreen newScreen) { - screens.add(newScreen); + private SWDefinition add(SWScreen newScreen) { + if (newScreen != null) screens.add(newScreen); return this; } SWDefinition() { - if (Config.APS || Config.PUMPCONTROL) + if (Config.APS) SWDefinitionFull(); + else if (Config.PUMPCONTROL) + SWDefinitionPumpControl(); else if (Config.NSCLIENT) SWDefinitionNSClient(); } - private void SWDefinitionFull() { - // List all the screens here - add(new SWScreen(R.string.nav_setupwizard) - .add(new SWInfotext() - .label(R.string.welcometosetupwizard)) - ) - .add(new SWScreen(R.string.language) - .skippable(false) - .add(new SWRadioButton() - .option(R.array.languagesArray, R.array.languagesValues) - .preferenceId(R.string.key_language).label(R.string.language) - .comment(R.string.setupwizard_language_prompt)) - .validator(() -> { - String lang = SP.getString("language", "en"); - LocaleHelper.setLocale(MainApp.instance().getApplicationContext(), lang); - return SP.contains(R.string.key_language); - }) - ) - .add(new SWScreen(R.string.end_user_license_agreement) - .skippable(false) - .add(new SWInfotext() - .label(R.string.end_user_license_agreement_text)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.end_user_license_agreement_i_understand) - .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) - .action(() -> { - SP.putBoolean(R.string.key_i_understand, true); - MainApp.bus().post(new EventSWUpdate(false)); - })) - .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) - .validator(() -> SP.getBoolean(R.string.key_i_understand, false)) - ) - .add(new SWScreen(R.string.permission) - .skippable(false) - .add(new SWInfotext() - .label(String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)))) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.askforpermission) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) - .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, AndroidPermission.CASE_BATTERY))) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) - .validator(() -> !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))) - ) - .add(new SWScreen(R.string.permission) - .skippable(false) - .add(new SWInfotext() - .label(MainApp.gs(R.string.needlocationpermission))) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.askforpermission) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) - .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION, AndroidPermission.CASE_LOCATION))) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) - .validator(() -> !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION))) - ) - .add(new SWScreen(R.string.permission) - .skippable(false) - .add(new SWInfotext() - .label(MainApp.gs(R.string.needstoragepermission))) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.askforpermission) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) - .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, AndroidPermission.CASE_STORAGE))) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) - .validator(() -> !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))) - ) - .add(new SWScreen(R.string.nav_import) - .add(new SWInfotext() - .label(R.string.storedsettingsfound)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.nav_import) - .action(() -> ImportExportPrefs.importSharedPreferences(getActivity()))) - .visibility(() -> ImportExportPrefs.file.exists() && !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))) - ) - .add(new SWScreen(R.string.nsclientinternal_title) - .skippable(true) - .add(new SWInfotext() - .label(R.string.nsclientinfotext)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.enable_nsclient) - .action(() -> { - NSClientPlugin.getPlugin().setPluginEnabled(PluginType.GENERAL, true); - NSClientPlugin.getPlugin().setFragmentVisible(PluginType.GENERAL, true); - ConfigBuilderFragment.processOnEnabledCategoryChanged(NSClientPlugin.getPlugin(), PluginType.GENERAL); - ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); - MainApp.bus().post(new EventConfigBuilderChange()); - MainApp.bus().post(new EventSWUpdate(true)); - }) - .visibility(() -> !NSClientPlugin.getPlugin().isEnabled(PluginType.GENERAL))) - .add(new SWEditUrl() - .preferenceId(R.string.key_nsclientinternal_url) - .updateDelay(5) - .label(R.string.nsclientinternal_url_title) - .comment(R.string.nsclientinternal_url_dialogmessage)) - .add(new SWEditString() - .validator(text -> text.length() >= 12) - .preferenceId(R.string.key_nsclientinternal_api_secret) - .updateDelay(5) - .label(R.string.nsclientinternal_secret_dialogtitle) - .comment(R.string.nsclientinternal_secret_dialogmessage)) - .add(new SWBreak()) - .add(new SWEventListener(this) - .label(R.string.status) - .initialStatus(NSClientPlugin.getPlugin().status) - .listener(new Object() { - @Subscribe - public void onEventNSClientStatus(EventNSClientStatus event) { - MainApp.bus().post(new EventSWLabel(event.status)); - } - }) - ) - .add(new SWBreak()) - .validator(() -> NSClientPlugin.getPlugin().nsClientService != null && NSClientPlugin.getPlugin().nsClientService.isConnected && NSClientPlugin.getPlugin().nsClientService.hasWriteAuth) - .visibility(() -> !(NSClientPlugin.getPlugin().nsClientService != null && NSClientPlugin.getPlugin().nsClientService.isConnected && NSClientPlugin.getPlugin().nsClientService.hasWriteAuth)) - ) - .add(new SWScreen(R.string.patientage) - .skippable(false) - .add(new SWInfotext() - .label(R.string.patientage_summary)) - .add(new SWBreak()) - .add(new SWRadioButton() - .option(R.array.ageArray, R.array.ageValues) - .preferenceId(R.string.key_age) - .label(R.string.patientage) - .comment(R.string.patientage_summary)) - .validator(() -> SP.contains(R.string.key_age)) - ) - .add(new SWScreen(R.string.configbuilder_insulin) - .skippable(false) - .add(new SWPlugin() - .option(PluginType.INSULIN, R.string.configbuilder_insulin_description) - .makeVisible(false) - .label(R.string.configbuilder_insulin)) - .add(new SWBreak()) - .add(new SWInfotext() - .label(R.string.diawarning)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.insulinsourcesetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin(); + private SWScreen screenSetupWizard = new SWScreen(R.string.nav_setupwizard) + .add(new SWInfotext() + .label(R.string.welcometosetupwizard)); + + private SWScreen screenLanguage = new SWScreen(R.string.language) + .skippable(false) + .add(new SWRadioButton() + .option(R.array.languagesArray, R.array.languagesValues) + .preferenceId(R.string.key_language).label(R.string.language) + .comment(R.string.setupwizard_language_prompt)) + .validator(() -> { + LocaleHelper.INSTANCE.update(MainApp.instance().getApplicationContext()); + return SP.contains(R.string.key_language); + }); + + private SWScreen screenEula = new SWScreen(R.string.end_user_license_agreement) + .skippable(false) + .add(new SWInfotext() + .label(R.string.end_user_license_agreement_text)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.end_user_license_agreement_i_understand) + .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) + .action(() -> { + SP.putBoolean(R.string.key_i_understand, true); + RxBus.INSTANCE.send(new EventSWUpdate(false)); + })) + .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) + .validator(() -> SP.getBoolean(R.string.key_i_understand, false)); + + private SWScreen screenUnits = new SWScreen(R.string.units) + .skippable(false) + .add(new SWRadioButton() + .option(R.array.unitsArray, R.array.unitsValues) + .preferenceId(R.string.key_units).label(R.string.units) + .comment(R.string.setupwizard_units_prompt)) + .validator(() -> SP.contains(R.string.key_units)); + + private SWScreen displaySettings = new SWScreen(R.string.wear_display_settings) + .skippable(false) + .add(new SWEditNumberWithUnits(Constants.LOWMARK * Constants.MGDL_TO_MMOLL, 3d, 8d) + .preferenceId(R.string.key_low_mark) + .updateDelay(5) + .label(R.string.low_mark) + .comment(R.string.low_mark_comment)) + .add(new SWBreak()) + .add(new SWEditNumberWithUnits(Constants.HIGHMARK * Constants.MGDL_TO_MMOLL, 5d, 20d) + .preferenceId(R.string.key_high_mark) + .updateDelay(5) + .label(R.string.high_mark) + .comment(R.string.high_mark_comment)); + + private SWScreen screenPermissionBattery = new SWScreen(R.string.permission) + .skippable(false) + .add(new SWInfotext() + .label(String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)))) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.askforpermission) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) + .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, AndroidPermission.CASE_BATTERY))) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) + .validator(() -> !(AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))); + + private SWScreen screenPermissionBt = new SWScreen(R.string.permission) + .skippable(false) + .add(new SWInfotext() + .label(MainApp.gs(R.string.needlocationpermission))) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.askforpermission) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) + .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION, AndroidPermission.CASE_LOCATION))) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)) + .validator(() -> !(AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION))); + + private SWScreen screenPermissionStore = new SWScreen(R.string.permission) + .skippable(false) + .add(new SWInfotext() + .label(MainApp.gs(R.string.needstoragepermission))) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.askforpermission) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) + .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, AndroidPermission.CASE_STORAGE))) + .visibility(() -> AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) + .validator(() -> !(AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))); + + private SWScreen screenImport = new SWScreen(R.string.nav_import) + .add(new SWInfotext() + .label(R.string.storedsettingsfound)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.nav_import) + .action(() -> ImportExportPrefs.importSharedPreferences(getActivity()))) + .visibility(() -> ImportExportPrefs.file.exists() && !(AndroidPermission.permissionNotGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))); + + private SWScreen screenNsClient = new SWScreen(R.string.nsclientinternal_title) + .skippable(true) + .add(new SWInfotext() + .label(R.string.nsclientinfotext)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.enable_nsclient) + .action(() -> { + NSClientPlugin.getPlugin().setPluginEnabled(PluginType.GENERAL, true); + NSClientPlugin.getPlugin().setFragmentVisible(PluginType.GENERAL, true); + ConfigBuilderPlugin.getPlugin().processOnEnabledCategoryChanged(NSClientPlugin.getPlugin(), PluginType.GENERAL); + ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); + RxBus.INSTANCE.send(new EventConfigBuilderChange()); + RxBus.INSTANCE.send(new EventSWUpdate(true)); + }) + .visibility(() -> !NSClientPlugin.getPlugin().isEnabled(PluginType.GENERAL))) + .add(new SWEditUrl() + .preferenceId(R.string.key_nsclientinternal_url) + .updateDelay(5) + .label(R.string.nsclientinternal_url_title) + .comment(R.string.nsclientinternal_url_dialogmessage)) + .add(new SWEditString() + .validator(text -> text.length() >= 12) + .preferenceId(R.string.key_nsclientinternal_api_secret) + .updateDelay(5) + .label(R.string.nsclientinternal_secret_dialogtitle) + .comment(R.string.nsclientinternal_secret_dialogmessage)) + .add(new SWBreak()) + .add(new SWEventListener(this, EventNSClientStatus.class) + .label(R.string.status) + .initialStatus(NSClientPlugin.getPlugin().status) + ) + .add(new SWBreak()) + .validator(() -> NSClientPlugin.getPlugin().nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth) + .visibility(() -> !(NSClientPlugin.getPlugin().nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth)); + + private SWScreen screenAge = new SWScreen(R.string.patientage) + .skippable(false) + .add(new SWBreak()) + .add(new SWRadioButton() + .option(R.array.ageArray, R.array.ageValues) + .preferenceId(R.string.key_age) + .label(R.string.patientage) + .comment(R.string.patientage_summary)) + .validator(() -> SP.contains(R.string.key_age)); + + private SWScreen screenInsulin = new SWScreen(R.string.configbuilder_insulin) + .skippable(false) + .add(new SWPlugin() + .option(PluginType.INSULIN, R.string.configbuilder_insulin_description) + .makeVisible(false) + .label(R.string.configbuilder_insulin)) + .add(new SWBreak()) + .add(new SWInfotext() + .label(R.string.diawarning)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.insulinsourcesetup) + .action(() -> { + final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin(); + if (plugin != null) { PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { Intent i = new Intent(activity, PreferencesActivity.class); i.putExtra("id", plugin.getPreferencesId()); activity.startActivity(i); }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin()!= null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin() != null) - ) - .add(new SWScreen(R.string.configbuilder_bgsource) - .skippable(false) - .add(new SWPlugin() - .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) - .label(R.string.configbuilder_bgsource)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.bgsourcesetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource(); + } + }) + .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin()).getPreferencesId() > 0)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin() != null); + + private SWScreen screenBgSource = new SWScreen(R.string.configbuilder_bgsource) + .skippable(false) + .add(new SWPlugin() + .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) + .label(R.string.configbuilder_bgsource)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.bgsourcesetup) + .action(() -> { + final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource(); + if (plugin != null) { PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { Intent i = new Intent(activity, PreferencesActivity.class); i.putExtra("id", plugin.getPreferencesId()); activity.startActivity(i); }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource()!= null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource() != null) - ) - .add(new SWScreen(R.string.configbuilder_profile) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_profile_description)) - .add(new SWBreak()) - .add(new SWPlugin() - .option(PluginType.PROFILE, R.string.configbuilder_profile_description) - .label(R.string.configbuilder_profile)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveProfileInterface() != null) - ) - .add(new SWScreen(R.string.nsprofile) - .skippable(false) - .add(new SWInfotext() - .label(R.string.adjustprofileinns)) - .add(new SWFragment(this) - .add(new NSProfileFragment())) - .validator(() -> NSProfilePlugin.getPlugin().getProfile() != null && NSProfilePlugin.getPlugin().getProfile().getDefaultProfile().isValid("StartupWizard")) - .visibility(() -> NSProfilePlugin.getPlugin().isEnabled(PluginType.PROFILE)) - ) - .add(new SWScreen(R.string.localprofile) - .skippable(false) - .add(new SWFragment(this) - .add(new LocalProfileFragment())) - .validator(() -> LocalProfilePlugin.getPlugin().getProfile() != null && LocalProfilePlugin.getPlugin().getProfile().getDefaultProfile().isValid("StartupWizard")) - .visibility(() -> LocalProfilePlugin.getPlugin().isEnabled(PluginType.PROFILE)) - ) - .add(new SWScreen(R.string.simpleprofile) - .skippable(false) - .add(new SWFragment(this) - .add(new SimpleProfileFragment())) - .validator(() -> SimpleProfilePlugin.getPlugin().getProfile() != null && SimpleProfilePlugin.getPlugin().getProfile().getDefaultProfile().isValid("StartupWizard")) - .visibility(() -> SimpleProfilePlugin.getPlugin().isEnabled(PluginType.PROFILE)) - ) - .add(new SWScreen(R.string.profileswitch) - .skippable(false) - .add(new SWInfotext() - .label(R.string.profileswitch_ismissing)) - .add(new SWButton() - .text(R.string.profileswitch) - .action(() -> { - NewNSTreatmentDialog newDialog = new NewNSTreatmentDialog(); - final OptionsToShow profileswitch = CareportalFragment.PROFILESWITCHDIRECT; - profileswitch.executeProfileSwitch = true; - newDialog.setOptions(profileswitch, R.string.careportal_profileswitch); - newDialog.show(getActivity().getSupportFragmentManager(), "NewNSTreatmentDialog"); - })) - .validator(() -> ProfileFunctions.getInstance().getProfile() != null) - .visibility(() -> ProfileFunctions.getInstance().getProfile() == null) - ) - .add(new SWScreen(R.string.configbuilder_pump) - .skippable(false) - .add(new SWPlugin() - .option(PluginType.PUMP, R.string.configbuilder_pump_description) - .label(R.string.configbuilder_pump)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.pumpsetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActivePump(); + } + }) + .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource()).getPreferencesId() > 0)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource() != null); + + private SWScreen screenProfile = new SWScreen(R.string.configbuilder_profile) + .skippable(false) + .add(new SWInfotext() + .label(R.string.setupwizard_profile_description)) + .add(new SWBreak()) + .add(new SWPlugin() + .option(PluginType.PROFILE, R.string.configbuilder_profile_description) + .label(R.string.configbuilder_profile)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveProfileInterface() != null); + + private SWScreen screenNsProfile = new SWScreen(R.string.nsprofile) + .skippable(false) + .add(new SWInfotext() + .label(R.string.adjustprofileinns)) + .add(new SWFragment(this) + .add(new NSProfileFragment())) + .validator(() -> NSProfilePlugin.getPlugin().getProfile() != null && NSProfilePlugin.getPlugin().getProfile().getDefaultProfile() != null && NSProfilePlugin.getPlugin().getProfile().getDefaultProfile().isValid("StartupWizard")) + .visibility(() -> NSProfilePlugin.getPlugin().isEnabled(PluginType.PROFILE)); + + private SWScreen screenLocalProfile = new SWScreen(R.string.localprofile) + .skippable(false) + .add(new SWFragment(this) + .add(new LocalProfileFragment())) + .validator(() -> LocalProfilePlugin.INSTANCE.getProfile() != null && LocalProfilePlugin.INSTANCE.getProfile().getDefaultProfile() != null && LocalProfilePlugin.INSTANCE.getProfile().getDefaultProfile().isValid("StartupWizard")) + .visibility(() -> LocalProfilePlugin.INSTANCE.isEnabled(PluginType.PROFILE)); + + private SWScreen screenProfileSwitch = new SWScreen(R.string.careportal_profileswitch) + .skippable(false) + .add(new SWInfotext() + .label(R.string.profileswitch_ismissing)) + .add(new SWButton() + .text(R.string.doprofileswitch) + .action(() -> { + new ProfileSwitchDialog().show(getActivity().getSupportFragmentManager(), "SetupWizard"); + })) + .validator(() -> ProfileFunctions.getInstance().getProfile() != null) + .visibility(() -> ProfileFunctions.getInstance().getProfile() == null); + + private SWScreen screenPump = new SWScreen(R.string.configbuilder_pump) + .skippable(false) + .add(new SWPlugin() + .option(PluginType.PUMP, R.string.configbuilder_pump_description) + .label(R.string.configbuilder_pump)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.pumpsetup) + .action(() -> { + final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActivePump(); + if (plugin != null) { PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { Intent i = new Intent(activity, PreferencesActivity.class); i.putExtra("id", plugin.getPreferencesId()); activity.startActivity(i); }, null); - }) - .visibility(() -> ((PluginBase) ConfigBuilderPlugin.getPlugin().getActivePump()).getPreferencesId() > 0)) - .add(new SWButton() - .text(R.string.readstatus) - .action(() -> ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("Clicked connect to pump", null)) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActivePump() != null)) - .add(new SWEventListener(this) - .listener(new Object() { - @Subscribe - public void onEventPumpStatusChanged(EventPumpStatusChanged event) { - MainApp.bus().post(new EventSWLabel(event.textStatus())); - } - }) - ) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActivePump() != null && ConfigBuilderPlugin.getPlugin().getActivePump().isInitialized()) - ) - .add(new SWScreen(R.string.configbuilder_aps) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_aps_description)) - .add(new SWBreak()) - .add(new SWHtmlLink() - .label("https://openaps.readthedocs.io/en/latest/")) - .add(new SWBreak()) - .add(new SWPlugin() - .option(PluginType.APS, R.string.configbuilder_aps_description) - .label(R.string.configbuilder_aps)) - .add(new SWButton() - .text(R.string.apssetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveAPS(); + } + }) + .visibility(() -> (ConfigBuilderPlugin.getPlugin().getActivePump() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActivePump()).getPreferencesId() > 0))) + .add(new SWButton() + .text(R.string.readstatus) + .action(() -> ConfigBuilderPlugin.getPlugin().getCommandQueue().readStatus("Clicked connect to pump", null)) + .visibility(() -> ConfigBuilderPlugin.getPlugin().getActivePump() != null)) + .add(new SWEventListener(this, EventPumpStatusChanged.class)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActivePump() != null && ConfigBuilderPlugin.getPlugin().getActivePump().isInitialized()); + + private SWScreen screenAps = new SWScreen(R.string.configbuilder_aps) + .skippable(false) + .add(new SWInfotext() + .label(R.string.setupwizard_aps_description)) + .add(new SWBreak()) + .add(new SWHtmlLink() + .label("https://openaps.readthedocs.io/en/latest/")) + .add(new SWBreak()) + .add(new SWPlugin() + .option(PluginType.APS, R.string.configbuilder_aps_description) + .label(R.string.configbuilder_aps)) + .add(new SWButton() + .text(R.string.apssetup) + .action(() -> { + final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveAPS(); + if (plugin != null) { PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { Intent i = new Intent(activity, PreferencesActivity.class); i.putExtra("id", plugin.getPreferencesId()); activity.startActivity(i); }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveAPS() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveAPS()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveAPS() != null) - .visibility(() -> Config.APS) - ) - .add(new SWScreen(R.string.apsmode_title) - .skippable(false) - .add(new SWRadioButton() - .option(R.array.aps_modeArray, R.array.aps_modeValues) - .preferenceId(R.string.key_aps_mode).label(R.string.apsmode_title) - .comment(R.string.setupwizard_preferred_aps_mode)) - .validator(() -> SP.contains(R.string.key_aps_mode)) - ) - .add(new SWScreen(R.string.configbuilder_loop) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_loop_description)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.enableloop) - .action(() -> { - LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, true); - LoopPlugin.getPlugin().setFragmentVisible(PluginType.LOOP, true); - ConfigBuilderFragment.processOnEnabledCategoryChanged(LoopPlugin.getPlugin(), PluginType.LOOP); - ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); - MainApp.bus().post(new EventConfigBuilderChange()); - MainApp.bus().post(new EventSWUpdate(true)); - }) - .visibility(() -> !LoopPlugin.getPlugin().isEnabled(PluginType.LOOP))) - .validator(() -> LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) - .visibility(() -> !LoopPlugin.getPlugin().isEnabled(PluginType.LOOP) && Config.APS) - ) - .add(new SWScreen(R.string.configbuilder_sensitivity) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_sensitivity_description)) - .add(new SWHtmlLink() - .label(R.string.setupwizard_sensitivity_url)) - .add(new SWBreak()) - .add(new SWPlugin() - .option(PluginType.SENSITIVITY, R.string.configbuilder_sensitivity_description) - .label(R.string.configbuilder_sensitivity)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.sensitivitysetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity(); + } + }) + .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveAPS() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveAPS()).getPreferencesId() > 0)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveAPS() != null) + .visibility(() -> Config.APS); + + private SWScreen screenApsMode = new SWScreen(R.string.apsmode_title) + .skippable(false) + .add(new SWRadioButton() + .option(R.array.aps_modeArray, R.array.aps_modeValues) + .preferenceId(R.string.key_aps_mode).label(R.string.apsmode_title) + .comment(R.string.setupwizard_preferred_aps_mode)) + .validator(() -> SP.contains(R.string.key_aps_mode)); + + private SWScreen screenLoop = new SWScreen(R.string.configbuilder_loop) + .skippable(false) + .add(new SWInfotext() + .label(R.string.setupwizard_loop_description)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.enableloop) + .action(() -> { + LoopPlugin.getPlugin().setPluginEnabled(PluginType.LOOP, true); + LoopPlugin.getPlugin().setFragmentVisible(PluginType.LOOP, true); + ConfigBuilderPlugin.getPlugin().processOnEnabledCategoryChanged(LoopPlugin.getPlugin(), PluginType.LOOP); + ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); + RxBus.INSTANCE.send(new EventConfigBuilderChange()); + RxBus.INSTANCE.send(new EventSWUpdate(true)); + }) + .visibility(() -> !LoopPlugin.getPlugin().isEnabled(PluginType.LOOP))) + .validator(() -> LoopPlugin.getPlugin().isEnabled(PluginType.LOOP)) + .visibility(() -> !LoopPlugin.getPlugin().isEnabled(PluginType.LOOP) && Config.APS); + + private SWScreen screenSensitivity = new SWScreen(R.string.configbuilder_sensitivity) + .skippable(false) + .add(new SWInfotext() + .label(R.string.setupwizard_sensitivity_description)) + .add(new SWHtmlLink() + .label(R.string.setupwizard_sensitivity_url)) + .add(new SWBreak()) + .add(new SWPlugin() + .option(PluginType.SENSITIVITY, R.string.configbuilder_sensitivity_description) + .label(R.string.configbuilder_sensitivity)) + .add(new SWBreak()) + .add(new SWButton() + .text(R.string.sensitivitysetup) + .action(() -> { + final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity(); + if (plugin != null) { PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { Intent i = new Intent(activity, PreferencesActivity.class); i.putExtra("id", plugin.getPreferencesId()); activity.startActivity(i); }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null) - ) - .add(new SWScreen(R.string.objectives) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_objectives_description)) - .add(new SWButton() - .text(R.string.enableobjectives) - .action(() -> { - ObjectivesPlugin.getPlugin().setPluginEnabled(PluginType.CONSTRAINTS, true); - ObjectivesPlugin.getPlugin().setFragmentVisible(PluginType.CONSTRAINTS, true); - ConfigBuilderFragment.processOnEnabledCategoryChanged(ObjectivesPlugin.getPlugin(), PluginType.CONSTRAINTS); - ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); - MainApp.bus().post(new EventConfigBuilderChange()); - MainApp.bus().post(new EventSWUpdate(true)); - }) - .visibility(() -> !ObjectivesPlugin.getPlugin().isFragmentVisible())) - .validator(() -> ObjectivesPlugin.getPlugin().isEnabled(PluginType.CONSTRAINTS)) - .visibility(() -> !ObjectivesPlugin.getPlugin().isFragmentVisible() && Config.APS) - ) - .add(new SWScreen(R.string.objectives) - .skippable(false) - .add(new SWInfotext() - .label(R.string.startobjective)) - .add(new SWBreak()) - .add(new SWFragment(this) - .add(new ObjectivesFragment())) - .validator(() -> ObjectivesPlugin.getPlugin().objectives.get(0).isStarted()) - .visibility(() -> !ObjectivesPlugin.getPlugin().objectives.get(0).isStarted() && Config.APS) - ) + } + }) + .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity()).getPreferencesId() > 0)) + .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null); + + private SWScreen getScreenObjectives = new SWScreen(R.string.objectives) + .skippable(false) + .add(new SWInfotext() + .label(R.string.startobjective)) + .add(new SWBreak()) + .add(new SWFragment(this) + .add(new ObjectivesFragment())) + .validator(() -> ObjectivesPlugin.INSTANCE.getObjectives().get(ObjectivesPlugin.INSTANCE.getFIRST_OBJECTIVE()).isStarted()) + .visibility(() -> !ObjectivesPlugin.INSTANCE.getObjectives().get(ObjectivesPlugin.INSTANCE.getFIRST_OBJECTIVE()).isStarted() && Config.APS); + + private void SWDefinitionFull() { + // List all the screens here + add(screenSetupWizard) + .add(screenLanguage) + .add(screenEula) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization + .add(screenPermissionBt) + .add(screenPermissionStore) + .add(screenImport) + .add(screenUnits) + .add(displaySettings) + .add(screenNsClient) + .add(screenAge) + .add(screenInsulin) + .add(screenBgSource) + .add(screenProfile) + .add(screenNsProfile) + .add(screenLocalProfile) + .add(screenProfileSwitch) + .add(screenPump) + .add(screenAps) + .add(screenApsMode) + .add(screenLoop) + .add(screenSensitivity) + .add(getScreenObjectives) ; } - private void SWDefinitionNSClient() { + private void SWDefinitionPumpControl() { // List all the screens here - add(new SWScreen(R.string.nav_setupwizard) - .add(new SWInfotext() - .label(R.string.welcometosetupwizard)) - ) - .add(new SWScreen(R.string.language) - .skippable(false) - .add(new SWRadioButton() - .option(R.array.languagesArray, R.array.languagesValues) - .preferenceId(R.string.key_language).label(R.string.language) - .comment(R.string.setupwizard_language_prompt)) - .validator(() -> { - String lang = SP.getString("language", "en"); - LocaleHelper.setLocale(MainApp.instance().getApplicationContext(), lang); - return SP.contains(R.string.key_language); - }) - ) - .add(new SWScreen(R.string.end_user_license_agreement) - .skippable(false) - .add(new SWInfotext() - .label(R.string.end_user_license_agreement_text)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.end_user_license_agreement_i_understand) - .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) - .action(() -> { - SP.putBoolean(R.string.key_i_understand, true); - MainApp.bus().post(new EventSWUpdate(false)); - })) - .visibility(() -> !SP.getBoolean(R.string.key_i_understand, false)) - .validator(() -> SP.getBoolean(R.string.key_i_understand, false)) - ) - .add(new SWScreen(R.string.permission) - .skippable(false) - .add(new SWInfotext() - .label(String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)))) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.askforpermission) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) - .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, AndroidPermission.CASE_BATTERY))) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) - .validator(() -> !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))) - ) - .add(new SWScreen(R.string.permission) - .skippable(false) - .add(new SWInfotext() - .label(MainApp.gs(R.string.needstoragepermission))) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.askforpermission) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) - .action(() -> AndroidPermission.askForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, AndroidPermission.CASE_STORAGE))) - .visibility(() -> Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) - .validator(() -> !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))) - ) - .add(new SWScreen(R.string.nav_import) - .add(new SWInfotext() - .label(R.string.storedsettingsfound)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.nav_import) - .action(() -> ImportExportPrefs.importSharedPreferences(getActivity()))) - .visibility(() -> ImportExportPrefs.file.exists() && !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !AndroidPermission.checkForPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE))) - ) - .add(new SWScreen(R.string.nsclientinternal_title) - .skippable(true) - .add(new SWInfotext() - .label(R.string.nsclientinfotext)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.enable_nsclient) - .action(() -> { - NSClientPlugin.getPlugin().setPluginEnabled(PluginType.GENERAL, true); - NSClientPlugin.getPlugin().setFragmentVisible(PluginType.GENERAL, true); - ConfigBuilderFragment.processOnEnabledCategoryChanged(NSClientPlugin.getPlugin(), PluginType.GENERAL); - ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); - MainApp.bus().post(new EventConfigBuilderChange()); - MainApp.bus().post(new EventSWUpdate(true)); - }) - .visibility(() -> !NSClientPlugin.getPlugin().isEnabled(PluginType.GENERAL))) - .add(new SWEditUrl() - .preferenceId(R.string.key_nsclientinternal_url) - .updateDelay(5) - .label(R.string.nsclientinternal_url_title) - .comment(R.string.nsclientinternal_url_dialogmessage)) - .add(new SWEditString() - .validator(text -> text.length() >= 12) - .updateDelay(5) - .preferenceId(R.string.key_nsclientinternal_api_secret) - .label(R.string.nsclientinternal_secret_dialogtitle) - .comment(R.string.nsclientinternal_secret_dialogmessage)) - .add(new SWBreak()) - .add(new SWEventListener(this) - .label(R.string.status) - .initialStatus(NSClientPlugin.getPlugin().status) - .listener(new Object() { - @Subscribe - public void onEventNSClientStatus(EventNSClientStatus event) { - MainApp.bus().post(new EventSWLabel(event.status)); - } - }) - ) - .add(new SWBreak()) - .validator(() -> NSClientPlugin.getPlugin().nsClientService != null && NSClientPlugin.getPlugin().nsClientService.isConnected && NSClientPlugin.getPlugin().nsClientService.hasWriteAuth) - .visibility(() -> !(NSClientPlugin.getPlugin().nsClientService != null && NSClientPlugin.getPlugin().nsClientService.isConnected && NSClientPlugin.getPlugin().nsClientService.hasWriteAuth)) - ) - .add(new SWScreen(R.string.configbuilder_bgsource) - .skippable(false) - .add(new SWPlugin() - .option(PluginType.BGSOURCE, R.string.configbuilder_bgsource_description) - .label(R.string.configbuilder_bgsource)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.bgsourcesetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource(); - PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { - Intent i = new Intent(activity, PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - activity.startActivity(i); - }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource()!= null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveBgSource()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveBgSource() != null) - ) - .add(new SWScreen(R.string.patientage) - .skippable(false) - .add(new SWInfotext() - .label(R.string.patientage_summary)) - .add(new SWBreak()) - .add(new SWRadioButton() - .option(R.array.ageArray, R.array.ageValues) - .preferenceId(R.string.key_age) - .label(R.string.patientage) - .comment(R.string.patientage_summary)) - .validator(() -> SP.contains(R.string.key_age)) - ) - .add(new SWScreen(R.string.configbuilder_insulin) - .skippable(false) - .add(new SWPlugin() - .option(PluginType.INSULIN, R.string.configbuilder_insulin_description) - .makeVisible(false) - .label(R.string.configbuilder_insulin)) - .add(new SWBreak()) - .add(new SWInfotext() - .label(R.string.diawarning)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.insulinsourcesetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin(); - PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { - Intent i = new Intent(activity, PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - activity.startActivity(i); - }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin()!= null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveInsulin()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveInsulin() != null) - ) - .add(new SWScreen(R.string.configbuilder_sensitivity) - .skippable(false) - .add(new SWInfotext() - .label(R.string.setupwizard_sensitivity_description)) - .add(new SWHtmlLink() - .label(R.string.setupwizard_sensitivity_url)) - .add(new SWBreak()) - .add(new SWPlugin() - .option(PluginType.SENSITIVITY, R.string.configbuilder_sensitivity_description) - .label(R.string.configbuilder_sensitivity)) - .add(new SWBreak()) - .add(new SWButton() - .text(R.string.sensitivitysetup) - .action(() -> { - final PluginBase plugin = (PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity(); - PasswordProtection.QueryPassword(activity, R.string.settings_password, "settings_password", () -> { - Intent i = new Intent(activity, PreferencesActivity.class); - i.putExtra("id", plugin.getPreferencesId()); - activity.startActivity(i); - }, null); - }) - .visibility(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null && ((PluginBase) ConfigBuilderPlugin.getPlugin().getActiveSensitivity()).getPreferencesId() > 0)) - .validator(() -> ConfigBuilderPlugin.getPlugin().getActiveSensitivity() != null) - ) + add(screenSetupWizard) + .add(screenLanguage) + .add(screenEula) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization + .add(screenPermissionBt) + .add(screenPermissionStore) + .add(screenImport) + .add(screenUnits) + .add(displaySettings) + .add(screenNsClient) + .add(screenAge) + .add(screenInsulin) + .add(screenBgSource) + .add(screenProfile) + .add(screenNsProfile) + .add(screenLocalProfile) + .add(screenProfileSwitch) + .add(screenPump) + .add(screenSensitivity) ; } + private void SWDefinitionNSClient() { + // List all the screens here + add(screenSetupWizard) + .add(screenLanguage) + .add(screenEula) + .add(isRunningTest() ? null : screenPermissionBattery) // cannot mock ask battery optimalization + .add(screenPermissionStore) + .add(screenImport) + .add(screenUnits) + .add(displaySettings) + .add(screenNsClient) + .add(screenBgSource) + .add(screenAge) + .add(screenInsulin) + .add(screenSensitivity) + ; + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.java index 487a80d91c6..26944effbf1 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWEventListener.java @@ -1,33 +1,42 @@ package info.nightscout.androidaps.setupwizard; import android.content.Context; -import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import com.squareup.otto.Subscribe; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.events.EventStatus; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.setupwizard.elements.SWItem; -import info.nightscout.androidaps.setupwizard.events.EventSWLabel; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; public class SWEventListener extends SWItem { private static Logger log = LoggerFactory.getLogger(SWEventListener.class); + private CompositeDisposable disposable = new CompositeDisposable(); private int textLabel = 0; private String status = ""; TextView textView; - Object listener; SWDefinition definition; - SWEventListener(SWDefinition definition) { + SWEventListener(SWDefinition definition, Class clazz) { super(Type.LISTENER); this.definition = definition; - MainApp.bus().register(this); + disposable.add(RxBus.INSTANCE + .toObservable(clazz) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + status = ((EventStatus) event).getStatus(); + if (textView != null) + textView.setText((textLabel != 0 ? MainApp.gs(textLabel) : "") + " " + status); + }) + ); + } public SWEventListener label(int newLabel) { @@ -35,16 +44,11 @@ public SWEventListener label(int newLabel) { return this; } - public SWEventListener initialStatus(String status) { + SWEventListener initialStatus(String status) { this.status = status; return this; } - public SWEventListener listener(Object listener) { - this.listener = listener; - return this; - } - @Override public void generateDialog(LinearLayout layout) { Context context = layout.getContext(); @@ -53,20 +57,5 @@ public void generateDialog(LinearLayout layout) { textView.setId(layout.generateViewId()); textView.setText((textLabel != 0 ? MainApp.gs(textLabel) : "") + " " + status); layout.addView(textView); - if (listener != null) - try { - MainApp.bus().register(listener); - } catch (Exception ignored) {} - } - - @Subscribe - public void onEventSWLabel(final EventSWLabel l) { - status = l.label; - if (definition != null && definition.getActivity() != null) - definition.getActivity().runOnUiThread(() -> { - if (textView != null) - textView.setText((textLabel != 0 ? MainApp.gs(textLabel) : "") + " " + status); - }); } - } diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWNumberValidator.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWNumberValidator.java new file mode 100644 index 00000000000..a71da1dea78 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWNumberValidator.java @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.setupwizard; + +public interface SWNumberValidator { + boolean isValid(double value); +} diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java index 9049ad74637..b74567d0271 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SetupWizardActivity.java @@ -3,39 +3,38 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; -import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; -import com.squareup.otto.Subscribe; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import java.util.List; import info.nightscout.androidaps.MainActivity; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventProfileStoreChanged; +import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.events.EventProfileNeedsUpdate; +import info.nightscout.androidaps.events.EventProfileStoreChanged; import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesSaved; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus; import info.nightscout.androidaps.setupwizard.elements.SWItem; import info.nightscout.androidaps.setupwizard.events.EventSWUpdate; import info.nightscout.androidaps.utils.AndroidPermission; +import info.nightscout.androidaps.utils.FabricPrivacy; import info.nightscout.androidaps.utils.LocaleHelper; import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.SP; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; -public class SetupWizardActivity extends AppCompatActivity { - //logging - private static Logger log = LoggerFactory.getLogger(SetupWizardActivity.class); +public class SetupWizardActivity extends NoSplashAppCompatActivity { + private CompositeDisposable disposable = new CompositeDisposable(); ScrollView scrollView; @@ -45,12 +44,12 @@ public class SetupWizardActivity extends AppCompatActivity { public static final String INTENT_MESSAGE = "WIZZARDPAGE"; @Override - protected void onCreate(Bundle savedInstanceState) { + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - LocaleHelper.onCreate(this, "en"); + LocaleHelper.INSTANCE.update(getApplicationContext()); setContentView(R.layout.activity_setupwizard); - scrollView = (ScrollView) findViewById(R.id.sw_scrollview); + scrollView = findViewById(R.id.sw_scrollview); Intent intent = getIntent(); currentWizardPage = intent.getIntExtra(SetupWizardActivity.INTENT_MESSAGE, 0); @@ -58,7 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { SWScreen currentScreen = screens.get(currentWizardPage); //Set screen name - TextView screenName = (TextView) findViewById(R.id.sw_content); + TextView screenName = findViewById(R.id.sw_content); screenName.setText(currentScreen.getHeader()); swDefinition.setActivity(this); @@ -68,60 +67,44 @@ protected void onCreate(Bundle savedInstanceState) { } } - @Override - public void onBackPressed() { - if (currentWizardPage == 0) OKDialog.showConfirmation(this, MainApp.gs(R.string.exitwizard), this::finish); - else showPreviousPage(null); - } - - public void exitPressed(View view) { - SP.putBoolean(R.string.key_setupwizard_processed, true); - OKDialog.showConfirmation(this, MainApp.gs(R.string.exitwizard), this::finish); - } - @Override public void onPause() { super.onPause(); - MainApp.bus().unregister(this); + disposable.clear(); } @Override protected void onResume() { super.onResume(); - MainApp.bus().register(this); swDefinition.setActivity(this); - } - - @Subscribe - public void onContentUpdate(EventSWUpdate ev) { - if (ev.redraw) - generateLayout(); - updateButtons(); - } - - @Subscribe - public void onEventNSClientStatus(EventNSClientStatus ignored) { - updateButtons(); - } - - @Subscribe - public void onEventPumpStatusChanged(EventPumpStatusChanged ignored) { - updateButtons(); - } - - @Subscribe - public void onEventProfileStoreChanged(EventProfileStoreChanged ignored) { - updateButtons(); - } - - @Subscribe - public void onEventProfileSwitchChange(EventProfileNeedsUpdate ignored) { - updateButtons(); - } - - @Subscribe - public void onEventObjectivesSaved(EventObjectivesSaved ignored) { - updateButtons(); + disposable.add(RxBus.INSTANCE + .toObservable(EventPumpStatusChanged.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateButtons(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventNSClientStatus.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateButtons(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventProfileNeedsUpdate.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateButtons(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventProfileStoreChanged.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> updateButtons(), FabricPrivacy::logException) + ); + disposable.add(RxBus.INSTANCE + .toObservable(EventSWUpdate.class) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(event -> { + if (event.getRedraw()) generateLayout(); + updateButtons(); + }, FabricPrivacy::logException) + ); } private void generateLayout() { @@ -131,7 +114,7 @@ private void generateLayout() { SWItem currentItem = currentScreen.items.get(i); currentItem.generateDialog(layout); } - scrollView.smoothScrollTo(0,0); + scrollView.smoothScrollTo(0, 0); } private void updateButtons() { @@ -157,6 +140,18 @@ private void updateButtons() { }); } + @Override + public void onBackPressed() { + if (currentWizardPage == 0) + OKDialog.showConfirmation(this, MainApp.gs(R.string.exitwizard), this::finish); + else showPreviousPage(null); + } + + public void exitPressed(View view) { + SP.putBoolean(R.string.key_setupwizard_processed, true); + OKDialog.showConfirmation(this, MainApp.gs(R.string.exitwizard), this::finish); + } + public void showNextPage(View view) { this.finish(); Intent intent = new Intent(this, SetupWizardActivity.class); @@ -187,7 +182,7 @@ private int nextPage() { return page; page++; } - return currentWizardPage; + return Math.min(currentWizardPage, screens.size() - 1); } private int previousPage() { @@ -197,21 +192,18 @@ private int previousPage() { return page; page--; } - return currentWizardPage; + return Math.max(currentWizardPage, 0); } @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (permissions.length != 0) { if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { switch (requestCode) { case AndroidPermission.CASE_STORAGE: //show dialog after permission is granted - AlertDialog.Builder alert = new AlertDialog.Builder(this); - alert.setMessage(R.string.alert_dialog_storage_permission_text); - alert.setPositiveButton(R.string.ok, null); - alert.show(); + OKDialog.show(this, MainApp.gs(R.string.permission), MainApp.gs(R.string.alert_dialog_storage_permission_text)); break; case AndroidPermission.CASE_LOCATION: case AndroidPermission.CASE_SMS: @@ -223,4 +215,10 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in updateButtons(); } + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == AndroidPermission.CASE_BATTERY) + updateButtons(); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditNumberWithUnits.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditNumberWithUnits.java new file mode 100644 index 00000000000..6816b6fa1f4 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditNumberWithUnits.java @@ -0,0 +1,98 @@ +package info.nightscout.androidaps.setupwizard.elements; + +import android.content.Context; +import android.graphics.Typeface; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.text.DecimalFormat; + +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.setupwizard.SWNumberValidator; +import info.nightscout.androidaps.utils.NumberPicker; +import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.utils.SafeParse; + + +public class SWEditNumberWithUnits extends SWItem { + + private SWNumberValidator validator = new SWNumberValidator() { + @Override + public boolean isValid(double value) { + return value >= min && value <= max; + } + }; + private int updateDelay = 0; + private double init, min, max; + + public SWEditNumberWithUnits(double defaultMMOL, double minMMOL, double maxMMOL) { + super(Type.UNITNUMBER); + init = defaultMMOL; + min = minMMOL; + max = maxMMOL; + } + + @Override + public void generateDialog(LinearLayout layout) { + Context context = layout.getContext(); + + TextWatcher watcher = new TextWatcher() { + @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 (validator != null && validator.isValid(SafeParse.stringToDouble(s.toString()))) + save(s.toString(), updateDelay); + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + + TextView l = new TextView(context); + l.setId(View.generateViewId()); + l.setText(label); + l.setTypeface(l.getTypeface(), Typeface.BOLD); + layout.addView(l); + + double initValue = SP.getDouble(preferenceId, init); + initValue = Profile.toCurrentUnits(initValue); + + NumberPicker numberPicker = new NumberPicker(context); + if (ProfileFunctions.getSystemUnits().equals(Constants.MMOL)) + numberPicker.setParams(initValue, min, max, 0.1d, new DecimalFormat("0.0"), false, null, watcher); + else + numberPicker.setParams(initValue, min * 18, max * 18, 1d, new DecimalFormat("0"), false, null, watcher); + +// LinearLayout.LayoutParams ll = (LinearLayout.LayoutParams) numberPicker.getLayoutParams(); +// ll.gravity = Gravity.CENTER; +// numberPicker.setLayoutParams(ll); + layout.addView(numberPicker); + + TextView c = new TextView(context); + c.setId(View.generateViewId()); + c.setText(comment); + c.setTypeface(c.getTypeface(), Typeface.ITALIC); + layout.addView(c); + + super.generateDialog(layout); + } + + public SWEditNumberWithUnits preferenceId(int preferenceId) { + this.preferenceId = preferenceId; + return this; + } + + public SWEditNumberWithUnits updateDelay(int updateDelay) { + this.updateDelay = updateDelay; + return this; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditString.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditString.java index 50e851ed6b1..b16164c0649 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditString.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditString.java @@ -5,6 +5,7 @@ import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; +import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; @@ -31,19 +32,19 @@ public void generateDialog(LinearLayout layout) { Context context = layout.getContext(); TextView l = new TextView(context); - l.setId(layout.generateViewId()); + l.setId(View.generateViewId()); l.setText(label); l.setTypeface(l.getTypeface(), Typeface.BOLD); layout.addView(l); TextView c = new TextView(context); - c.setId(layout.generateViewId()); + c.setId(View.generateViewId()); c.setText(comment); c.setTypeface(c.getTypeface(), Typeface.ITALIC); layout.addView(c); EditText editText = new EditText(context); - editText.setId(layout.generateViewId()); + editText.setId(View.generateViewId()); editText.setInputType(InputType.TYPE_CLASS_TEXT); editText.setMaxLines(1); editText.setText(SP.getString(preferenceId, "")); diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditUrl.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditUrl.java index 7e6ead7d724..f35decbf53b 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditUrl.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWEditUrl.java @@ -16,6 +16,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.setupwizard.events.EventSWLabel; import info.nightscout.androidaps.utils.SP; @@ -62,7 +63,7 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { if (Patterns.WEB_URL.matcher(s).matches()) save(s.toString(), updateDelay); else - MainApp.bus().post(new EventSWLabel(MainApp.gs(R.string.error_url_not_valid))); + RxBus.INSTANCE.send(new EventSWLabel(MainApp.gs(R.string.error_url_not_valid))); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java index a061f57eb64..29149e5b81d 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.java @@ -1,7 +1,7 @@ package info.nightscout.androidaps.setupwizard.elements; -import android.support.v4.app.Fragment; -import android.view.View; +import androidx.fragment.app.Fragment; + import android.widget.LinearLayout; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.java index 4e8113c73c0..734b204ff33 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWItem.java @@ -3,6 +3,8 @@ import android.view.View; import android.widget.LinearLayout; +import androidx.annotation.StringRes; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,6 +16,7 @@ import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.setupwizard.events.EventSWUpdate; import info.nightscout.androidaps.utils.SP; @@ -37,7 +40,8 @@ public enum Type { RADIOBUTTON, PLUGIN, BUTTON, - FRAGMENT + FRAGMENT, + UNITNUMBER } Type type; @@ -65,12 +69,12 @@ Type getType() { return type; } - public SWItem label(int label) { + public SWItem label(@StringRes int label) { this.label = label; return this; } - public SWItem comment(int comment) { + public SWItem comment(@StringRes int comment) { this.comment = comment; return this; } @@ -97,8 +101,8 @@ class PostRunnable implements Runnable { public void run() { if (L.isEnabled(L.CORE)) log.debug("Firing EventPreferenceChange"); - MainApp.bus().post(new EventPreferenceChange(preferenceId)); - MainApp.bus().post(new EventSWUpdate()); + RxBus.INSTANCE.send(new EventPreferenceChange(preferenceId)); + RxBus.INSTANCE.send(new EventSWUpdate(false)); scheduledEventPost = null; } } diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWPlugin.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWPlugin.java index 060102ba9c1..e5dde501bc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWPlugin.java @@ -17,7 +17,7 @@ import info.nightscout.androidaps.events.EventConfigBuilderChange; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderFragment; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.setupwizard.events.EventSWUpdate; @@ -86,10 +86,10 @@ public void generateDialog(LinearLayout layout) { PluginBase plugin = (PluginBase) rb.getTag(); plugin.setPluginEnabled(pType, rb.isChecked()); plugin.setFragmentVisible(pType, rb.isChecked() && makeVisible); - ConfigBuilderFragment.processOnEnabledCategoryChanged(plugin, pType); + ConfigBuilderPlugin.getPlugin().processOnEnabledCategoryChanged(plugin, pType); ConfigBuilderPlugin.getPlugin().storeSettings("SetupWizard"); - MainApp.bus().post(new EventConfigBuilderChange()); - MainApp.bus().post(new EventSWUpdate()); + RxBus.INSTANCE.send(new EventConfigBuilderChange()); + RxBus.INSTANCE.send(new EventSWUpdate(false)); }); layout.addView(radioGroup); super.generateDialog(layout); diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.java b/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.java deleted file mode 100644 index 181960ac400..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.setupwizard.events; - -import info.nightscout.androidaps.events.Event; - -public class EventSWUpdate extends Event { - public boolean redraw = false; - - public EventSWUpdate() { - } - - public EventSWUpdate(boolean redraw) { - this.redraw = redraw; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.kt new file mode 100644 index 00000000000..3c700cda415 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/events/EventSWUpdate.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.setupwizard.events + +import info.nightscout.androidaps.events.Event + +class EventSWUpdate(var redraw: Boolean) : 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 53b3700c726..c567df7b499 100644 --- a/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java +++ b/app/src/main/java/info/nightscout/androidaps/tabs/TabPageAdapter.java @@ -1,10 +1,10 @@ package info.nightscout.androidaps.tabs; import android.content.Context; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentPagerAdapter; import android.view.ViewGroup; import org.slf4j.Logger; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt new file mode 100644 index 00000000000..8cf59223925 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/ActivityMonitor.kt @@ -0,0 +1,79 @@ +package info.nightscout.androidaps.utils + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.text.Spanned +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.logging.L +import org.slf4j.LoggerFactory + +object ActivityMonitor : Application.ActivityLifecycleCallbacks { + private val log = LoggerFactory.getLogger(L.CORE) + override fun onActivityPaused(activity: Activity?) { + val name = activity?.javaClass?.simpleName ?: return + val resumed = SP.getLong("Monitor_" + name + "_" + "resumed", 0) + if (resumed == 0L) { + log.debug("onActivityPaused: $name resumed == 0") + return + } + val elapsed = DateUtil.now() - resumed + val total = SP.getLong("Monitor_" + name + "_total", 0) + if (total == 0L) { + SP.putLong("Monitor_" + name + "_start", DateUtil.now()) + } + SP.putLong("Monitor_" + name + "_total", total + elapsed) + log.debug("onActivityPaused: $name elapsed=$elapsed total=${total + elapsed}") + } + + override fun onActivityResumed(activity: Activity?) { + val name = activity?.javaClass?.simpleName ?: return + log.debug("onActivityResumed: $name") + SP.putLong("Monitor_" + name + "_" + "resumed", DateUtil.now()) + } + + override fun onActivityStarted(activity: Activity?) { + } + + override fun onActivityDestroyed(activity: Activity?) { + } + + override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) { + } + + override fun onActivityStopped(activity: Activity?) { + } + + override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { + } + + fun toText(): String { + val keys: Map = SP.getAll() + var result = "" + for ((key, value) in keys) + if (key.startsWith("Monitor") && key.endsWith("total")) { + val v = if (value is Long) value else SafeParse.stringToLong(value as String) + val activity = key.split("_")[1].replace("Activity", "") + val duration = DateUtil.niceTimeScalar(v as Long) + val start = SP.getLong(key.replace("total", "start"), 0) + val days = T.msecs(DateUtil.now() - start).days() + result += "$activity: $duration in $days days
" + } + return result + } + + fun stats(): Spanned { + return HtmlHelper.fromHtml("
" + MainApp.gs(R.string.activitymonitor) + ":
" + toText()) + } + + fun reset() { + val keys: Map = SP.getAll() + for ((key, _) in keys) + if (key.startsWith("Monitor") && key.endsWith("total")) { + SP.remove(key) + SP.remove(key.replace("total", "start")) + SP.remove(key.replace("total", "resumed")) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java index 3f6c3a3ce06..41e26e8e3b1 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java @@ -1,19 +1,29 @@ package info.nightscout.androidaps.utils; import android.Manifest; +import android.annotation.SuppressLint; import android.app.Activity; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; +import android.os.PowerManager; +import android.provider.Settings; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction; +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; public class AndroidPermission { @@ -21,85 +31,122 @@ public class AndroidPermission { public static final int CASE_SMS = 0x2; public static final int CASE_LOCATION = 0x3; public static final int CASE_BATTERY = 0x4; - public static final int CASE_PHONESTATE = 0x5; + public static final int CASE_PHONE_STATE = 0x5; + public static final int CASE_SYSTEM_WINDOW = 0x6; + + private static boolean permission_battery_optimization_failed = false; - public static void askForPermission(Activity activity, String[] permission, Integer requestCode) { + @SuppressLint("BatteryLife") + private static void askForPermission(Activity activity, String[] permission, Integer requestCode) { boolean test = false; - for (int i = 0; i < permission.length; i++) { - test = test || (ContextCompat.checkSelfPermission(activity, permission[i]) != PackageManager.PERMISSION_GRANTED); + boolean testBattery = false; + for (String s : permission) { + test = test || (ContextCompat.checkSelfPermission(activity, s) != PackageManager.PERMISSION_GRANTED); + if (s.equals(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { + PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE); + String packageName = activity.getPackageName(); + testBattery = testBattery || !powerManager.isIgnoringBatteryOptimizations(packageName); + } } if (test) { ActivityCompat.requestPermissions(activity, permission, requestCode); } + if (testBattery) { + try { + Intent i = new Intent(); + i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + i.setData(Uri.parse("package:" + activity.getPackageName())); + activity.startActivityForResult(i, CASE_BATTERY); + } catch (ActivityNotFoundException e) { + permission_battery_optimization_failed = true; + OKDialog.show(activity, MainApp.gs(R.string.permission), MainApp.gs(R.string.alert_dialog_permission_battery_optimization_failed), activity::recreate); + } + } } public static void askForPermission(Activity activity, String permission, Integer requestCode) { String[] permissions = {permission}; - - if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) - ActivityCompat.requestPermissions(activity, permissions, requestCode); + askForPermission(activity, permissions, requestCode); } - public static boolean checkForPermission(Context context, String permission) { - return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; + public static boolean permissionNotGranted(Context context, String permission) { + boolean selfCheck = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; + if (permission.equals(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { + if (!permission_battery_optimization_failed) { + PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + String packageName = context.getPackageName(); + selfCheck = selfCheck && powerManager.isIgnoringBatteryOptimizations(packageName); + } + } + return !selfCheck; } public static synchronized void notifyForSMSPermissions(Activity activity) { - if (SP.getBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) { - if (!checkForPermission(activity, Manifest.permission.RECEIVE_SMS)) { - NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_SMS, MainApp.gs(R.string.smscommunicator_missingsmspermission), Notification.URGENT); - notification.action(MainApp.gs(R.string.request), () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.RECEIVE_SMS, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_MMS}, AndroidPermission.CASE_SMS)); - MainApp.bus().post(new EventNewNotification(notification)); - } else - MainApp.bus().post(new EventDismissNotification(Notification.PERMISSION_SMS)); - } + if (SmsCommunicatorPlugin.INSTANCE.isEnabled(PluginType.GENERAL)) { + if (permissionNotGranted(activity, Manifest.permission.RECEIVE_SMS)) { + NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_SMS, MainApp.gs(R.string.smscommunicator_missingsmspermission), Notification.URGENT); + notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.RECEIVE_SMS, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_MMS}, AndroidPermission.CASE_SMS)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_SMS)); // Following is a bug in Android 8 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { - if (!checkForPermission(activity, Manifest.permission.READ_PHONE_STATE)) { + if (permissionNotGranted(activity, Manifest.permission.READ_PHONE_STATE)) { NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_PHONESTATE, MainApp.gs(R.string.smscommunicator_missingphonestatepermission), Notification.URGENT); - notification.action(MainApp.gs(R.string.request), () -> - AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, AndroidPermission.CASE_PHONESTATE)); - MainApp.bus().post(new EventNewNotification(notification)); + notification.action(R.string.request, () -> + AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, AndroidPermission.CASE_PHONE_STATE)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else - MainApp.bus().post(new EventDismissNotification(Notification.PERMISSION_PHONESTATE)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_PHONESTATE)); } } } public static synchronized void notifyForBatteryOptimizationPermission(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!checkForPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_BATTERY, String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)), Notification.URGENT); - notification.action(MainApp.gs(R.string.request), () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}, AndroidPermission.CASE_BATTERY)); - MainApp.bus().post(new EventNewNotification(notification)); - } else - MainApp.bus().post(new EventDismissNotification(Notification.PERMISSION_BATTERY)); - } + if (permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { + NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_BATTERY, String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)), Notification.URGENT); + notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}, AndroidPermission.CASE_BATTERY)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_BATTERY)); } public static synchronized void notifyForStoragePermission(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!checkForPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_STORAGE, MainApp.gs(R.string.needstoragepermission), Notification.URGENT); - notification.action(MainApp.gs(R.string.request), () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, AndroidPermission.CASE_STORAGE)); - MainApp.bus().post(new EventNewNotification(notification)); - } else - MainApp.bus().post(new EventDismissNotification(Notification.PERMISSION_STORAGE)); - } + if (permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_STORAGE, MainApp.gs(R.string.needstoragepermission), Notification.URGENT); + notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}, AndroidPermission.CASE_STORAGE)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_STORAGE)); } public static synchronized void notifyForLocationPermissions(Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!checkForPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION)) { - NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_LOCATION, MainApp.gs(R.string.needlocationpermission), Notification.URGENT); - notification.action(MainApp.gs(R.string.request), () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, AndroidPermission.CASE_LOCATION)); - MainApp.bus().post(new EventNewNotification(notification)); + if (permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION)) { + NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_LOCATION, MainApp.gs(R.string.needlocationpermission), Notification.URGENT); + notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, AndroidPermission.CASE_LOCATION)); + RxBus.INSTANCE.send(new EventNewNotification(notification)); + } else + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_LOCATION)); + } + + public static synchronized void notifyForSystemWindowPermissions(Activity activity) { + // Check if Android Q or higher + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + if (!Settings.canDrawOverlays(activity)) { + NotificationWithAction notification = new NotificationWithAction(Notification.PERMISSION_SYSTEM_WINDOW, MainApp.gs(R.string.needsystemwindowpermission), Notification.URGENT); + notification.action(R.string.request, () -> { + // Show alert dialog to the user saying a separate permission is needed + // Launch the settings activity if the user prefers + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + activity.getPackageName())); + activity.startActivity(intent); + }); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } else - MainApp.bus().post(new EventDismissNotification(Notification.PERMISSION_LOCATION)); + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PERMISSION_SYSTEM_WINDOW)); } } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java b/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java deleted file mode 100644 index cd789ee225e..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.java +++ /dev/null @@ -1,183 +0,0 @@ -package info.nightscout.androidaps.utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.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.logging.L; -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; - -/** - * Created by mike on 11.10.2016. - */ - -public class BolusWizard { - private Logger log = LoggerFactory.getLogger(L.CORE); - // Inputs - private Profile specificProfile = null; - private TempTarget tempTarget; - public Integer carbs = 0; - private Double bg = 0d; - private Double cob = 0d; - private Double correction; - private Double percentageCorrection; - private Boolean includeBolusIOB = true; - private Boolean includeBasalIOB = true; - public Boolean superBolus = false; - private Boolean trend = false; - - // Intermediate - public double sens = 0d; - public double ic = 0d; - - public GlucoseStatus glucoseStatus; - - public double targetBGLow = 0d; - public double targetBGHigh = 0d; - public double bgDiff = 0d; - - public double insulinFromBG = 0d; - public double insulinFromCarbs = 0d; - public double insulingFromBolusIOB = 0d; - public double insulingFromBasalsIOB = 0d; - public double insulinFromCorrection = 0d; - public double insulinFromSuperBolus = 0d; - public double insulinFromCOB = 0d; - public double insulinFromTrend = 0d; - - // Result - public Double calculatedTotalInsulin = 0d; - public Double totalBeforePercentageAdjustment = 0d; - public Double carbsEquivalent = 0d; - - public Double doCalc(Profile specificProfile, TempTarget tempTarget, Integer carbs, Double cob, Double bg, Double correction, Boolean includeBolusIOB, Boolean includeBasalIOB, Boolean superBolus, Boolean trend) { - return doCalc(specificProfile, tempTarget, carbs, cob, bg, correction, 100d, includeBolusIOB, includeBasalIOB, superBolus, trend); - } - - public Double doCalc(Profile specificProfile, TempTarget tempTarget, Integer carbs, Double cob, Double bg, Double correction, double percentageCorrection, Boolean includeBolusIOB, Boolean includeBasalIOB, Boolean superBolus, Boolean trend) { - this.specificProfile = specificProfile; - this.tempTarget = tempTarget; - this.carbs = carbs; - this.bg = bg; - this.cob = cob; - this.correction = correction; - this.percentageCorrection = percentageCorrection; - this.includeBolusIOB = includeBolusIOB; - this.includeBasalIOB = includeBasalIOB; - this.superBolus = superBolus; - this.trend = trend; - - // Insulin from BG - sens = specificProfile.getIsf(); - targetBGLow = specificProfile.getTargetLow(); - targetBGHigh = specificProfile.getTargetHigh(); - if (tempTarget != null) { - targetBGLow = Profile.fromMgdlToUnits(tempTarget.low, specificProfile.getUnits()); - targetBGHigh = Profile.fromMgdlToUnits(tempTarget.high, specificProfile.getUnits()); - } - if (bg >= targetBGLow && bg <= targetBGHigh) { - bgDiff = 0d; - } else if (bg <= targetBGLow) { - bgDiff = bg - targetBGLow; - } else { - bgDiff = bg - targetBGHigh; - } - insulinFromBG = bg != 0d ? bgDiff / sens : 0d; - - // Insulin from 15 min trend - glucoseStatus = GlucoseStatus.getGlucoseStatusData(); - if (glucoseStatus != null && trend) { - insulinFromTrend = (Profile.fromMgdlToUnits(glucoseStatus.short_avgdelta, specificProfile.getUnits()) * 3) / sens; - } - - // Insuling from carbs - ic = specificProfile.getIc(); - insulinFromCarbs = carbs / ic; - insulinFromCOB = cob / ic; - - // Insulin from IOB - // IOB calculation - TreatmentsInterface treatments = TreatmentsPlugin.getPlugin(); - treatments.updateTotalIOBTreatments(); - IobTotal bolusIob = treatments.getLastCalculationTreatments().round(); - treatments.updateTotalIOBTempBasals(); - IobTotal basalIob = treatments.getLastCalculationTempBasals().round(); - - insulingFromBolusIOB = includeBolusIOB ? -bolusIob.iob : 0d; - insulingFromBasalsIOB = includeBasalIOB ? -basalIob.basaliob : 0d; - - // Insulin from correction - insulinFromCorrection = correction; - - // Insulin from superbolus for 2h. Get basal rate now and after 1h - if (superBolus) { - insulinFromSuperBolus = specificProfile.getBasal(); - long timeAfter1h = System.currentTimeMillis(); - timeAfter1h += T.hours(1).msecs(); - insulinFromSuperBolus += specificProfile.getBasal(timeAfter1h); - } - - // Total - calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulingFromBolusIOB + insulingFromBasalsIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB; - - // Percentage adjustment - totalBeforePercentageAdjustment = calculatedTotalInsulin; - if (calculatedTotalInsulin > 0) { - calculatedTotalInsulin = calculatedTotalInsulin * percentageCorrection / 100d; - } - - if (calculatedTotalInsulin < 0) { - carbsEquivalent = -calculatedTotalInsulin * ic; - calculatedTotalInsulin = 0d; - } - - double bolusStep = ConfigBuilderPlugin.getPlugin().getActivePump().getPumpDescription().bolusStep; - calculatedTotalInsulin = Round.roundTo(calculatedTotalInsulin, bolusStep); - - log.debug(log()); - - return calculatedTotalInsulin; - } - - public String log() { - StringBuilder sb = new StringBuilder(); - - sb.append("TempTarget=").append(tempTarget != null ? tempTarget.toString() : "null").append("; "); - sb.append("Carbs=").append(carbs != null ? carbs : null).append("; "); - sb.append("Bg=").append(bg).append("; "); - sb.append("Cob=").append(cob).append("; "); - sb.append("Correction=").append(correction).append("; "); - sb.append("PercentageCorrection=").append(percentageCorrection).append("; "); - sb.append("IncludeBolusIOB=").append(includeBolusIOB).append("; "); - sb.append("IncludeBasalIOB=").append(includeBasalIOB).append("; "); - sb.append("Superbolus=").append(superBolus).append("; "); - sb.append("Trend=").append(trend).append("; "); - sb.append("Profile=").append(specificProfile != null && specificProfile.getData() != null ? specificProfile.getData().toString() : "null").append("; "); - sb.append("\n"); - - sb.append("targetBGLow=").append(targetBGLow).append("; "); - sb.append("targetBGHigh=").append(targetBGHigh).append("; "); - sb.append("bgDiff=").append(bgDiff).append("; "); - sb.append("insulinFromBG=").append(insulinFromBG).append("; "); - sb.append("insulinFromCarbs=").append(insulinFromCarbs).append("; "); - sb.append("insulingFromBolusIOB=").append(insulingFromBolusIOB).append("; "); - sb.append("insulingFromBasalsIOB=").append(insulingFromBasalsIOB).append("; "); - sb.append("insulinFromCorrection=").append(insulinFromCorrection).append("; "); - sb.append("insulinFromSuperBolus=").append(insulinFromSuperBolus).append("; "); - sb.append("insulinFromCOB=").append(insulinFromCOB).append("; "); - sb.append("insulinFromTrend=").append(insulinFromTrend).append("; "); - sb.append("\n"); - - - sb.append("calculatedTotalInsulin=").append(calculatedTotalInsulin).append("; "); - sb.append("totalBeforePercentageAdjustment=").append(totalBeforePercentageAdjustment).append("; "); - sb.append("carbsEquivalent=").append(carbsEquivalent).append("; "); - - return sb.toString(); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.kt b/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.kt new file mode 100644 index 00000000000..ea4da238cbc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/BolusWizard.kt @@ -0,0 +1,350 @@ +package info.nightscout.androidaps.utils + +import android.content.Context +import android.content.Intent +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.ErrorHelperActivity +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.events.EventRefreshOverview +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.logging.L +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.queue.Callback +import org.json.JSONException +import org.json.JSONObject +import org.slf4j.LoggerFactory +import java.util.* +import kotlin.math.abs + +class BolusWizard @JvmOverloads constructor(val profile: Profile, + val profileName: String, + val tempTarget: TempTarget?, + val carbs: Int, + val cob: Double, + val bg: Double, + val correction: Double, + private val percentageCorrection: Double = 100.0, + private val useBg: Boolean, + private val useCob: Boolean, + private val includeBolusIOB: Boolean, + private val includeBasalIOB: Boolean, + private val useSuperBolus: Boolean, + private val useTT: Boolean, + private val useTrend: Boolean, + val notes: String = "", + private val carbTime: Int = 0 +) { + + private val log = LoggerFactory.getLogger(L.CORE) + + // Intermediate + var sens = 0.0 + private set + + var ic = 0.0 + private set + + var glucoseStatus: GlucoseStatus? = null + private set + + private var targetBGLow = 0.0 + + private var targetBGHigh = 0.0 + + private var bgDiff = 0.0 + + var insulinFromBG = 0.0 + private set + + var insulinFromCarbs = 0.0 + private set + + var insulinFromBolusIOB = 0.0 + private set + + var insulinFromBasalsIOB = 0.0 + private set + + var insulinFromCorrection = 0.0 + private set + + var insulinFromSuperBolus = 0.0 + private set + + var insulinFromCOB = 0.0 + private set + + var insulinFromTrend = 0.0 + private set + + var trend = 0.0 + private set + + private var accepted = false + + // Result + var calculatedTotalInsulin: Double = 0.0 + private set + + var totalBeforePercentageAdjustment: Double = 0.0 + private set + + var carbsEquivalent: Double = 0.0 + private set + + var insulinAfterConstraints: Double = 0.0 + private set + + init { + doCalc() + } + + private fun doCalc() { + + // Insulin from BG + sens = Profile.fromMgdlToUnits(profile.isfMgdl, ProfileFunctions.getSystemUnits()) + targetBGLow = Profile.fromMgdlToUnits(profile.targetLowMgdl, ProfileFunctions.getSystemUnits()) + targetBGHigh = Profile.fromMgdlToUnits(profile.targetHighMgdl, ProfileFunctions.getSystemUnits()) + if (useTT && tempTarget != null) { + targetBGLow = Profile.fromMgdlToUnits(tempTarget.low, ProfileFunctions.getSystemUnits()) + targetBGHigh = Profile.fromMgdlToUnits(tempTarget.high, ProfileFunctions.getSystemUnits()) + } + if (useBg && bg > 0) { + bgDiff = when { + bg in targetBGLow..targetBGHigh -> 0.0 + bg <= targetBGLow -> bg - targetBGLow + else -> bg - targetBGHigh + } + insulinFromBG = bgDiff / sens + } + + // Insulin from 15 min trend + glucoseStatus = GlucoseStatus.getGlucoseStatusData() + glucoseStatus?.let { + if (useTrend) { + trend = it.short_avgdelta + insulinFromTrend = Profile.fromMgdlToUnits(trend, ProfileFunctions.getSystemUnits()) * 3 / sens + } + } + + // Insulin from carbs + ic = profile.ic + insulinFromCarbs = carbs / ic + insulinFromCOB = if (useCob) (cob / ic) else 0.0 + + // Insulin from IOB + // IOB calculation + val treatments = TreatmentsPlugin.getPlugin() + treatments.updateTotalIOBTreatments() + val bolusIob = treatments.lastCalculationTreatments.round() + treatments.updateTotalIOBTempBasals() + val basalIob = treatments.lastCalculationTempBasals.round() + + insulinFromBolusIOB = if (includeBolusIOB) -bolusIob.iob else 0.0 + insulinFromBasalsIOB = if (includeBasalIOB) -basalIob.basaliob else 0.0 + + // Insulin from correction + insulinFromCorrection = correction + + // Insulin from superbolus for 2h. Get basal rate now and after 1h + if (useSuperBolus) { + insulinFromSuperBolus = profile.basal + var timeAfter1h = System.currentTimeMillis() + timeAfter1h += T.hours(1).msecs() + insulinFromSuperBolus += profile.getBasal(timeAfter1h) + } + + // Total + calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulinFromBolusIOB + insulinFromBasalsIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB + + // Percentage adjustment + totalBeforePercentageAdjustment = calculatedTotalInsulin + if (calculatedTotalInsulin > 0) { + calculatedTotalInsulin = calculatedTotalInsulin * percentageCorrection / 100.0 + } + + if (calculatedTotalInsulin < 0) { + carbsEquivalent = (-calculatedTotalInsulin) * ic + calculatedTotalInsulin = 0.0 + } + + val bolusStep = ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.bolusStep + ?: 0.1 + calculatedTotalInsulin = Round.roundTo(calculatedTotalInsulin, bolusStep) + + insulinAfterConstraints = MainApp.getConstraintChecker().applyBolusConstraints(Constraint(calculatedTotalInsulin)).value() + + log.debug(this.toString()) + } + + private fun nsJSON(): JSONObject { + val boluscalcJSON = JSONObject() + try { + boluscalcJSON.put("profile", profileName) + boluscalcJSON.put("notes", notes) + boluscalcJSON.put("eventTime", DateUtil.toISOString(Date())) + boluscalcJSON.put("targetBGLow", targetBGLow) + boluscalcJSON.put("targetBGHigh", targetBGHigh) + boluscalcJSON.put("isf", sens) + boluscalcJSON.put("ic", ic) + boluscalcJSON.put("iob", -(insulinFromBolusIOB + insulinFromBasalsIOB)) + boluscalcJSON.put("bolusiob", insulinFromBolusIOB) + boluscalcJSON.put("basaliob", insulinFromBasalsIOB) + boluscalcJSON.put("bolusiobused", includeBolusIOB) + boluscalcJSON.put("basaliobused", includeBasalIOB) + boluscalcJSON.put("bg", bg) + boluscalcJSON.put("insulinbg", insulinFromBG) + boluscalcJSON.put("insulinbgused", useBg) + boluscalcJSON.put("bgdiff", bgDiff) + boluscalcJSON.put("insulincarbs", insulinFromCarbs) + boluscalcJSON.put("carbs", carbs) + boluscalcJSON.put("cob", cob) + boluscalcJSON.put("cobused", useCob) + boluscalcJSON.put("insulincob", insulinFromCOB) + boluscalcJSON.put("othercorrection", correction) + boluscalcJSON.put("insulinsuperbolus", insulinFromSuperBolus) + boluscalcJSON.put("insulintrend", insulinFromTrend) + boluscalcJSON.put("insulin", calculatedTotalInsulin) + boluscalcJSON.put("superbolusused", useSuperBolus) + boluscalcJSON.put("insulinsuperbolus", insulinFromSuperBolus) + boluscalcJSON.put("trendused", useTrend) + boluscalcJSON.put("insulintrend", insulinFromTrend) + boluscalcJSON.put("trend", trend) + boluscalcJSON.put("ttused", useTT) + boluscalcJSON.put("percentageCorrection", percentageCorrection) + } catch (e: JSONException) { + log.error("Unhandled exception", e) + } + return boluscalcJSON + } + + private fun confirmMessageAfterConstraints(pump: PumpInterface): String { + + var confirmMessage = "" + if (insulinAfterConstraints > 0) { + val pct = if (percentageCorrection != 100.0) " (" + percentageCorrection.toInt() + "%)" else "" + confirmMessage += "
" + MainApp.gs(R.string.bolus) + ": " + "" + DecimalFormatter.toPumpSupportedBolus(insulinAfterConstraints) + "U" + pct + "" + } + if (carbs > 0) { + var timeShift = "" + if (carbTime > 0) { + timeShift += " ( +" + MainApp.gs(R.string.mins, carbTime) + " )" + } else if (carbTime < 0) { + timeShift += " ( -" + MainApp.gs(R.string.mins, carbTime) + " )" + } + confirmMessage += "
" + MainApp.gs(R.string.carbs) + ": " + "" + carbs + "g" + timeShift + "" + } + if (insulinFromCOB > 0) { + confirmMessage += "
" + MainApp.gs(R.string.insulinFromCob, MainApp.gc(R.color.cobAlert), insulinFromBolusIOB + insulinFromBasalsIOB + insulinFromCOB + insulinFromBG) + val absorptionRate = IobCobCalculatorPlugin.getPlugin().slowAbsorptionPercentage(60) + if (absorptionRate > .25) + confirmMessage += "
" + MainApp.gs(R.string.slowabsorptiondetected, MainApp.gc(R.color.cobAlert), (absorptionRate * 100).toInt()) + } + if (abs(insulinAfterConstraints - calculatedTotalInsulin) > pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)) { + confirmMessage += "
" + MainApp.gs(R.string.bolusconstraintappliedwarning, MainApp.gc(R.color.warning), calculatedTotalInsulin, insulinAfterConstraints) + } + + return confirmMessage + } + + fun confirmAndExecute(context: Context) { + val profile = ProfileFunctions.getInstance().profile ?: return + val pump = ConfigBuilderPlugin.getPlugin().activePump ?: return + + if (calculatedTotalInsulin > 0.0 || carbs > 0.0) { + if (accepted) { + log.debug("guarding: already accepted") + return + } + accepted = true + + val confirmMessage = confirmMessageAfterConstraints(pump) + + OKDialog.showConfirmation(context, MainApp.gs(R.string.boluswizard), HtmlHelper.fromHtml(confirmMessage), Runnable { + if (insulinAfterConstraints > 0 || carbs > 0) { + if (useSuperBolus) { + log.debug("USER ENTRY: SUPERBOLUS TBR") + val loopPlugin = LoopPlugin.getPlugin() + if (loopPlugin.isEnabled(PluginType.LOOP)) { + loopPlugin.superBolusTo(System.currentTimeMillis() + 2 * 60L * 60 * 1000) + RxBus.send(EventRefreshOverview("WizardDialog")) + } + + val pump1 = ConfigBuilderPlugin.getPlugin().activePump + + if (pump1?.pumpDescription?.tempBasalStyle == PumpDescription.ABSOLUTE) { + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalAbsolute(0.0, 120, true, profile, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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) + } + } + }) + } else { + + ConfigBuilderPlugin.getPlugin().commandQueue.tempBasalPercent(0, 120, true, profile, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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) + } + } + }) + } + } + val detailedBolusInfo = DetailedBolusInfo() + detailedBolusInfo.eventType = CareportalEvent.BOLUSWIZARD + detailedBolusInfo.insulin = insulinAfterConstraints + detailedBolusInfo.carbs = carbs.toDouble() + detailedBolusInfo.context = context + detailedBolusInfo.glucose = bg + detailedBolusInfo.glucoseType = "Manual" + detailedBolusInfo.carbTime = carbTime + detailedBolusInfo.boluscalc = nsJSON() + detailedBolusInfo.source = Source.USER + detailedBolusInfo.notes = notes + log.debug("USER ENTRY: BOLUS insulin $insulinAfterConstraints carbs: $carbs") + if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getPlugin().activePump?.pumpDescription?.storesCarbInfo == true) { + ConfigBuilderPlugin.getPlugin().commandQueue.bolus(detailedBolusInfo, object : Callback() { + override fun run() { + if (!result.success) { + val i = Intent(MainApp.instance(), ErrorHelperActivity::class.java) + 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(detailedBolusInfo, false) + } + } + }) + } + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/CompositeDisposablePlusAssign.kt b/app/src/main/java/info/nightscout/androidaps/utils/CompositeDisposablePlusAssign.kt new file mode 100644 index 00000000000..1f2fb892e5c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/CompositeDisposablePlusAssign.kt @@ -0,0 +1,9 @@ +package info.nightscout.androidaps.utils + +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +operator fun CompositeDisposable.plusAssign(disposable: Disposable) { + add(disposable) +} + diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java index 7cc44242446..aa228b96cad 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java @@ -1,6 +1,6 @@ package info.nightscout.androidaps.utils; -import android.support.v4.util.LongSparseArray; +import androidx.collection.LongSparseArray; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; @@ -8,6 +8,8 @@ import org.joda.time.format.ISODateTimeFormat; import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -37,10 +39,8 @@ public class DateUtil { * * @param isoDateString the iso date string * @return the date - * @throws Exception the exception */ - public static Date fromISODateString(String isoDateString) - throws Exception { + public static Date fromISODateString(String isoDateString) { DateTimeFormatter parser = ISODateTimeFormat.dateTimeParser(); DateTime dateTime = DateTime.parse(isoDateString, parser); @@ -71,6 +71,18 @@ public static String toISOString(long date) { return toISOString(new Date(date), FORMAT_DATE_ISO_OUT, TimeZone.getTimeZone("UTC")); } + public static String toISOAsUTC(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'0000Z'", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.format(timestamp); + } + + public static String toISONoZone(final long timestamp) { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + format.setTimeZone(TimeZone.getDefault()); + return format.format(timestamp); + } + public static Date toDate(Integer seconds) { Calendar calendar = new GregorianCalendar(); calendar.set(Calendar.MONTH, 0); // Set january to be sure we miss DST changing @@ -81,15 +93,15 @@ public static Date toDate(Integer seconds) { } public static int toSeconds(String hh_colon_mm) { - Pattern p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM | PM|)"); + Pattern p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)"); Matcher m = p.matcher(hh_colon_mm); int retval = 0; if (m.find()) { retval = SafeParse.stringToInt(m.group(1)) * 60 * 60 + SafeParse.stringToInt(m.group(2)) * 60; - if ((m.group(3).equals(" a.m.") || m.group(3).equals(" AM")) && m.group(1).equals("12")) + if ((m.group(3).equals(" a.m.") || m.group(3).equals(" AM") || m.group(3).equals("AM")) && m.group(1).equals("12")) retval -= 12 * 60 * 60; - if ((m.group(3).equals(" p.m.") || m.group(3).equals(" PM")) && !(m.group(1).equals("12"))) + if ((m.group(3).equals(" p.m.") || m.group(3).equals(" PM") || m.group(3).equals("PM")) && !(m.group(1).equals("12"))) retval += 12 * 60 * 60; } return retval; @@ -105,12 +117,36 @@ public static String dateString(long mills) { return df.format(mills); } + public static String dateStringShort(long mills) { + String format = "MM/dd"; + if (android.text.format.DateFormat.is24HourFormat(MainApp.instance())) { + format = "dd/MM"; + } + return new DateTime(mills).toString(DateTimeFormat.forPattern(format)); + } + public static String timeString(Date date) { - return new DateTime(date).toString(DateTimeFormat.shortTime()); + String format = "hh:mma"; + if (android.text.format.DateFormat.is24HourFormat(MainApp.instance())) { + format = "HH:mm"; + } + return new DateTime(date).toString(DateTimeFormat.forPattern(format)); } public static String timeString(long mills) { - return new DateTime(mills).toString(DateTimeFormat.shortTime()); + String format = "hh:mma"; + if (android.text.format.DateFormat.is24HourFormat(MainApp.instance())) { + format = "HH:mm"; + } + return new DateTime(mills).toString(DateTimeFormat.forPattern(format)); + } + + public static String timeStringWithSeconds(long mills) { + String format = "hh:mm:ssa"; + if (android.text.format.DateFormat.is24HourFormat(MainApp.instance())) { + format = "HH:mm:ss"; + } + return new DateTime(mills).toString(DateTimeFormat.forPattern(format)); } public static String timeFullString(long mills) { @@ -126,9 +162,15 @@ public static String dateAndTimeRangeString(long start, long end) { } public static String dateAndTimeString(long mills) { + if (mills == 0) return ""; return dateString(mills) + " " + timeString(mills); } + public static String dateAndTimeAndSecondsString(long mills) { + if (mills == 0) return ""; + return dateString(mills) + " " + timeStringWithSeconds(mills); + } + public static String dateAndTimeFullString(long mills) { return dateString(mills) + " " + timeFullString(mills); } @@ -164,7 +206,7 @@ public static String timeFrameString(long timeInMillis) { long remainingTimeMinutes = timeInMillis / (1000 * 60); long remainingTimeHours = remainingTimeMinutes / 60; remainingTimeMinutes = remainingTimeMinutes % 60; - return "(" + ((remainingTimeHours > 0) ? (remainingTimeHours + "h ") : "") + remainingTimeMinutes + "')"; + return "(" + ((remainingTimeHours > 0) ? (remainingTimeHours + MainApp.gs(R.string.shorthour) + " ") : "") + remainingTimeMinutes + "')"; } public static String sinceString(long timestamp) { @@ -187,4 +229,89 @@ public static boolean isCloseToNow(long date) { long diff = Math.abs(date - now()); return diff < T.mins(2).msecs(); } + + public static boolean isOlderThan(long date, long minutes) { + long diff = now() - date; + return diff > T.mins(minutes).msecs(); + } + + public static GregorianCalendar gregorianCalendar() { + return new GregorianCalendar(); + } + + public static long getTimeZoneOffsetMs() { + return new GregorianCalendar().getTimeZone().getRawOffset(); + } + + public static int getTimeZoneOffsetMinutes(final long timestamp) { + return TimeZone.getDefault().getOffset(timestamp) / 60000; + } + + public static String niceTimeScalar(long t) { + String unit = MainApp.gs(R.string.unit_second); + t = t / 1000; + if (t != 1) unit = MainApp.gs(R.string.unit_seconds); + if (t > 59) { + unit = MainApp.gs(R.string.unit_minute); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_minutes); + if (t > 59) { + unit = MainApp.gs(R.string.unit_hour); + t = t / 60; + if (t != 1) unit = MainApp.gs(R.string.unit_hours); + if (t > 24) { + unit = MainApp.gs(R.string.unit_day) + "\""; + t = t / 24; + if (t != 1) unit = MainApp.gs(R.string.unit_days) + "\""; + if (t > 28) { + unit = MainApp.gs(R.string.unit_week) + "\""; + t = t / 7; + if (t != 1) unit = MainApp.gs(R.string.unit_weeks) + "\""; + } + } + } + } + //if (t != 1) unit = unit + "s"; //implemented plurality in every step, because in other languages plurality of time is not every time adding the same character + return qs((double) t, 0) + " " + unit; + } + + // singletons to avoid repeated allocation + private static DecimalFormatSymbols dfs; + private static DecimalFormat df; + 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); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DefaultValueHelper.java b/app/src/main/java/info/nightscout/androidaps/utils/DefaultValueHelper.java deleted file mode 100644 index 40448d2680a..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/DefaultValueHelper.java +++ /dev/null @@ -1,90 +0,0 @@ -package info.nightscout.androidaps.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/androidaps/utils/DefaultValueHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/DefaultValueHelper.kt new file mode 100644 index 00000000000..7b40d5d35bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/DefaultValueHelper.kt @@ -0,0 +1,95 @@ +package info.nightscout.androidaps.utils + +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions + +object DefaultValueHelper { + /** + * returns the corresponding EatingSoon TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + fun getDefaultEatingSoonTT(units: String): Double { + return if (Constants.MMOL == units) Constants.defaultEatingSoonTTmmol else Constants.defaultEatingSoonTTmgdl + } + + /** + * returns the corresponding Activity TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + fun getDefaultActivityTT(units: String): Double { + return if (Constants.MMOL == units) Constants.defaultActivityTTmmol else Constants.defaultActivityTTmgdl + } + + /** + * returns the corresponding Hypo TempTarget based on the given units (MMOL / MGDL) + * + * @param units + * @return + */ + fun getDefaultHypoTT(units: String): Double { + return if (Constants.MMOL == units) Constants.defaultHypoTTmmol else Constants.defaultHypoTTmgdl + } + + /** + * returns the configured EatingSoon TempTarget, if this is set to 0, the Default-Value is returned. + * + * @return + */ + @JvmStatic + fun determineEatingSoonTT(): Double { + val units = ProfileFunctions.getSystemUnits() + var value = SP.getDouble(R.string.key_eatingsoon_target, getDefaultEatingSoonTT(units)) + value = Profile.toCurrentUnits(value) + return if (value > 0) value else getDefaultEatingSoonTT(units) + } + + @JvmStatic + fun determineEatingSoonTTDuration(): Int { + val value = SP.getInt(R.string.key_eatingsoon_duration, Constants.defaultEatingSoonTTDuration) + return if (value > 0) value else Constants.defaultEatingSoonTTDuration + } + + /** + * returns the configured Activity TempTarget, if this is set to 0, the Default-Value is returned. + * + * @return + */ + @JvmStatic + fun determineActivityTT(): Double { + val units = ProfileFunctions.getSystemUnits() + var value = SP.getDouble(R.string.key_activity_target, getDefaultActivityTT(units)) + value = Profile.toCurrentUnits(value) + return if (value > 0) value else getDefaultActivityTT(units) + } + + @JvmStatic + fun determineActivityTTDuration(): Int { + val value = SP.getInt(R.string.key_activity_duration, Constants.defaultActivityTTDuration) + return if (value > 0) value else Constants.defaultActivityTTDuration + } + + /** + * returns the configured Hypo TempTarget, if this is set to 0, the Default-Value is returned. + * + * @return + */ + @JvmStatic + fun determineHypoTT(): Double { + val units = ProfileFunctions.getSystemUnits() + var value = SP.getDouble(R.string.key_hypo_target, getDefaultHypoTT(units)) + value = Profile.toCurrentUnits(value) + return if (value > 0) value else getDefaultHypoTT(units) + } + + @JvmStatic + fun determineHypoTTDuration(): Int { + val value = SP.getInt(R.string.key_hypo_duration, Constants.defaultHypoTTDuration) + return if (value > 0) value else Constants.defaultHypoTTDuration + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DigitsKeyListenerWithComma.java b/app/src/main/java/info/nightscout/androidaps/utils/DigitsKeyListenerWithComma.java new file mode 100644 index 00000000000..a84ccd61a21 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/DigitsKeyListenerWithComma.java @@ -0,0 +1,199 @@ +package info.nightscout.androidaps.utils; + +import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.NumberKeyListener; +import android.view.KeyEvent; + +class DigitsKeyListenerWithComma extends NumberKeyListener { + + /** + * The characters that are used. + * + * @see KeyEvent#getMatch + * @see #getAcceptedChars + */ + private static final char[][] CHARACTERS = new char[][]{ + new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}, + new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-'}, + new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ','}, + new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.', ','}, + }; + + private char[] mAccepted; + private boolean mSign; + private boolean mDecimal; + + private static final int SIGN = 1; + private static final int DECIMAL = 2; + + private static DigitsKeyListenerWithComma[] sInstance = new DigitsKeyListenerWithComma[4]; + + @Override + protected char[] getAcceptedChars() { + return mAccepted; + } + + /** + * Allocates a DigitsKeyListener that accepts the digits 0 through 9. + */ + public DigitsKeyListenerWithComma() { + this(false, false); + } + + /** + * Allocates a DigitsKeyListener that accepts the digits 0 through 9, + * plus the minus sign (only at the beginning) and/or decimal point + * (only one per field) if specified. + */ + public DigitsKeyListenerWithComma(boolean sign, boolean decimal) { + mSign = sign; + mDecimal = decimal; + + int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); + mAccepted = CHARACTERS[kind]; + } + + /** + * Returns a DigitsKeyListener that accepts the digits 0 through 9. + */ + public static DigitsKeyListenerWithComma getInstance() { + return getInstance(false, false); + } + + /** + * Returns a DigitsKeyListener that accepts the digits 0 through 9, + * plus the minus sign (only at the beginning) and/or decimal point + * (only one per field) if specified. + */ + public static DigitsKeyListenerWithComma getInstance(boolean sign, boolean decimal) { + int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0); + + if (sInstance[kind] != null) + return sInstance[kind]; + + sInstance[kind] = new DigitsKeyListenerWithComma(sign, decimal); + return sInstance[kind]; + } + + /** + * Returns a DigitsKeyListener that accepts only the characters + * that appear in the specified String. Note that not all characters + * may be available on every keyboard. + */ + public static DigitsKeyListenerWithComma getInstance(String accepted) { + // TODO: do we need a cache of these to avoid allocating? + + DigitsKeyListenerWithComma dim = new DigitsKeyListenerWithComma(); + + dim.mAccepted = new char[accepted.length()]; + accepted.getChars(0, accepted.length(), dim.mAccepted, 0); + + return dim; + } + + public int getInputType() { + int contentType = InputType.TYPE_CLASS_NUMBER; + if (mSign) { + contentType |= InputType.TYPE_NUMBER_FLAG_SIGNED; + } + if (mDecimal) { + contentType |= InputType.TYPE_NUMBER_FLAG_DECIMAL; + } + return contentType; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + CharSequence out = super.filter(source, start, end, dest, dstart, dend); + + if (mSign == false && mDecimal == false) { + return out; + } + + if (out != null) { + source = out; + start = 0; + end = out.length(); + } + + int sign = -1; + int decimal = -1; + int dlen = dest.length(); + + /* + * Find out if the existing text has '-' or '.' characters. + */ + + for (int i = 0; i < dstart; i++) { + char c = dest.charAt(i); + + if (c == '-') { + sign = i; + } else if (c == '.' || c == ',') { + decimal = i; + } + } + for (int i = dend; i < dlen; i++) { + char c = dest.charAt(i); + + if (c == '-') { + return ""; // Nothing can be inserted in front of a '-'. + } else if (c == '.' || c == ',') { + decimal = i; + } + } + + /* + * If it does, we must strip them out from the source. + * In addition, '-' must be the very first character, + * and nothing can be inserted before an existing '-'. + * Go in reverse order so the offsets are stable. + */ + + SpannableStringBuilder stripped = null; + + for (int i = end - 1; i >= start; i--) { + char c = source.charAt(i); + boolean strip = false; + + if (c == '-') { + if (i != start || dstart != 0) { + strip = true; + } else if (sign >= 0) { + strip = true; + } else { + sign = i; + } + } else if (c == '.' || c == ',') { + if (decimal >= 0) { + strip = true; + } else { + decimal = i; + } + } + + if (strip) { + if (end == start + 1) { + return ""; // Only one character, and it was stripped. + } + + if (stripped == null) { + stripped = new SpannableStringBuilder(source, start, end); + } + + stripped.delete(i - start, i + 1 - start); + } + } + + if (stripped != null) { + return stripped; + } else if (out != null) { + return out; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt new file mode 100644 index 00000000000..0cddcc0792e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/EspressoTestHelper.kt @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.utils + +@Synchronized +fun isRunningTest(): Boolean { + return try { + Class.forName("androidx.test.espresso.Espresso") + true + } catch (e: ClassNotFoundException) { + false + } +} + +@Synchronized +fun isRunningRealPumpTest(): Boolean { + return try { + Class.forName("info.nightscout.androidaps.RealPumpTest") + true + } catch (e: ClassNotFoundException) { + false + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java b/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java index 54f06bfaf02..355623f38d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/FabricPrivacy.java @@ -8,10 +8,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin; /** * Created by jamorham on 21/02/2018. @@ -112,19 +115,23 @@ public static void setUserStats() { String closedLoopEnabled = MainApp.getConstraintChecker().isClosedLoopAllowed().value() ? "CLOSED_LOOP_ENABLED" : "CLOSED_LOOP_DISABLED"; // Size is limited to 36 chars - String remote = BuildConfig.REMOTE - .replace("https://","") - .replace("http://","") + String remote = BuildConfig.REMOTE.toLowerCase() + .replace("https://", "") + .replace("http://", "") .replace(".git", "") .replace(".com/", ":") .replace(".org/", ":") .replace(".net/", ":"); MainApp.getFirebaseAnalytics().setUserProperty("Mode", BuildConfig.APPLICATION_ID + "-" + closedLoopEnabled); - MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.getLanguage(MainApp.instance())); + MainApp.getFirebaseAnalytics().setUserProperty("Language", LocaleHelper.INSTANCE.currentLanguage()); MainApp.getFirebaseAnalytics().setUserProperty("Version", BuildConfig.VERSION); MainApp.getFirebaseAnalytics().setUserProperty("HEAD", BuildConfig.HEAD); MainApp.getFirebaseAnalytics().setUserProperty("Remote", remote); + List hashes = SignatureVerifierPlugin.getPlugin().shortHashes(); + if (hashes.size() >= 1) + MainApp.getFirebaseAnalytics().setUserProperty("Hash", hashes.get(0)); + if (ConfigBuilderPlugin.getPlugin().getActivePump() != null) MainApp.getFirebaseAnalytics().setUserProperty("Pump", ConfigBuilderPlugin.getPlugin().getActivePump().getClass().getSimpleName()); if (ConfigBuilderPlugin.getPlugin().getActiveAPS() != null) @@ -137,7 +144,6 @@ public static void setUserStats() { MainApp.getFirebaseAnalytics().setUserProperty("Sensitivity", ConfigBuilderPlugin.getPlugin().getActiveSensitivity().getClass().getSimpleName()); if (ConfigBuilderPlugin.getPlugin().getActiveInsulin() != null) MainApp.getFirebaseAnalytics().setUserProperty("Insulin", ConfigBuilderPlugin.getPlugin().getActiveInsulin().getClass().getSimpleName()); - } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/HtmlHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/HtmlHelper.kt new file mode 100644 index 00000000000..b79fe884b4c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/HtmlHelper.kt @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.utils + +import android.os.Build +import android.text.Html +import android.text.Spanned + +object HtmlHelper { + fun fromHtml(source: String): Spanned { + // API level 24 to replace call + @Suppress("DEPRECATION") + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY) + else -> Html.fromHtml(source) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/InstanceId.kt b/app/src/main/java/info/nightscout/androidaps/utils/InstanceId.kt new file mode 100644 index 00000000000..64c84fcaa94 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/InstanceId.kt @@ -0,0 +1,10 @@ +package info.nightscout.androidaps.utils + +import com.google.firebase.iid.FirebaseInstanceId + +object InstanceId { + fun instanceId(): String { + var id = FirebaseInstanceId.getInstance().id + return id + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.java b/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.java index 159e90fda76..6205beee21d 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/JSONFormatter.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.utils; +import android.os.Build; import android.text.Html; import android.text.Spanned; @@ -18,14 +19,23 @@ public class JSONFormatter { private static Logger log = LoggerFactory.getLogger(JSONFormatter.class); public static Spanned format(final String jsonString) { - final JsonVisitor visitor = new JsonVisitor(4, ' '); + final JsonVisitor visitor = new JsonVisitor(1, '\t'); try { if (jsonString.equals("undefined")) return Html.fromHtml("undefined"); else if (jsonString.getBytes()[0] == '[') - return Html.fromHtml(visitor.visit(new JSONArray(jsonString), 0)); - else - return Html.fromHtml(visitor.visit(new JSONObject(jsonString), 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(visitor.visit(new JSONArray(jsonString), 0), Html.FROM_HTML_MODE_COMPACT); + } else { + return Html.fromHtml(visitor.visit(new JSONArray(jsonString), 0)); + } + else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return Html.fromHtml(visitor.visit(new JSONObject(jsonString), 0), Html.FROM_HTML_MODE_COMPACT); + } else { + return Html.fromHtml(visitor.visit(new JSONObject(jsonString), 0)); + } + } } catch (JSONException e) { log.error("Unhandled exception", e); return Html.fromHtml(""); @@ -33,7 +43,7 @@ else if (jsonString.getBytes()[0] == '[') } public static Spanned format(final JSONObject object) { - final JsonVisitor visitor = new JsonVisitor(4, ' '); + final JsonVisitor visitor = new JsonVisitor(1, '\t'); try { return Html.fromHtml(visitor.visit(object, 0)); } catch (JSONException e) { @@ -58,7 +68,7 @@ private String visit(final JSONArray array, final int indent) throws JSONExcepti } else { ret += write("[", indent); for (int i = 0; i < length; i++) { - ret += visit(array.get(i), indent + 1); + ret += visit(array.get(i), indent); } ret += write("]", indent); } @@ -73,8 +83,8 @@ private String visit(final JSONObject obj, final int indent) throws JSONExceptio final Iterator keys = obj.keys(); while (keys.hasNext()) { final String key = keys.next(); - ret += write("" + key + ": ", indent + 1); - ret += visit(obj.get(key), 0); + ret += write("" + key + ": ", indent); + ret += visit(obj.get(key), indent + 1); ret += "
"; } } @@ -86,7 +96,7 @@ private String visit(final Object object, final int indent) throws JSONException if (object instanceof JSONArray) { ret += visit((JSONArray) object, indent); } else if (object instanceof JSONObject) { - ret += visit((JSONObject) object, indent); + ret += "
" + visit((JSONObject) object, indent); } else { if (object instanceof String) { ret += write("\"" + ((String) object).replace("<", "<").replace(">", ">") + "\"", indent); diff --git a/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java b/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java deleted file mode 100644 index 8f7d9611b1d..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.java +++ /dev/null @@ -1,113 +0,0 @@ -package info.nightscout.androidaps.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 != null && 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 != null && 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 != null && 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 != null && 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 != null && 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 != null && 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 != null && json.has(fieldName)) { - try { - result = json.getBoolean(fieldName); - } catch (JSONException e) { - } - } - - return result; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.kt new file mode 100644 index 00000000000..484f6793450 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/JsonHelper.kt @@ -0,0 +1,126 @@ +package info.nightscout.androidaps.utils + +import org.json.JSONException +import org.json.JSONObject + +object JsonHelper { + @JvmStatic + fun safeGetObject(json: JSONObject?, fieldName: String, defaultValue: Any): Any { + var result = defaultValue + if (json != null && json.has(fieldName)) { + try { + result = json[fieldName] + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetJSONObject(json: JSONObject?, fieldName: String, defaultValue: JSONObject?): JSONObject? { + var result = defaultValue + if (json != null && json.has(fieldName)) { + try { + result = json.getJSONObject(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetString(json: JSONObject?, fieldName: String): String? { + var result: String? = null + if (json != null && json.has(fieldName)) { + try { + result = json.getString(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetString(json: JSONObject?, fieldName: String, defaultValue: String): String { + var result = defaultValue + if (json != null && json.has(fieldName)) { + try { + result = json.getString(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetStringAllowNull(json: JSONObject?, fieldName: String, defaultValue: String?): String? { + var result = defaultValue + if (json != null && json.has(fieldName)) { + try { + result = json.getString(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetDouble(json: JSONObject?, fieldName: String): Double { + var result = 0.0 + if (json != null && json.has(fieldName)) { + try { + result = json.getDouble(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetDouble(json: JSONObject?, fieldName: String, defaultValue: Double): Double { + var result = defaultValue + if (json != null && json.has(fieldName)) { + try { + result = json.getDouble(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetInt(json: JSONObject?, fieldName: String): Int { + var result = 0 + if (json != null && json.has(fieldName)) { + try { + result = json.getInt(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetLong(json: JSONObject?, fieldName: String): Long { + var result: Long = 0 + if (json != null && json.has(fieldName)) { + try { + result = json.getLong(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } + + @JvmStatic + fun safeGetBoolean(json: JSONObject?, fieldName: String): Boolean { + var result = false + if (json != null && json.has(fieldName)) { + try { + result = json.getBoolean(fieldName) + } catch (ignored: JSONException) { + } + } + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.java index d23020a56cb..cc68cadf39f 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/LocalAlertUtils.java @@ -10,9 +10,10 @@ import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; @@ -34,32 +35,59 @@ private static long pumpUnreachableThreshold() { } public static void checkPumpUnreachableAlarm(long lastConnection, boolean isStatusOutdated) { + PumpInterface activePump = ConfigBuilderPlugin.getPlugin().getActivePump(); + + boolean alertsEnabled = Config.APS && // + ((activePump != null && activePump.getPumpDescription().hasFixedUnreachableAlert) // + || SP.getBoolean(MainApp.gs(R.string.key_enable_pump_unreachable_alert), true)); + + if (alertsEnabled) { + boolean isUnreachable; + + if (activePump != null && activePump.getPumpDescription().hasCustomUnreachableAlertCheck) { + isUnreachable = checkCustomPumpUnreachableAlarm(activePump); + } else { + isUnreachable = checkDefaultPumpUnreachableAlarm(lastConnection, isStatusOutdated); + } + + if (isUnreachable) { + log.debug("Generating pump unreachable alarm."); + showUnreachableNotification(pumpUnreachableThreshold()); + } else { + RxBus.INSTANCE.send(new EventDismissNotification(Notification.PUMP_UNREACHABLE)); + } + } + } + + private static boolean checkCustomPumpUnreachableAlarm(PumpInterface pump) { + return pump != null && pump.isUnreachableAlertTimeoutExceeded(pumpUnreachableThreshold()); + } + + private static boolean checkDefaultPumpUnreachableAlarm(long lastConnection, boolean isStatusOutdated) { boolean alarmTimeoutExpired = lastConnection + pumpUnreachableThreshold() < System.currentTimeMillis(); boolean nextAlarmOccurrenceReached = SP.getLong("nextPumpDisconnectedAlarm", 0L) < System.currentTimeMillis(); - if (Config.APS && SP.getBoolean(MainApp.gs(R.string.key_enable_pump_unreachable_alert), true) - && 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.gs(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); - } + return isStatusOutdated && alarmTimeoutExpired && nextAlarmOccurrenceReached && !LoopPlugin.getPlugin().isDisconnected(); + } + + private static void showUnreachableNotification(long nextAlarmTimeoutMillis) { + Notification n = new Notification(Notification.PUMP_UNREACHABLE, MainApp.gs(R.string.pump_unreachable), Notification.URGENT); + n.soundId = R.raw.alarm; + SP.putLong("nextPumpDisconnectedAlarm", System.currentTimeMillis() + nextAlarmTimeoutMillis); + RxBus.INSTANCE.send(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! */ public static void presnoozeAlarms() { - if (SP.getLong("nextMissedReadingsAlarm", 0l) < System.currentTimeMillis()) { + if (SP.getLong("nextMissedReadingsAlarm", 0L) < System.currentTimeMillis()) { SP.putLong("nextMissedReadingsAlarm", System.currentTimeMillis() + 5 * 60 * 1000); } - if (SP.getLong("nextPumpDisconnectedAlarm", 0l) < System.currentTimeMillis()) { + if (SP.getLong("nextPumpDisconnectedAlarm", 0L) < System.currentTimeMillis()) { SP.putLong("nextPumpDisconnectedAlarm", System.currentTimeMillis() + 5 * 60 * 1000); } } @@ -83,7 +111,7 @@ public static void notifyPumpStatusRead() { if (pump != null && profile != null) { long lastConnection = pump.lastDataTime(); long earliestAlarmTime = lastConnection + pumpUnreachableThreshold(); - if (SP.getLong("nextPumpDisconnectedAlarm", 0l) < earliestAlarmTime) { + if (SP.getLong("nextPumpDisconnectedAlarm", 0L) < earliestAlarmTime) { SP.putLong("nextPumpDisconnectedAlarm", earliestAlarmTime); } } @@ -93,11 +121,11 @@ public static void checkStaleBGAlert() { BgReading bgReading = DatabaseHelper.lastBg(); if (SP.getBoolean(MainApp.gs(R.string.key_enable_missed_bg_readings_alert), false) && bgReading != null && bgReading.date + missedReadingsThreshold() < System.currentTimeMillis() - && SP.getLong("nextMissedReadingsAlarm", 0l) < System.currentTimeMillis()) { + && SP.getLong("nextMissedReadingsAlarm", 0L) < System.currentTimeMillis()) { Notification n = new Notification(Notification.BG_READINGS_MISSED, MainApp.gs(R.string.missed_bg_readings), Notification.URGENT); n.soundId = R.raw.alarm; SP.putLong("nextMissedReadingsAlarm", System.currentTimeMillis() + missedReadingsThreshold()); - MainApp.bus().post(new EventNewNotification(n)); + RxBus.INSTANCE.send(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/androidaps/utils/LocaleHelper.java b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java deleted file mode 100644 index bf0b2796eea..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.java +++ /dev/null @@ -1,65 +0,0 @@ -package info.nightscout.androidaps.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.preference.PreferenceManager; - -import java.util.Locale; - -/** - * This class is used to change your application locale and persist this change for the next time - * that your app is going to be used. - *

- * You can also change the locale of your application on the fly by using the setLocale method. - *

- * Created by gunhansancar on 07/10/15. - */ -public class LocaleHelper { - - private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"; - - public static void onCreate(Context context) { - String lang = getPersistedData(context, Locale.getDefault().getLanguage()); - setLocale(context, lang); - } - - public static void onCreate(Context context, String defaultLanguage) { - String lang = getPersistedData(context, defaultLanguage); - setLocale(context, lang); - } - - public static String getLanguage(Context context) { - return getPersistedData(context, Locale.getDefault().getLanguage()); - } - - public static void setLocale(Context context, String language) { - persist(context, language); - updateResources(context, language); - } - - private static String getPersistedData(Context context, String defaultLanguage) { - return SP.getString(SELECTED_LANGUAGE, defaultLanguage); - } - - private static void persist(Context context, String language) { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor editor = preferences.edit(); - - editor.putString(SELECTED_LANGUAGE, language); - editor.apply(); - } - - private static void updateResources(Context context, String language) { - Locale locale = new Locale(language); - Locale.setDefault(locale); - - Resources resources = context.getResources(); - - Configuration configuration = resources.getConfiguration(); - configuration.locale = locale; - - resources.updateConfiguration(configuration, resources.getDisplayMetrics()); - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt new file mode 100644 index 00000000000..447d076d936 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt @@ -0,0 +1,55 @@ +package info.nightscout.androidaps.utils + +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.os.LocaleList +import info.nightscout.androidaps.R +import java.util.* + + +object LocaleHelper { + fun currentLanguage(): String = + SP.getString(R.string.key_language, Locale.getDefault().language) + + private fun currentLocale(): Locale { + val language = currentLanguage() + var locale = Locale(language) + if (language.contains("_")) { + // language with country like pt_BR defined in arrays.xml + val lang = language.substring(0, 2) + val country = language.substring(3, 5) + locale = Locale(lang, country) + } + return locale + } + + @Suppress("DEPRECATION") + fun update(context: Context) { + val locale = currentLocale() + Locale.setDefault(locale) + val resources = context.resources + val configuration = resources.configuration + context.createConfigurationContext(configuration) + configuration.setLocale(locale) + configuration.locale = locale + resources.updateConfiguration(configuration, resources.displayMetrics) + } + + fun wrap(ctx: Context): ContextWrapper { + val res = ctx.resources + val configuration = res.configuration + val newLocale = currentLocale() + val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + configuration.setLocale(newLocale) + val localeList = LocaleList(newLocale) + LocaleList.setDefault(localeList) + configuration.locales = localeList + ctx.createConfigurationContext(configuration) + } else { + configuration.setLocale(newLocale) + ctx.createConfigurationContext(configuration) + } + return ContextWrapper(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java b/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java index 71c16442dc5..b91236ba252 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/NumberPicker.java @@ -14,7 +14,6 @@ import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.TextView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +33,11 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, View.OnTouchListener, View.OnClickListener { private static Logger log = LoggerFactory.getLogger(NumberPicker.class); - TextView editText; + public interface OnValueChangedListener { + void onValueChanged(double value); + } + + EditText editText; Button minusButton; Button plusButton; @@ -46,8 +49,11 @@ public class NumberPicker extends LinearLayout implements View.OnKeyListener, boolean allowZero = false; TextWatcher textWatcher = null; + Button okButton = null; + private Handler mHandler; private ScheduledExecutorService mUpdater; + private OnValueChangedListener mOnValueChangedListener; private class UpdateCounterTask implements Runnable { private boolean mInc; @@ -56,7 +62,7 @@ private class UpdateCounterTask implements Runnable { private final int doubleLimit = 5; - public UpdateCounterTask(boolean inc) { + UpdateCounterTask(boolean inc) { mInc = inc; } @@ -80,44 +86,42 @@ public void run() { public NumberPicker(Context context) { super(context, null); + this.initialize(context); } public NumberPicker(Context context, AttributeSet attrs) { super(context, attrs); - this.initialize(context, attrs); + this.initialize(context); } - public NumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + protected void inflate(Context context) { + LayoutInflater.from(context).inflate(R.layout.number_picker_layout, this, true); } - private void initialize(Context context, AttributeSet attrs) { + private void initialize(Context context) { // set layout view - LayoutInflater.from(context).inflate(R.layout.number_picker_layout, this, true); + inflate(context); // init ui components - minusButton = (Button) findViewById(R.id.decrement); + minusButton = findViewById(R.id.decrement); minusButton.setId(View.generateViewId()); - plusButton = (Button) findViewById(R.id.increment); + plusButton = findViewById(R.id.increment); plusButton.setId(View.generateViewId()); - editText = (EditText) findViewById(R.id.display); + editText = findViewById(R.id.display); editText.setId(View.generateViewId()); - mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_INC: - inc(msg.arg1); - return; - case MSG_DEC: - dec(msg.arg1); - return; - } - super.handleMessage(msg); + mHandler = new Handler(msg -> { + switch (msg.what) { + case MSG_INC: + inc(msg.arg1); + return true; + case MSG_DEC: + dec(msg.arg1); + return true; } - }; + return false; + }); minusButton.setOnTouchListener(this); minusButton.setOnKeyListener(this); @@ -139,33 +143,71 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { @Override public void afterTextChanged(Editable s) { value = SafeParse.stringToDouble(editText.getText().toString()); + callValueChangedListener(); + if (okButton != null) { + if (value > maxValue || value < minValue) + okButton.setVisibility(INVISIBLE); + else + okButton.setVisibility(VISIBLE); + } } }); } + @Override + public void setTag(Object tag) { + editText.setTag(tag); + } + + public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) { + mOnValueChangedListener = onValueChangedListener; + } + public void setTextWatcher(TextWatcher textWatcher) { this.textWatcher = textWatcher; editText.addTextChangedListener(textWatcher); + editText.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + value = SafeParse.stringToDouble(editText.getText().toString()); + if (value > maxValue) { + value = maxValue; + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit)); + updateEditText(); + if (okButton != null) + okButton.setVisibility(VISIBLE); + } + if (value < minValue) { + value = minValue; + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit)); + updateEditText(); + if (okButton != null) + okButton.setVisibility(VISIBLE); + } + } + }); } - public void setParams(Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero, TextWatcher textWatcher) { + public void setParams(Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero, Button okButton, TextWatcher textWatcher) { if (this.textWatcher != null) { editText.removeTextChangedListener(this.textWatcher); } - setParams(initValue, minValue, maxValue, step, formater, allowZero); + setParams(initValue, minValue, maxValue, step, formater, allowZero, okButton); this.textWatcher = textWatcher; - editText.addTextChangedListener(textWatcher); + if (textWatcher != null) + editText.addTextChangedListener(textWatcher); } - public void setParams(Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero) { + public void setParams(Double initValue, Double minValue, Double maxValue, Double step, NumberFormat formater, boolean allowZero, Button okButton) { this.value = initValue; this.minValue = minValue; this.maxValue = maxValue; this.step = step; this.formater = formater; this.allowZero = allowZero; + callValueChangedListener(); + this.okButton = okButton; - editText.setKeyListener(DigitsKeyListener.getInstance(minValue < 0, step != Math.rint(step))); + editText.setKeyListener(DigitsKeyListenerWithComma.getInstance(minValue < 0, step != Math.rint(step))); if (textWatcher != null) editText.removeTextChangedListener(textWatcher); @@ -178,6 +220,7 @@ public void setValue(Double value) { if (textWatcher != null) editText.removeTextChangedListener(textWatcher); this.value = value; + callValueChangedListener(); updateEditText(); if (textWatcher != null) editText.addTextChangedListener(textWatcher); @@ -199,6 +242,7 @@ private void inc(int multiplier) { value += step * multiplier; if (value > maxValue) { value = maxValue; + callValueChangedListener(); ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit)); stopUpdating(); } @@ -209,6 +253,7 @@ private void dec(int multiplier) { value -= step * multiplier; if (value < minValue) { value = minValue; + callValueChangedListener(); ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.gs(R.string.youareonallowedlimit)); stopUpdating(); } @@ -222,6 +267,11 @@ private void updateEditText() { editText.setText(formater.format(value)); } + private void callValueChangedListener() { + if (mOnValueChangedListener != null) + mOnValueChangedListener.onValueChanged(value); + } + private void startUpdating(boolean inc) { if (mUpdater != null) { log.debug("Another executor is still active"); diff --git a/app/src/main/java/info/nightscout/androidaps/utils/NumberPickerVertical.java b/app/src/main/java/info/nightscout/androidaps/utils/NumberPickerVertical.java new file mode 100644 index 00000000000..3265280c484 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/NumberPickerVertical.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.utils; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.NumberFormat; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + +/** + * Created by mike on 28.06.2016. + */ +public class NumberPickerVertical extends NumberPicker { + private static Logger log = LoggerFactory.getLogger(NumberPickerVertical.class); + + public NumberPickerVertical(Context context) { + super(context); + } + + public NumberPickerVertical(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void inflate(Context context) { + LayoutInflater.from(context).inflate(R.layout.number_picker_layout_vertical, this, true); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java deleted file mode 100644 index 00b3c296929..00000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.java +++ /dev/null @@ -1,86 +0,0 @@ -package info.nightscout.androidaps.utils; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Handler; -import android.os.SystemClock; -import android.support.v7.app.AlertDialog; -import android.support.v7.view.ContextThemeWrapper; -import android.text.Spanned; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; - -/** - * Created by mike on 31.03.2017. - */ - -public class OKDialog { - private static Logger log = LoggerFactory.getLogger(OKDialog.class); - - public static void show(final Context context, String title, String message, final Runnable runnable) { - try { - AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(context, R.style.AppTheme)); - builder.setTitle(title); - builder.setMessage(message); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (runnable != null) { - SystemClock.sleep(100); - runOnUiThread(runnable); - } - } - }); - - builder.create().show(); - } catch (Exception e) { - log.debug("show_dialog exception: " + e); - } - } - - public static boolean runOnUiThread(Runnable theRunnable) { - final Handler mainHandler = new Handler(MainApp.instance().getApplicationContext().getMainLooper()); - return mainHandler.post(theRunnable); - } - - public static void show(final Activity activity, String title, Spanned message, final Runnable runnable) { - try { - AlertDialog.Builder builder = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.AppTheme)); - builder.setTitle(title); - builder.setMessage(message); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (runnable != null) { - SystemClock.sleep(100); - activity.runOnUiThread(runnable); - } - } - }); - - builder.create().show(); - } catch (Exception e) { - 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/androidaps/utils/OKDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt new file mode 100644 index 00000000000..1d22c380ca1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt @@ -0,0 +1,203 @@ +package info.nightscout.androidaps.utils + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.os.Handler +import android.os.SystemClock +import android.text.Spanned +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.view.ContextThemeWrapper +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R + +object OKDialog { + @JvmStatic + @JvmOverloads + fun show(context: Context, title: String, message: String, runnable: Runnable? = null) { + var notEmptytitle = title + if (notEmptytitle.isEmpty()) notEmptytitle = MainApp.gs(R.string.message) + val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + .setCustomTitle(titleLayout) + .setMessage(message) + .setPositiveButton(MainApp.gs(R.string.ok)) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runOnUiThread(runnable) + } + .show() + .setCanceledOnTouchOutside(false) + } + + fun runOnUiThread(theRunnable: Runnable?) { + val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) + theRunnable?.let { mainHandler.post(it) } + } + + @JvmStatic + @JvmOverloads + fun show(activity: Activity, title: String, message: Spanned, runnable: Runnable? = null) { + var notEmptytitle = title + if (notEmptytitle.isEmpty()) notEmptytitle = MainApp.gs(R.string.message) + val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + .setCustomTitle(titleLayout) + .setMessage(message) + .setPositiveButton(MainApp.gs(R.string.ok)) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runnable?.let { activity.runOnUiThread(it) } + } + .show() + .setCanceledOnTouchOutside(false) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(activity: Activity, message: String, ok: Runnable?) { + showConfirmation(activity, MainApp.gs(R.string.confirmation), message, ok, null) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(activity: Activity, message: Spanned, ok: Runnable?) { + showConfirmation(activity, MainApp.gs(R.string.confirmation), message, ok, null) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(activity: Activity, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { + val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + .setMessage(message) + .setCustomTitle(titleLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + ok?.let { activity.runOnUiThread(it) } + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + cancel?.let { activity.runOnUiThread(it) } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + .setCanceledOnTouchOutside(false) + } + + @JvmStatic + fun showConfirmation(activity: Activity, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { + val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + .setMessage(message) + .setCustomTitle(titleLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + ok?.let { activity.runOnUiThread(it) } + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + cancel?.let { activity.runOnUiThread(it) } + } + .show() + .setCanceledOnTouchOutside(false) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(context: Context, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { + showConfirmation(context, MainApp.gs(R.string.confirmation), message, ok, cancel) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(context: Context, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { + val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + .setMessage(message) + .setCustomTitle(titleLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runOnUiThread(ok) + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runOnUiThread(cancel) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + .setCanceledOnTouchOutside(false) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(context: Context, message: String, ok: Runnable?, cancel: Runnable? = null) { + showConfirmation(context, MainApp.gs(R.string.confirmation), message, ok, cancel) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(context: Context, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { + val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + .setMessage(message) + .setCustomTitle(titleLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runOnUiThread(ok) + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + runOnUiThread(cancel) + } + .show() + .setCanceledOnTouchOutside(false) + } + + @JvmStatic + @JvmOverloads + fun showConfirmation(context: Context, title: String, message: String, ok: DialogInterface.OnClickListener?, cancel: DialogInterface.OnClickListener? = null) { + val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) + AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + .setMessage(message) + .setCustomTitle(titleLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> + dialog.dismiss() + SystemClock.sleep(100) + ok?.onClick(dialog, which) + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, which: Int -> + dialog.dismiss() + SystemClock.sleep(100) + cancel?.onClick(dialog, which) + } + .show() + .setCanceledOnTouchOutside(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/PasswordProtection.java b/app/src/main/java/info/nightscout/androidaps/utils/PasswordProtection.java index 230be97e22b..6c4348bb03c 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/PasswordProtection.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/PasswordProtection.java @@ -2,10 +2,10 @@ import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; +import android.widget.ImageView; import android.widget.TextView; import info.nightscout.androidaps.MainApp; @@ -18,10 +18,7 @@ public class PasswordProtection { static public boolean isLocked(String preference) { final String password = SP.getString(preference, ""); - if (password.equals("")) { - return false; - } - return true; + return !password.equals(""); } static public void QueryPassword(final Context context, int stringID, String preference, final Runnable ok, final Runnable fail) { @@ -30,39 +27,38 @@ static public void QueryPassword(final Context context, int stringID, String pre if (ok != null) ok.run(); return; } - LayoutInflater li = LayoutInflater.from(context); - View promptsView = li.inflate(R.layout.passwordprompt, null); + View promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null); + + View titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null); + ((TextView) titleLayout.findViewById(R.id.alertdialog_title)).setText(R.string.confirmation); + ((ImageView) titleLayout.findViewById(R.id.alertdialog_icon)).setImageResource(R.drawable.ic_check_while_48dp); AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); alertDialogBuilder.setView(promptsView); + alertDialogBuilder.setCustomTitle(titleLayout); - final TextView label = (TextView) promptsView.findViewById(R.id.passwordprompt_text); + final TextView label = promptsView.findViewById(R.id.passwordprompt_text); label.setText(MainApp.gs(stringID)); - final EditText userInput = (EditText) promptsView.findViewById(R.id.passwordprompt_pass); + final EditText userInput = promptsView.findViewById(R.id.passwordprompt_pass); // set dialog message alertDialogBuilder .setCancelable(false) - .setPositiveButton("OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog,int id) { - String enteredPassword = userInput.getText().toString(); - if (password.equals(enteredPassword)) { - if (ok != null) ok.run(); - } else { - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.wrongpassword)); - if (fail != null) fail.run(); - } + .setPositiveButton(android.R.string.ok, + (dialog, id) -> { + String enteredPassword = userInput.getText().toString(); + if (password.equals(enteredPassword)) { + if (ok != null) ok.run(); + } else { + ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.wrongpassword)); + if (fail != null) fail.run(); } }) - .setNegativeButton("Cancel", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog,int id) { - dialog.cancel(); - } - }); + .setNegativeButton(android.R.string.cancel, + (dialog, id) -> dialog.cancel()); AlertDialog alertDialog = alertDialogBuilder.create(); alertDialog.show(); + alertDialog.setCanceledOnTouchOutside(false); } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/Round.java b/app/src/main/java/info/nightscout/androidaps/utils/Round.java index ba7f7e3f86b..bc3c5d8b9cd 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/Round.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/Round.java @@ -1,25 +1,43 @@ package info.nightscout.androidaps.utils; +import java.math.BigDecimal; + /** * Created by mike on 20.06.2016. */ public class Round { public static Double roundTo(double x, Double step) { - if (x != 0d) { - return Math.round(x / step) * step; + if (x == 0d) { + return 0d; } - return 0d; + + //Double oldCalc = Math.round(x / step) * step; + Double newCalc = BigDecimal.valueOf(Math.round(x / step)).multiply(BigDecimal.valueOf(step)).doubleValue(); + + // just for the tests, forcing failures + //newCalc = oldCalc; + + return newCalc; } + public static Double floorTo(Double x, Double step) { if (x != 0d) { return Math.floor(x / step) * step; } return 0d; } + public static Double ceilTo(Double x, Double step) { if (x != 0d) { return Math.ceil(x / step) * step; } return 0d; } + + public static boolean isSame(Double d1, Double d2) { + double diff = d1 - d2; + + return (Math.abs(diff) <= 0.000001); + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/SP.java b/app/src/main/java/info/nightscout/androidaps/utils/SP.java index 3b3a8e69072..191fd9421b3 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/SP.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/SP.java @@ -3,6 +3,8 @@ import android.content.SharedPreferences; import android.preference.PreferenceManager; +import java.util.Map; + import info.nightscout.androidaps.MainApp; /** @@ -10,7 +12,15 @@ */ public class SP { - static SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); + private static SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext()); + + static public Map getAll() { + return sharedPreferences.getAll(); + } + + static public void clear() { + sharedPreferences.edit().clear().apply(); + } static public boolean contains(String key) { return sharedPreferences.contains(key); @@ -85,74 +95,51 @@ static public long getLong(String key, Long defaultValue) { } static public void putBoolean(String key, boolean value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putBoolean(key, value); - editor.apply(); + sharedPreferences.edit().putBoolean(key, value).apply(); } static public void putBoolean(int resourceID, boolean value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putBoolean(MainApp.gs(resourceID), value); - editor.apply(); + sharedPreferences.edit().putBoolean(MainApp.gs(resourceID), value).apply(); } static public void putDouble(String key, double value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(key, Double.toString(value)); - editor.apply(); - } - - static public void putDouble(int resourceID, double value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(MainApp.gs(resourceID), Double.toString(value)); - editor.apply(); + sharedPreferences.edit().putString(key, Double.toString(value)).apply(); } static public void putLong(String key, long value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong(key, value); - editor.apply(); + sharedPreferences.edit().putLong(key, value).apply(); } static public void putLong(int resourceID, long value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putLong(MainApp.gs(resourceID), value); - editor.apply(); + sharedPreferences.edit().putLong(MainApp.gs(resourceID), value).apply(); } static public void putInt(String key, int value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putInt(key, value); - editor.apply(); + sharedPreferences.edit().putInt(key, value).apply(); } static public void putInt(int resourceID, int value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putInt(MainApp.gs(resourceID), value); - editor.apply(); + sharedPreferences.edit().putInt(MainApp.gs(resourceID), value).apply(); + } + + static public void incInt(int resourceID) { + int value = SP.getInt(resourceID, 0) + 1; + sharedPreferences.edit().putInt(MainApp.gs(resourceID), value).apply(); } static public void putString(int resourceID, String value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(MainApp.gs(resourceID), value); - editor.apply(); + sharedPreferences.edit().putString(MainApp.gs(resourceID), value).apply(); } static public void putString(String key, String value) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(key, value); - editor.apply(); + sharedPreferences.edit().putString(key, value).apply(); } static public void remove(int resourceID) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.remove(MainApp.gs(resourceID)); - editor.apply(); + sharedPreferences.edit().remove(MainApp.gs(resourceID)).apply(); } static public void remove(String key) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.remove(key); - editor.apply(); + sharedPreferences.edit().remove(key).apply(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java b/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java index eef4ca57fe4..74500ebdbc2 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/SingleClickButton.java @@ -3,7 +3,7 @@ import android.app.Activity; import android.content.Context; import android.os.SystemClock; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; @@ -14,7 +14,7 @@ * Created by mike on 22.12.2017. */ -public class SingleClickButton extends android.support.v7.widget.AppCompatButton implements View.OnClickListener { +public class SingleClickButton extends androidx.appcompat.widget.AppCompatButton implements View.OnClickListener { private static Logger log = LoggerFactory.getLogger(SingleClickButton.class); Context context; diff --git a/app/src/main/java/info/nightscout/androidaps/utils/SntpClient.java b/app/src/main/java/info/nightscout/androidaps/utils/SntpClient.java new file mode 100644 index 00000000000..60de82a93ce --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/SntpClient.java @@ -0,0 +1,233 @@ +package info.nightscout.androidaps.utils; +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.os.SystemClock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import info.nightscout.androidaps.logging.L; + +/** + * {@hide} + *

+ * Simple SNTP client class for retrieving network time. + *

+ * Sample usage: + *

SntpClient client = new SntpClient();
+ * if (client.requestTime("time.foo.com")) {
+ *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
+ * }
+ * 
+ */ +public class SntpClient { + private static Logger log = LoggerFactory.getLogger(L.CORE); + + //private static final int REFERENCE_TIME_OFFSET = 16; + private static final int ORIGINATE_TIME_OFFSET = 24; + private static final int RECEIVE_TIME_OFFSET = 32; + private static final int TRANSMIT_TIME_OFFSET = 40; + private static final int NTP_PACKET_SIZE = 48; + + private static final int NTP_PORT = 123; + private static final int NTP_MODE_CLIENT = 3; + private static final int NTP_VERSION = 3; + + // Number of seconds between Jan 1, 1900 and Jan 1, 1970 + // 70 years plus 17 leap days + private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L; + + // system time computed from NTP server response + private static long mNtpTime; + + // value of SystemClock.elapsedRealtime() corresponding to mNtpTime + private static long mNtpTimeReference; + + // round trip time in milliseconds + private static long mRoundTripTime; + + public static abstract class Callback implements Runnable { + public boolean networkConnected = false; + public boolean success = false; + public long time = 0; + } + + public static synchronized void ntpTime(final Callback callback, boolean isConnected) { + callback.networkConnected = isConnected; + if (callback.networkConnected) { + new Thread(() -> doNtpTime(callback)).start(); + } else { + callback.run(); + } + } + + static void doNtpTime(final Callback callback) { + log.debug("Time detection started"); + callback.success = requestTime("time.google.com", 5000); + callback.time = getNtpTime() + SystemClock.elapsedRealtime() - getNtpTimeReference(); + log.debug("Time detection ended: " + callback.success + " " + DateUtil.dateAndTimeString(getNtpTime())); + callback.run(); + } + + /** + * Sends an SNTP request to the given host and processes the response. + * + * @param host host name of the server. + * @param timeout network timeout in milliseconds. + * @return true if the transaction was successful. + */ + private static synchronized boolean requestTime(String host, int timeout) { + try { + DatagramSocket socket = new DatagramSocket(); + socket.setSoTimeout(timeout); + InetAddress address = InetAddress.getByName(host); + byte[] buffer = new byte[NTP_PACKET_SIZE]; + DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT); + + // set mode = 3 (client) and version = 3 + // mode is in low 3 bits of first byte + // version is in bits 3-5 of first byte + buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3); + + // get current time and write it to the request packet + long requestTime = System.currentTimeMillis(); + long requestTicks = SystemClock.elapsedRealtime(); + writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime); + + socket.send(request); + + // read the response + DatagramPacket response = new DatagramPacket(buffer, buffer.length); + socket.receive(response); + long responseTicks = SystemClock.elapsedRealtime(); + long responseTime = requestTime + (responseTicks - requestTicks); + socket.close(); + + // extract the results + long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET); + long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET); + long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET); + long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime); + // receiveTime = originateTime + transit + skew + // responseTime = transmitTime + transit - skew + // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2 + // = ((originateTime + transit + skew - originateTime) + + // (transmitTime - (transmitTime + transit - skew)))/2 + // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2 + // = (transit + skew - transit + skew)/2 + // = (2 * skew)/2 = skew + long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2; + // if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms"); + // if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms"); + + // save our results - use the times on this side of the network latency + // (response rather than request time) + mNtpTime = responseTime + clockOffset; + mNtpTimeReference = responseTicks; + mRoundTripTime = roundTripTime; + } catch (Exception e) { + log.debug("request time failed: " + e); + return false; + } + + return true; + } + + /** + * Returns the time computed from the NTP transaction. + * + * @return time value computed from NTP server response. + */ + private static long getNtpTime() { + return mNtpTime; + } + + /** + * Returns the reference clock value (value of SystemClock.elapsedRealtime()) + * corresponding to the NTP time. + * + * @return reference clock corresponding to the NTP time. + */ + private static long getNtpTimeReference() { + return mNtpTimeReference; + } + + /** + * Returns the round trip time of the NTP transaction + * + * @return round trip time in milliseconds. + */ + public long getRoundTripTime() { + return mRoundTripTime; + } + + /** + * Reads an unsigned 32 bit big endian number from the given offset in the buffer. + */ + private static long read32(byte[] buffer, int offset) { + byte b0 = buffer[offset]; + byte b1 = buffer[offset + 1]; + byte b2 = buffer[offset + 2]; + byte b3 = buffer[offset + 3]; + + // convert signed bytes to unsigned values + int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0); + int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1); + int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2); + int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3); + + return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8) + (long) i3; + } + + /** + * Reads the NTP time stamp at the given offset in the buffer and returns + * it as a system time (milliseconds since January 1, 1970). + */ + private static long readTimeStamp(byte[] buffer, int offset) { + long seconds = read32(buffer, offset); + long fraction = read32(buffer, offset + 4); + return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L); + } + + /** + * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp + * at the given offset in the buffer. + */ + private static void writeTimeStamp(byte[] buffer, int offset, long time) { + long seconds = time / 1000L; + long milliseconds = time - seconds * 1000L; + seconds += OFFSET_1900_TO_1970; + + // write seconds in big endian format + buffer[offset++] = (byte) (seconds >> 24); + buffer[offset++] = (byte) (seconds >> 16); + buffer[offset++] = (byte) (seconds >> 8); + buffer[offset++] = (byte) (seconds >> 0); + + long fraction = milliseconds * 0x100000000L / 1000L; + // write fraction in big endian format + buffer[offset++] = (byte) (fraction >> 24); + buffer[offset++] = (byte) (fraction >> 16); + buffer[offset++] = (byte) (fraction >> 8); + // low order bits should be random data + buffer[offset++] = (byte) (Math.random() * 255.0); + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java index cad2b762912..74e8da7c9dd 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/StringUtils.java @@ -1,5 +1,8 @@ package info.nightscout.androidaps.utils; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; + /** * class contains useful String functions */ @@ -17,4 +20,12 @@ public static String removeSurroundingQuotes(String string) { return string; } + + public static boolean emptyString(final String str) { + return str == null || str.length() == 0; + } + + public static String formatInsulin(double insulin) { + return String.format(MainApp.gs(R.string.formatinsulinunits), insulin); + } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/T.java b/app/src/main/java/info/nightscout/androidaps/utils/T.java index 2a9bcfc42c6..5671f0725c9 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/T.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/T.java @@ -43,6 +43,12 @@ public static T days(long day) { return t; } + public static T months(long month) { + T t = new T(); + t.time = month * 31 * 24 * 60 * 60 * 1000L; + return t; + } + public long msecs() { return time; } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TIR.kt b/app/src/main/java/info/nightscout/androidaps/utils/TIR.kt new file mode 100644 index 00000000000..5f2ea9bbaff --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/TIR.kt @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.utils + +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import kotlin.math.roundToInt + +class TIR(val date: Long, val lowThreshold: Double, val highThreshold: Double) { + internal var below = 0 + internal var inRange = 0 + internal var above = 0 + internal var error = 0 + internal var count = 0 + + fun error() = run { error++ } + fun below() = run { below++; count++ } + fun inRange() = run { inRange++; count++ } + fun above() = run { above++; count++ } + + fun belowPct() = if (count > 0) (below.toDouble() / count * 100.0).roundToInt() else 0 + fun inRangePct() = if (count > 0) (inRange.toDouble() / count * 100.0).roundToInt() else 0 + fun abovePct() = if (count > 0) (above.toDouble() / count * 100.0).roundToInt() else 0 + + fun toText(): String = MainApp.gs(R.string.tirformat, DateUtil.dateStringShort(date), belowPct(), inRangePct(), abovePct()) + + fun toText(days: Int): String = MainApp.gs(R.string.tirformat, "%02d".format(days) + " " + MainApp.gs(R.string.days), belowPct(), inRangePct(), abovePct()) +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/TddCalculator.kt new file mode 100644 index 00000000000..0210247eb3e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/TddCalculator.kt @@ -0,0 +1,81 @@ +package info.nightscout.androidaps.utils + +import android.text.Spanned +import android.util.LongSparseArray +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.db.TDD +import info.nightscout.androidaps.logging.L +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import org.slf4j.LoggerFactory + +object TddCalculator : TreatmentsPlugin() { + private val log = LoggerFactory.getLogger(L.DATATREATMENTS) + + fun calculate(days: Long): LongSparseArray { + val range = T.days(days + 1).msecs() + val startTime = MidnightTime.calc(DateUtil.now()) - T.days(days).msecs() + val endTime = MidnightTime.calc(DateUtil.now()) + initializeData(range) + + val result = LongSparseArray() + for (t in treatmentsFromHistory) { + if (!t.isValid) continue + if (t.date < startTime || t.date > endTime) continue + val midnight = MidnightTime.calc(t.date) + val tdd = result[midnight] ?: TDD(midnight, 0.0, 0.0, 0.0) + tdd.bolus += t.insulin + result.put(midnight, tdd) + } + + for (t in startTime until endTime step T.mins(5).msecs()) { + val midnight = MidnightTime.calc(t) + val tdd = result[midnight] ?: TDD(midnight, 0.0, 0.0, 0.0) + val tbr = getTempBasalFromHistory(t) + val profile = ProfileFunctions.getInstance().getProfile(t) ?: continue + val absoluteRate = tbr?.tempBasalConvertedToAbsolute(t, profile) ?: profile.getBasal(t) + tdd.basal += absoluteRate / 60.0 * 5.0 + result.put(midnight, tdd) + } + for (i in 0 until result.size()) { + val tdd = result.valueAt(i) + tdd.total = tdd.bolus + tdd.basal + } + log.debug(result.toString()) + return result + } + + fun averageTDD(tdds: LongSparseArray): TDD { + val totalTdd = TDD() + for (i in 0 until tdds.size()) { + val tdd = tdds.valueAt(i) + totalTdd.basal += tdd.basal + totalTdd.bolus += tdd.bolus + totalTdd.total += tdd.total + } + totalTdd.basal /= tdds.size().toDouble() + totalTdd.bolus /= tdds.size().toDouble() + totalTdd.total /= tdds.size().toDouble() + return totalTdd + } + + fun stats(): Spanned { + val tdds = calculate(7) + val averageTdd = averageTDD(tdds) + return HtmlHelper.fromHtml( + "" + MainApp.gs(R.string.tdd) + ":
" + + toText(tdds) + + "" + MainApp.gs(R.string.average) + ":
" + + averageTdd.toText(tdds.size()) + ) + } + + fun toText(tdds: LongSparseArray): String { + var t = "" + for (i in 0 until tdds.size()) { + t += "${tdds.valueAt(i).toText()}
" + } + return t + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TimeChangeType.java b/app/src/main/java/info/nightscout/androidaps/utils/TimeChangeType.java new file mode 100644 index 00000000000..720ecc1a1b8 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/TimeChangeType.java @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.utils; + +public enum TimeChangeType { + TimezoneChange, + DST_Started, + DST_Ended, + ManualTimeChange +} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java b/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java index d120e102d18..0143377bb2d 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/TimeListEdit.java @@ -1,9 +1,6 @@ package info.nightscout.androidaps.utils; import android.content.Context; -import android.os.Handler; -import android.support.v4.content.ContextCompat; -import android.support.v4.widget.TextViewCompat; import android.text.Editable; import android.text.TextWatcher; import android.view.Gravity; @@ -15,6 +12,10 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.widget.TextViewCompat; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -24,6 +25,7 @@ import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; +import java.util.List; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; @@ -48,6 +50,7 @@ public class TimeListEdit { private Context context; private View view; private int resLayoutId; + private String tagPrefix; private String label; private JSONArray data1; private JSONArray data2; @@ -61,10 +64,11 @@ public class TimeListEdit { private int inflatedUntil = -1; - public TimeListEdit(Context context, View view, int resLayoutId, String label, JSONArray data1, JSONArray data2, double min, double max, double step, NumberFormat formatter, Runnable save) { + public TimeListEdit(Context context, View view, int resLayoutId, String tagPrefix, String label, JSONArray data1, JSONArray data2, double min, double max, double step, NumberFormat formatter, Runnable save) { this.context = context; this.view = view; this.resLayoutId = resLayoutId; + this.tagPrefix = tagPrefix; this.label = label; this.data1 = data1; this.data2 = data2; @@ -77,16 +81,16 @@ public TimeListEdit(Context context, View view, int resLayoutId, String label, J } private void buildView() { - layout = (LinearLayout) view.findViewById(resLayoutId); - layout.removeAllViews(); + layout = view.findViewById(resLayoutId); + layout.removeAllViewsInLayout(); textlabel = new TextView(context); textlabel.setText(label); - textlabel.setGravity(Gravity.START); + textlabel.setGravity(Gravity.CENTER); LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - llp.setMargins(10, 0, 0, 0); // llp.setMargins(left, top, right, bottom); + llp.setMargins(0, 5, 0, 5); textlabel.setLayoutParams(llp); - textlabel.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.linearBlockBackground)); + //textlabel.setBackgroundColor(ContextCompat.getColor(MainApp.instance(), R.color.linearBlockBackground)); TextViewCompat.setTextAppearance(textlabel, android.R.style.TextAppearance_Medium); layout.addView(textlabel); @@ -96,72 +100,64 @@ private void buildView() { } // last "plus" to append new interval + float factor = layout.getContext().getResources().getDisplayMetrics().density; finalAdd = new ImageView(context); finalAdd.setImageResource(R.drawable.add); - LinearLayout.LayoutParams illp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + LinearLayout.LayoutParams illp = new LinearLayout.LayoutParams((int) (35d * factor), (int) (35 * factor)); illp.setMargins(0, 25, 0, 25); // llp.setMargins(left, top, right, bottom); illp.gravity = Gravity.CENTER; layout.addView(finalAdd); finalAdd.setLayoutParams(illp); - finalAdd.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - addItem(itemsCount(), itemsCount() > 0 ? secondFromMidnight(itemsCount() - 1) + ONEHOURINSECONDS : 0, 0, 0); - callSave(); - log(); - fillView(); - } + finalAdd.setOnClickListener(view -> { + addItem(itemsCount(), itemsCount() > 0 ? secondFromMidnight(itemsCount() - 1) + ONEHOURINSECONDS : 0, 0, 0); + callSave(); + log(); + fillView(); }); fillView(); } - private void inflateRow(int i) { + private void inflateRow(final int position) { LayoutInflater inflater = LayoutInflater.from(context); - View childview = intervals[i] = inflater.inflate(R.layout.timelistedit_element, layout, false); - spinners[i] = new SpinnerHelper(childview.findViewById(R.id.timelistedit_time)); - numberPickers1[i] = (NumberPicker) childview.findViewById(R.id.timelistedit_edit1); - numberPickers2[i] = (NumberPicker) childview.findViewById(R.id.timelistedit_edit2); - addButtons[i] = (ImageView) childview.findViewById(R.id.timelistedit_add); - removeButtons[i] = (ImageView) childview.findViewById(R.id.timelistedit_remove); - - final int fixedPos = i; - addButtons[i].setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - int seconds = secondFromMidnight(fixedPos); - addItem(fixedPos, seconds, 0, 0); - // for here for the rest of values - for (int i = fixedPos + 1; i < itemsCount(); i++) { - if (secondFromMidnight(i - 1) >= secondFromMidnight(i)) { - editItem(i, secondFromMidnight(i - 1) + ONEHOURINSECONDS, value1(i), value2(i)); - } + int resource = data2 == null ? R.layout.timelistedit_element : R.layout.timelistedit_element_vertical; + View childView = intervals[position] = inflater.inflate(resource, layout, false); + spinners[position] = new SpinnerHelper(childView.findViewById(R.id.timelistedit_time)); + numberPickers1[position] = childView.findViewById(R.id.timelistedit_edit1); + numberPickers2[position] = childView.findViewById(R.id.timelistedit_edit2); + addButtons[position] = childView.findViewById(R.id.timelistedit_add); + removeButtons[position] = childView.findViewById(R.id.timelistedit_remove); + + addButtons[position].setOnClickListener(view -> { + int seconds = secondFromMidnight(position); + addItem(position, seconds, 0, 0); + // for here for the rest of values + for (int i = position + 1; i < itemsCount(); i++) { + if (secondFromMidnight(i - 1) >= secondFromMidnight(i)) { + editItem(i, secondFromMidnight(i - 1) + ONEHOURINSECONDS, value1(i), value2(i)); } - while (itemsCount() > 24 || secondFromMidnight(itemsCount() - 1) > 23 * ONEHOURINSECONDS) - removeItem(itemsCount() - 1); - callSave(); - log(); - fillView(); } + while (itemsCount() > 24 || secondFromMidnight(itemsCount() - 1) > 23 * ONEHOURINSECONDS) + removeItem(itemsCount() - 1); + callSave(); + log(); + fillView(); }); - removeButtons[i].setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - removeItem(fixedPos); - callSave(); - log(); - fillView(); - } + removeButtons[position].setOnClickListener(view -> { + removeItem(position); + callSave(); + log(); + fillView(); }); - spinners[i].setOnItemSelectedListener( + spinners[position].setOnItemSelectedListener( new AdapterView.OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - int seconds = DateUtil.toSeconds(spinners[fixedPos].getSelectedItem().toString()); - editItem(fixedPos, seconds, value1(fixedPos), value2(fixedPos)); + public void onItemSelected(AdapterView parent, View view, int selected, long id) { + int seconds = ((SpinnerAdapter) spinners[position].getAdapter()).valueForPosition(selected); + editItem(position, seconds, value1(position), value2(position)); log(); callSave(); fillView(); @@ -173,30 +169,30 @@ public void onNothingSelected(AdapterView parent) { } ); - numberPickers1[i].setTextWatcher(new TextWatcher() { - @Override - public void afterTextChanged(Editable s) { - editItem(fixedPos, secondFromMidnight(fixedPos), SafeParse.stringToDouble(numberPickers1[fixedPos].getText()), value2(fixedPos)); - callSave(); - log(); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, - int count, int after) { - } + numberPickers1[position].setTextWatcher(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + editItem(position, secondFromMidnight(position), SafeParse.stringToDouble(numberPickers1[position].getText()), value2(position)); + callSave(); + log(); + } - @Override - public void onTextChanged(CharSequence s, int start, - int before, int count) { - } - }); + @Override + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + } + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) { + } + }); + numberPickers1[position].setTag(tagPrefix +"-1-" + position); - numberPickers2[i].setTextWatcher(new TextWatcher() { + numberPickers2[position].setTextWatcher(new TextWatcher() { @Override public void afterTextChanged(Editable s) { - editItem(fixedPos, secondFromMidnight(fixedPos), value1(fixedPos), SafeParse.stringToDouble(numberPickers2[fixedPos].getText())); + editItem(position, secondFromMidnight(position), value1(position), SafeParse.stringToDouble(numberPickers2[position].getText())); callSave(); log(); } @@ -211,8 +207,9 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } }); + numberPickers2[position].setTag(tagPrefix +"-2-" + position); - layout.addView(childview); + layout.addView(childView); } private void fillView() { @@ -220,7 +217,7 @@ private void fillView() { if (i < itemsCount()) { intervals[i].setVisibility(View.VISIBLE); buildInterval(i); - } else if (i <= inflatedUntil){ + } else if (i <= inflatedUntil) { intervals[i].setVisibility(View.GONE); } } @@ -232,9 +229,8 @@ private void fillView() { } } - private View buildInterval(int i) { + private void buildInterval(int i) { SpinnerHelper timeSpinner = spinners[i]; - View childview = intervals[i]; final NumberPicker editText1 = numberPickers1[i]; final NumberPicker editText2 = numberPickers2[i]; @@ -244,8 +240,8 @@ private View buildInterval(int i) { if (i == 0) next = ONEHOURINSECONDS; fillSpinner(timeSpinner, secondFromMidnight(i), previous, next); - editText1.setParams(value1(i), min, max, step, formatter, false); - editText2.setParams(value2(i), min, max, step, formatter, false); + editText1.setParams(value1(i), min, max, step, formatter, false,null); + editText2.setParams(value2(i), min, max, step, formatter, false, null); if (data2 == null) { editText2.setVisibility(View.GONE); @@ -263,29 +259,38 @@ private View buildInterval(int i) { addButtons[i].setVisibility(View.VISIBLE); } - return childview; + } + + class SpinnerAdapter extends ArrayAdapter { + List values; + + SpinnerAdapter(@NonNull Context context, int resource, final @NonNull List objects, final @NonNull List values) { + super(context, resource, objects); + this.values = values; + } + + int valueForPosition(int position) { + return values.get(position); + } } private void fillSpinner(final SpinnerHelper spinner, int secondsFromMidnight, int previous, int next) { int posInList = 0; ArrayList timeList = new ArrayList<>(); + ArrayList timeListValues = new ArrayList<>(); int pos = 0; for (int t = previous + ONEHOURINSECONDS; t < next; t += ONEHOURINSECONDS) { timeList.add(DateUtil.timeStringFromSeconds(t)); + timeListValues.add(t); if (secondsFromMidnight == t) posInList = pos; pos++; } - final ArrayAdapter adapter = new ArrayAdapter<>(context, - R.layout.spinner_centered, timeList); + final SpinnerAdapter adapter = new SpinnerAdapter(context, + R.layout.spinner_centered, timeList, timeListValues); spinner.setAdapter(adapter); - final int finalPosInList = posInList; - new Handler().postDelayed(new Runnable() { - public void run() { - spinner.setSelection(finalPosInList, false); - adapter.notifyDataSetChanged(); - } - }, 100); + spinner.setSelection(posInList, false); + adapter.notifyDataSetChanged(); } private int itemsCount() { @@ -350,7 +355,7 @@ private void editItem(int index, int timeAsSeconds, double value1, double value2 data1.put(index, newObject1); if (data2 != null) { JSONObject newObject2 = new JSONObject(); - newObject1.put("time", time); + newObject2.put("time", time); newObject2.put("timeAsSeconds", timeAsSeconds); newObject2.put("value", value2); data2.put(index, newObject2); @@ -362,7 +367,7 @@ private void editItem(int index, int timeAsSeconds, double value1, double value2 } private void addItem(int index, int timeAsSeconds, double value1, double value2) { - if(itemsCount()>inflatedUntil) { + if (itemsCount() > inflatedUntil) { layout.removeView(finalAdd); inflateRow(++inflatedUntil); layout.addView(finalAdd); @@ -389,10 +394,8 @@ private void removeItem(int index) { } private void log() { - if (log.isDebugEnabled()) { - for (int i = 0; i < data1.length(); i++) { - log.debug(i + ": @" + DateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + (data2 != null ? " " + value2(i) : "")); - } + for (int i = 0; i < data1.length(); i++) { + log.debug(i + ": @" + DateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + (data2 != null ? " " + value2(i) : "")); } } @@ -400,9 +403,9 @@ private void callSave() { if (save != null) save.run(); } - public void updateLabel(String txt){ + public void updateLabel(String txt) { this.label = txt; - if(textlabel!=null) + if (textlabel != null) textlabel.setText(txt); } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/TirCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/TirCalculator.kt new file mode 100644 index 00000000000..4e41f9a331e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/TirCalculator.kt @@ -0,0 +1,85 @@ +package info.nightscout.androidaps.utils + +import android.text.Spanned +import android.util.LongSparseArray +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.Profile + +object TirCalculator { + fun calculate(days: Long, lowMgdl: Double, highMgdl: Double): LongSparseArray { + if (lowMgdl < 39) throw RuntimeException("Low below 39") + if (lowMgdl > highMgdl) throw RuntimeException("Low > High") + val startTime = MidnightTime.calc(DateUtil.now()) - T.days(days).msecs() + val endTime = MidnightTime.calc(DateUtil.now()) + + val bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime(startTime, endTime, true) + val result = LongSparseArray() + for (bg in bgReadings) { + val midnight = MidnightTime.calc(bg.date) + var tir = result[midnight] + if (tir == null) { + tir = TIR(midnight, lowMgdl, highMgdl) + result.append(midnight, tir) + } + if (bg.value < 39) tir.error() + if (bg.value >= 39 && bg.value < lowMgdl) tir.below() + if (bg.value in lowMgdl..highMgdl) tir.inRange() + if (bg.value > highMgdl) tir.above() + } + return result + } + + fun averageTIR(tirs: LongSparseArray): TIR { + val totalTir = if (tirs.size() > 0) { + TIR(tirs.valueAt(0).date, tirs.valueAt(0).lowThreshold, tirs.valueAt(0).highThreshold) + } else { + TIR(7, 70.0, 180.0) + } + for (i in 0 until tirs.size()) { + val tir = tirs.valueAt(i) + totalTir.below += tir.below + totalTir.inRange += tir.inRange + totalTir.above += tir.above + totalTir.error += tir.error + totalTir.count += tir.count + } + return totalTir + } + + fun stats(): Spanned { + val lowTirMgdl = Constants.STATS_RANGE_LOW_MMOL * Constants.MMOLL_TO_MGDL + val highTirMgdl = Constants.STATS_RANGE_HIGH_MMOL * Constants.MMOLL_TO_MGDL + val lowTitMgdl = Constants.STATS_TARGET_LOW_MMOL * Constants.MMOLL_TO_MGDL + val highTitMgdl = Constants.STATS_TARGET_HIGH_MMOL * Constants.MMOLL_TO_MGDL + + val tir7 = calculate(7, lowTirMgdl, highTirMgdl) + val averageTir7 = averageTIR(tir7) + val tir30 = calculate(30, lowTirMgdl, highTirMgdl) + val averageTir30 = averageTIR(tir30) + val tit7 = calculate(7, lowTitMgdl, highTitMgdl) + val averageTit7 = averageTIR(tit7) + val tit30 = calculate(30, lowTitMgdl, highTitMgdl) + val averageTit30 = averageTIR(tit30) + return HtmlHelper.fromHtml( + "
" + MainApp.gs(R.string.tir) + ":
" + + toText(tir7) + + "
" + MainApp.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(lowTirMgdl) + "-" + Profile.toCurrentUnitsString(highTirMgdl) + "):
" + + averageTir7.toText(tir7.size()) + "
" + + averageTir30.toText(tir30.size()) + + "
" + MainApp.gs(R.string.average) + " (" + Profile.toCurrentUnitsString(lowTitMgdl) + "-" + Profile.toCurrentUnitsString(highTitMgdl) + "):
" + + averageTit7.toText(tit7.size()) + "
" + + averageTit30.toText(tit30.size()) + ) + } + + fun toText(tirs: LongSparseArray): String { + var t = "" + for (i in 0 until tirs.size()) { + t += "${tirs.valueAt(i).toText()}
" + } + return t + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java index d85b1510a0d..fe596d8f080 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java @@ -7,21 +7,19 @@ import android.widget.Toast; import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; +import info.nightscout.androidaps.plugins.bus.RxBus; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; public class ToastUtils { - public static void showToastInUiThread(final Context ctx, - final String string) { + public static void showToastInUiThread(final Context ctx, final int stringId) { + showToastInUiThread(ctx, MainApp.gs(stringId)); + } + public static void showToastInUiThread(final Context ctx, final String string) { Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(new Runnable() { - @Override - public void run() { - Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show(); - } - }); + mainThread.post(() -> Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show()); } public static void showToastInUiThread(final Context ctx, @@ -29,23 +27,13 @@ public static void showToastInUiThread(final Context ctx, showToastInUiThread(ctx, string); playSound(ctx, soundID); - new Thread(new Runnable() { - @Override - public void run() { - Notification notification = new Notification(Notification.TOAST_ALARM, string, Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - } - }).start(); + Notification notification = new Notification(Notification.TOAST_ALARM, string, Notification.URGENT); + RxBus.INSTANCE.send(new EventNewNotification(notification)); } private static void playSound(final Context ctx, final int soundID) { final MediaPlayer soundMP = MediaPlayer.create(ctx, soundID); soundMP.start(); - soundMP.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - mp.release(); - } - }); + soundMP.setOnCompletionListener(MediaPlayer::release); } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/Translator.java b/app/src/main/java/info/nightscout/androidaps/utils/Translator.java index ee83f856e4d..e25c083a7b0 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/Translator.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/Translator.java @@ -33,6 +33,8 @@ public static String translate(String text) { return MainApp.gs(R.string.careportal_exercise); case "Site Change": return MainApp.gs(R.string.careportal_pumpsitechange); + case "Pump Battery Change": + return MainApp.gs(R.string.careportal_pumpbatterychange); case "Sensor Start": return MainApp.gs(R.string.careportal_cgmsensorstart); case "Sensor Change": diff --git a/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt new file mode 100644 index 00000000000..99c4a4af31e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt @@ -0,0 +1,10 @@ +package info.nightscout.androidaps.utils + +import android.view.View + +/** + * Created by adrian on 2019-12-20. + */ + +fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE + diff --git a/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java b/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java index 99961a156b4..4354ccd5284 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/XdripCalibrations.java @@ -1,11 +1,9 @@ package info.nightscout.androidaps.utils; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.support.v7.app.AlertDialog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,20 +23,10 @@ public class XdripCalibrations { private static Logger log = LoggerFactory.getLogger(XdripCalibrations.class); - public static void confirmAndSendCalibration(final Double bg, Context parentContext) { + public static void confirmAndSendCalibration(final Double bg, final Context parentContext) { if (parentContext != null) { String confirmMessage = String.format(MainApp.gs(R.string.send_calibration), bg); - - AlertDialog.Builder builder = new AlertDialog.Builder(parentContext); - builder.setTitle(MainApp.gs(R.string.confirmation)); - builder.setMessage(confirmMessage); - builder.setPositiveButton(MainApp.gs(R.string.ok), new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - sendIntent(bg); - } - }); - builder.setNegativeButton(MainApp.gs(R.string.cancel), null); - builder.show(); + OKDialog.showConfirmation(parentContext, confirmMessage, () -> sendIntent(bg)); } } @@ -46,7 +34,7 @@ public static boolean sendIntent(Double bg) { Context context = MainApp.instance().getApplicationContext(); Bundle bundle = new Bundle(); bundle.putDouble("glucose_number", bg); - bundle.putString("units", ProfileFunctions.getInstance().getProfileUnits().equals(Constants.MGDL) ? "mgdl" : "mmol"); + bundle.putString("units", ProfileFunctions.getSystemUnits().equals(Constants.MGDL) ? "mgdl" : "mmol"); bundle.putLong("timestamp", System.currentTimeMillis()); Intent intent = new Intent(Intents.ACTION_REMOTE_CALIBRATION); intent.putExtras(bundle); diff --git a/app/src/main/res/drawable-anydpi/ic_keyboard_capslock.xml b/app/src/main/res/drawable-anydpi/ic_keyboard_capslock.xml new file mode 100644 index 00000000000..db4409f2b8d --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_keyboard_capslock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_keyboard_tab.xml b/app/src/main/res/drawable-anydpi/ic_keyboard_tab.xml new file mode 100644 index 00000000000..03ec6ed69f3 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_keyboard_tab.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_location_on.xml b/app/src/main/res/drawable-anydpi/ic_location_on.xml new file mode 100644 index 00000000000..1017cbc8d27 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_location_on.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-anydpi/ic_network_wifi.xml b/app/src/main/res/drawable-anydpi/ic_network_wifi.xml new file mode 100644 index 00000000000..c5ae21ec891 --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_network_wifi.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable-anydpi/ic_notifications.xml b/app/src/main/res/drawable-anydpi/ic_notifications.xml new file mode 100644 index 00000000000..b655d741e6d --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_notifications.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-mdpi-v11/refresh.xml b/app/src/main/res/drawable-mdpi-v11/refresh.xml new file mode 100644 index 00000000000..677809a8c2a --- /dev/null +++ b/app/src/main/res/drawable-mdpi-v11/refresh.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/as.png b/app/src/main/res/drawable/as.png new file mode 100644 index 00000000000..a37e4c34650 Binary files /dev/null and b/app/src/main/res/drawable/as.png differ diff --git a/app/src/main/res/drawable/border_automation_unit.xml b/app/src/main/res/drawable/border_automation_unit.xml new file mode 100644 index 00000000000..eb856562956 --- /dev/null +++ b/app/src/main/res/drawable/border_automation_unit.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/clone.png b/app/src/main/res/drawable/clone.png new file mode 100644 index 00000000000..82fd90cca8f Binary files /dev/null and b/app/src/main/res/drawable/clone.png differ diff --git a/app/src/main/res/drawable/ic_access_alarm_24dp.xml b/app/src/main/res/drawable/ic_access_alarm_24dp.xml new file mode 100644 index 00000000000..3e1d84e037d --- /dev/null +++ b/app/src/main/res/drawable/ic_access_alarm_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_orange_48dp.xml b/app/src/main/res/drawable/ic_action_orange_48dp.xml new file mode 100644 index 00000000000..07e8c306a8a --- /dev/null +++ b/app/src/main/res/drawable/ic_action_orange_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 00000000000..0258249cc48 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml new file mode 100644 index 00000000000..5304b93e183 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bluetooth_white_48dp.xml b/app/src/main/res/drawable/ic_bluetooth_white_48dp.xml new file mode 100644 index 00000000000..f5f2fa4797b --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth_white_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_while_48dp.xml b/app/src/main/res/drawable/ic_check_while_48dp.xml new file mode 100644 index 00000000000..2c136685ecd --- /dev/null +++ b/app/src/main/res/drawable/ic_check_while_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_error_red_48dp.xml b/app/src/main/res/drawable/ic_error_red_48dp.xml new file mode 100644 index 00000000000..b3666f2662f --- /dev/null +++ b/app/src/main/res/drawable/ic_error_red_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml b/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml new file mode 100644 index 00000000000..4a469646bdf --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_circle_outline_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml b/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml new file mode 100644 index 00000000000..9997bf20346 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000000..8229a9a64c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_reorder_gray_24dp.xml b/app/src/main/res/drawable/ic_reorder_gray_24dp.xml new file mode 100644 index 00000000000..8c12d67379c --- /dev/null +++ b/app/src/main/res/drawable/ic_reorder_gray_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_replay_24dp.xml b/app/src/main/res/drawable/ic_replay_24dp.xml new file mode 100644 index 00000000000..32942990ac6 --- /dev/null +++ b/app/src/main/res/drawable/ic_replay_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_stop_24dp.xml b/app/src/main/res/drawable/ic_stop_24dp.xml new file mode 100644 index 00000000000..88299804a8e --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_trash_outline.xml b/app/src/main/res/drawable/ic_trash_outline.xml new file mode 100644 index 00000000000..a411c394e09 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_outline.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_trending_flat_white_48dp.xml b/app/src/main/res/drawable/ic_trending_flat_white_48dp.xml new file mode 100644 index 00000000000..a9bd7800278 --- /dev/null +++ b/app/src/main/res/drawable/ic_trending_flat_white_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_trigger_green_48dp.xml b/app/src/main/res/drawable/ic_trigger_green_48dp.xml new file mode 100644 index 00000000000..1bfc8d3e0d2 --- /dev/null +++ b/app/src/main/res/drawable/ic_trigger_green_48dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/icon_auto_delta.png b/app/src/main/res/drawable/icon_auto_delta.png new file mode 100644 index 00000000000..ddb0930b0b7 Binary files /dev/null and b/app/src/main/res/drawable/icon_auto_delta.png differ diff --git a/app/src/main/res/drawable/launch_screen.xml b/app/src/main/res/drawable/launch_screen.xml new file mode 100644 index 00000000000..543fd899298 --- /dev/null +++ b/app/src/main/res/drawable/launch_screen.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/splash_icon.png b/app/src/main/res/drawable/splash_icon.png new file mode 100644 index 00000000000..2c0ee32c1bb Binary files /dev/null and b/app/src/main/res/drawable/splash_icon.png differ diff --git a/app/src/main/res/layout/actions_fill_dialog.xml b/app/src/main/res/layout/actions_fill_dialog.xml deleted file mode 100644 index 27fdb02ee77..00000000000 --- a/app/src/main/res/layout/actions_fill_dialog.xml +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -