diff --git a/.travis.yml b/.travis.yml index 52552b3ceb..73fe4b80d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,15 @@ android: components: - platform-tools - tools - - build-tools-26.0.2 + - build-tools-27.0.2 - android-23 - extra-google-m2repository - extra-android-m2repository - extra-google-google_play_services +before_install: +- yes | sdkmanager "platforms;android-27" + script: # Unit Test - ./gradlew test jacocoTestReport diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..db66ffec38 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,10 @@ +Reporting bugs +-------------- +- Note the precise time the problem occurred and describe the circumstances and steps that caused + the problem +- Note the Build version (found in the About dialog in the app, when pressing the three dots in the + upper-right corner). +- Obtain the app's log files, which can be found on the phone in + _/storage/emulated/0/Android/data/info.nightscout.androidaps/_ + See https://github.com/MilosKozak/AndroidAPS/wiki/Accessing-logfiles +- Open an issue at https://github.com/MilosKozak/AndroidAPS/issues/new \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3289276b59..b83ba004c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,9 +12,10 @@ buildscript { apply plugin: "com.android.application" apply plugin: "io.fabric" apply plugin: "jacoco-android" +apply plugin: 'com.jakewharton.butterknife' ext { - supportLibraryVersion = "23.4.0" + supportLibraryVersion = "27.0.2" ormLiteVersion = "4.46" powermockVersion = "1.7.3" dexmakerVersion = "1.2" @@ -47,8 +48,8 @@ def generateGitBuild = { -> } android { - compileSdkVersion 23 - buildToolsVersion "26.0.2" + compileSdkVersion 27 + buildToolsVersion "${supportLibraryVersion}" defaultConfig { applicationId "info.nightscout.androidaps" @@ -56,7 +57,7 @@ android { targetSdkVersion 23 multiDexEnabled true versionCode 1500 - version "1.57-dev" + version "1.59-dev" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", generateGitBuild() @@ -65,6 +66,13 @@ android { } } lintOptions { + // TODO remove once wear dependency com.google.android.gms:play-services-wearable:7.3.0 + // has been upgraded (requiring significant code changes), which currently fails release + // build with a deprecation warning + //abortOnError false + // (disabled entirely to avoid reports on the error, which would still be displayed + // and it's easy to overlook that it's ignored) + checkReleaseBuilds false disable 'MissingTranslation' disable 'ExtraTranslation' } @@ -206,6 +214,9 @@ dependencies { compile "net.danlew:android.joda:2.9.9.1" + api "com.jakewharton:butterknife:8.8.1" + annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1" + testCompile "junit:junit:4.12" testCompile "org.json:json:20140107" testCompile "org.mockito:mockito-core:2.7.22" diff --git a/app/src/main/assets/OpenAPSSMB/basal-set-temp.js b/app/src/main/assets/OpenAPSSMB/basal-set-temp.js new file mode 100644 index 0000000000..1686ea2c47 --- /dev/null +++ b/app/src/main/assets/OpenAPSSMB/basal-set-temp.js @@ -0,0 +1,61 @@ +'use strict'; + +function reason(rT, msg) { + rT.reason = (rT.reason ? rT.reason + '. ' : '') + msg; + console.error(msg); +} + +var tempBasalFunctions = {}; + +tempBasalFunctions.getMaxSafeBasal = function getMaxSafeBasal(profile) { + + var max_daily_safety_multiplier = (isNaN(profile.max_daily_safety_multiplier) || profile.max_daily_safety_multiplier == null) ? 3 : profile.max_daily_safety_multiplier; + var current_basal_safety_multiplier = (isNaN(profile.current_basal_safety_multiplier) || profile.current_basal_safety_multiplier == null) ? 4 : profile.current_basal_safety_multiplier; + + return Math.min(profile.max_basal, max_daily_safety_multiplier * profile.max_daily_basal, current_basal_safety_multiplier * profile.current_basal); +}; + +tempBasalFunctions.setTempBasal = function setTempBasal(rate, duration, profile, rT, currenttemp) { + //var maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * profile.current_basal); + + var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); +var round_basal = require('./round-basal'); + + if (rate < 0) { + rate = 0; + } // if >30m @ 0 required, zero temp will be extended to 30m instead + else if (rate > maxSafeBasal) { + rate = maxSafeBasal; + } + + var suggestedRate = round_basal(rate, profile); + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && typeof(currenttemp.rate) !== 'undefined' && currenttemp.duration > (duration-10) && currenttemp.duration <= 120 && suggestedRate <= currenttemp.rate * 1.2 && suggestedRate >= currenttemp.rate * 0.8) { + rT.reason += " "+currenttemp.duration+"m left and " + currenttemp.rate + " ~ req " + suggestedRate + "U/hr: no temp required"; + return rT; + } + + if (suggestedRate === profile.current_basal) { + if (profile.skip_neutral_temps) { + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && currenttemp.duration > 0) { + reason(rT, 'Suggested rate is same as profile rate, a temp basal is active, canceling current temp'); + rT.duration = 0; + rT.rate = 0; + return rT; + } else { + reason(rT, 'Suggested rate is same as profile rate, no temp basal is active, doing nothing'); + return rT; + } + } else { + reason(rT, 'Setting neutral temp basal of ' + profile.current_basal + 'U/hr'); + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } + } else { + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } +}; + +module.exports = tempBasalFunctions; \ No newline at end of file diff --git a/app/src/main/assets/OpenAPSSMB/determine-basal.js b/app/src/main/assets/OpenAPSSMB/determine-basal.js new file mode 100644 index 0000000000..84bd684cf6 --- /dev/null +++ b/app/src/main/assets/OpenAPSSMB/determine-basal.js @@ -0,0 +1,1146 @@ +/* + Determine Basal + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + + +var round_basal = require('../round-basal') + +// Rounds value to 'digits' decimal places +function round(value, digits) +{ + if (! digits) { digits = 0; } + var scale = Math.pow(10, digits); + return Math.round(value * scale) / scale; +} + +// we expect BG to rise or fall at the rate of BGI, +// adjusted by the rate at which BG would need to rise / +// fall to get eventualBG to target over 2 hours +function calculate_expected_delta(target_bg, eventual_bg, bgi) { + // (hours * mins_per_hour) / 5 = how many 5 minute periods in 2h = 24 + var five_min_blocks = (2 * 60) / 5; + var target_delta = target_bg - eventual_bg; + var expectedDelta = round(bgi + (target_delta / five_min_blocks), 1); + return expectedDelta; +} + + +function convert_bg(value, profile) +{ + if (profile.out_units == "mmol/L") + { + return round(value / 18, 1).toFixed(1); + } + else + { + return Math.round(value); + } +} + +var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data) { + var rT = {}; //short for requestedTemp + + var deliverAt = new Date(); + + if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') { + rT.error ='Error: could not get current basal rate'; + return rT; + } + var profile_current_basal = round_basal(profile.current_basal, profile); + var basal = profile_current_basal; + + var systemTime = new Date(); + var bgTime = new Date(glucose_status.date); + var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1); + + var bg = glucose_status.glucose; + if (bg < 39) { //Dexcom is in ??? mode or calibrating + rT.reason = "CGM is calibrating or in ??? state"; + } + if (minAgo > 12 || minAgo < -5) { // Dexcom data is too old, or way in the future + rT.reason = "If current system time "+systemTime+" is correct, then BG data is too old. The last BG data was read "+minAgo+"m ago at "+bgTime; + } + if (bg < 39 || minAgo > 12 || minAgo < -5) { + if (currenttemp.rate >= basal) { // high temp is running + rT.reason += ". Canceling high temp basal of "+currenttemp.rate; + rT.deliverAt = deliverAt; + rT.temp = 'absolute'; + rT.duration = 0; + rT.rate = 0; + return rT; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } else if ( currenttemp.rate == 0 && currenttemp.duration > 30 ) { //shorten long zero temps to 30m + rT.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. "; + rT.deliverAt = deliverAt; + rT.temp = 'absolute'; + rT.duration = 30; + rT.rate = 0; + return rT; + //return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); + } else { //do nothing. + rT.reason += ". Temp " + currenttemp.rate + " <= current basal " + basal + "U/hr; doing nothing. "; + return rT; + } + } + + var max_iob = profile.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver + + // if min and max are set, then set target to their average + var target_bg; + var min_bg; + var max_bg; + if (typeof profile.min_bg !== 'undefined') { + min_bg = profile.min_bg; + } + if (typeof profile.max_bg !== 'undefined') { + max_bg = profile.max_bg; + } + if (typeof profile.min_bg !== 'undefined' && typeof profile.max_bg !== 'undefined') { + target_bg = (profile.min_bg + profile.max_bg) / 2; + } else { + rT.error ='Error: could not determine target_bg. '; + return rT; + } + + var sensitivityRatio; + var high_temptarget_raises_sensitivity = profile.exercise_mode || profile.high_temptarget_raises_sensitivity; + var normalTarget = 100; // evaluate high/low temptarget against 100, not scheduled basal (which might change) + if ( profile.half_basal_exercise_target ) { + var halfBasalTarget = profile.half_basal_exercise_target; + } else { + var halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%) + // 80 mg/dL with low_temptarget_lowers_sensitivity would give 1.5x basal, but is limited to autosens_max (1.2x by default) + } + if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget + 10 + || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { + // w/ target 100, temp target 110 = .89, 120 = 0.8, 140 = 0.67, 160 = .57, and 200 = .44 + // e.g.: Sensitivity ratio set to 0.8 based on temp target of 120; Adjusting basal from 1.65 to 1.35; ISF from 58.9 to 73.6 + //sensitivityRatio = 2/(2+(target_bg-normalTarget)/40); + var c = halfBasalTarget - normalTarget; + sensitivityRatio = c/(c+target_bg-normalTarget); + // limit sensitivityRatio to profile.autosens_max (1.2x by default) + sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); + sensitivityRatio = round(sensitivityRatio,2); + console.error("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); + } else if (typeof autosens_data !== 'undefined' ) { + sensitivityRatio = autosens_data.ratio; + console.error("Autosens ratio: "+sensitivityRatio+"; "); + } + if (sensitivityRatio) { + basal = profile.current_basal * sensitivityRatio; + basal = round_basal(basal, profile); + if (basal != profile_current_basal) { + console.error("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); + } else { + console.error("Basal unchanged: "+basal+"; "); + } + } + + // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120 + if (profile.temptargetSet) { + //console.error("Temp Target set, not adjusting with autosens; "); + } else if (typeof autosens_data !== 'undefined' ) { + if ( profile.sensitivity_raises_target && autosens_data.ratio < 1 || profile.resistance_lowers_target && autosens_data.ratio > 1 ) { + // with a target of 100, default 0.7-1.2 autosens min/max range would allow a 93-117 target range + min_bg = round((min_bg - 60) / autosens_data.ratio) + 60; + max_bg = round((max_bg - 60) / autosens_data.ratio) + 60; + new_target_bg = round((target_bg - 60) / autosens_data.ratio) + 60; + // don't allow target_bg below 80 + new_target_bg = Math.max(80, new_target_bg); + if (target_bg == new_target_bg) { + console.error("target_bg unchanged: "+new_target_bg+"; "); + } else { + console.error("target_bg from "+target_bg+" to "+new_target_bg+"; "); + } + target_bg = new_target_bg; + } + } + + if (typeof iob_data === 'undefined' ) { + rT.error ='Error: iob_data undefined. '; + return rT; + } + + var iobArray = iob_data; + if (typeof(iob_data.length) && iob_data.length > 1) { + iob_data = iobArray[0]; + //console.error(JSON.stringify(iob_data[0])); + } + + if (typeof iob_data.activity === 'undefined' || typeof iob_data.iob === 'undefined' ) { + rT.error ='Error: iob_data missing some property. '; + return rT; + } + + var tick; + + if (glucose_status.delta > -0.5) { + tick = "+" + round(glucose_status.delta,0); + } else { + tick = round(glucose_status.delta,0); + } + //var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var minDelta = Math.min(glucose_status.delta, glucose_status.short_avgdelta); + var minAvgDelta = Math.min(glucose_status.short_avgdelta, glucose_status.long_avgdelta); + var maxDelta = Math.max(glucose_status.delta, glucose_status.short_avgdelta, glucose_status.long_avgdelta); + + var profile_sens = round(profile.sens,1) + var sens = profile.sens; + if (typeof autosens_data !== 'undefined' ) { + sens = profile.sens / sensitivityRatio; + sens = round(sens, 1); + if (sens != profile_sens) { + console.error("ISF from "+profile_sens+" to "+sens); + } else { + console.error("ISF unchanged: "+sens); + } + //console.error(" (autosens ratio "+sensitivityRatio+")"); + } + console.error("; CR:",profile.carb_ratio); + + // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match + var lastTempAge; + if (typeof iob_data.lastTemp !== 'undefined' ) { + lastTempAge = round(( new Date().getTime() - iob_data.lastTemp.date ) / 60000); // in minutes + // } ---- added to not produce errors + } else { + lastTempAge = 0; + } + //console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m"); + tempModulus = (lastTempAge + currenttemp.duration) % 30; + console.error("currenttemp:",currenttemp,"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m"); + rT.temp = 'absolute'; + rT.deliverAt = deliverAt; + if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate != iob_data.lastTemp.rate ) { + rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; setting neutral temp of "+basal+"."; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + if ( currenttemp && iob_data.lastTemp && currenttemp.duration > 0 ) { + // TODO: fix this (lastTemp.duration is how long it has run; currenttemp.duration is time left + //if ( currenttemp.duration < iob_data.lastTemp.duration - 2) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" << lastTemp duration "+round(iob_data.lastTemp.duration,1)+" from pumphistory; setting neutral temp of "+basal+"."; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + //console.error(lastTempAge, round(iob_data.lastTemp.duration,1), round(lastTempAge - iob_data.lastTemp.duration,1)); + var lastTempEnded = lastTempAge - iob_data.lastTemp.duration + if ( lastTempEnded > 5 ) { + rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; setting neutral temp of "+basal+"."; + //console.error(currenttemp, round(iob_data.lastTemp,1), round(lastTempAge,1)); + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + // TODO: figure out a way to do this check that doesn't fail across basal schedule boundaries + //if ( tempModulus < 25 && tempModulus > 5 ) { + //rT.reason = "Warning: currenttemp duration "+currenttemp.duration+" + lastTempAge "+lastTempAge+" isn't a multiple of 30m; setting neutral temp of "+basal+"."; + //console.error(rT.reason); + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + } + + //calculate BG impact: the amount BG "should" be rising or falling based on insulin activity alone + var bgi = round(( -iob_data.activity * sens * 5 ), 2); + // project deviations for 30 minutes + var deviation = round( 30 / 5 * ( minDelta - bgi ) ); + // don't overreact to a big negative delta: use minAvgDelta if deviation is negative + if (deviation < 0) { + deviation = round( (30 / 5) * ( minAvgDelta - bgi ) ); + // and if deviation is still negative, use long_avgdelta + if (deviation < 0) { + deviation = round( (30 / 5) * ( glucose_status.long_avgdelta - bgi ) ); + } + } + + // calculate the naive (bolus calculator math) eventual BG based on net IOB and sensitivity + if (iob_data.iob > 0) { + var naive_eventualBG = round( bg - (iob_data.iob * sens) ); + } else { // if IOB is negative, be more conservative and use the lower of sens, profile.sens + var naive_eventualBG = round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); + } + // and adjust it for the deviation above + var eventualBG = naive_eventualBG + deviation; + // calculate what portion of that is due to bolussnooze + //var bolusContrib = iob_data.bolussnooze * sens; + // and add it back in to get snoozeBG, plus another 50% to avoid low-temping at mealtime + //var naive_snoozeBG = round( naive_eventualBG + 1.5 * bolusContrib ); + // adjust that for deviation like we did eventualBG + //var snoozeBG = naive_snoozeBG + deviation; + + // adjust target BG range if needed to safely bring down high BG faster without causing lows + if ( bg > max_bg && profile.adv_target_adjustments && ! profile.temptargetSet ) { + // with target=100, as BG rises from 100 to 160, adjustedTarget drops from 100 to 80 + var adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0); + var adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0); + var adjustedMaxBG = round(Math.max(80, max_bg - (bg - max_bg)/3 ),0); + // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedMinBG, don’t use it + //console.error("naive_eventualBG:",naive_eventualBG+", eventualBG:",eventualBG); + if (eventualBG > adjustedMinBG && naive_eventualBG > adjustedMinBG && min_bg > adjustedMinBG) { + console.error("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; "); + min_bg = adjustedMinBG; + } else { + console.error("min_bg unchanged: "+min_bg+"; "); + } + // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedTargetBG, don’t use it + if (eventualBG > adjustedTargetBG && naive_eventualBG > adjustedTargetBG && target_bg > adjustedTargetBG) { + console.error("target_bg from "+target_bg+" to "+adjustedTargetBG+"; "); + target_bg = adjustedTargetBG; + } else { + console.error("target_bg unchanged: "+target_bg+"; "); + } + // if eventualBG, naive_eventualBG, and max_bg aren't all above adjustedMaxBG, don’t use it + if (eventualBG > adjustedMaxBG && naive_eventualBG > adjustedMaxBG && max_bg > adjustedMaxBG) { + console.error("max_bg from "+max_bg+" to "+adjustedMaxBG); + max_bg = adjustedMaxBG; + } else { + console.error("max_bg unchanged: "+max_bg); + } + } + + var expectedDelta = calculate_expected_delta(target_bg, eventualBG, bgi); + if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) { + rT.error ='Error: could not calculate eventualBG. '; + return rT; + } + + // min_bg of 90 -> threshold of 65, 100 -> 70 110 -> 75, and 130 -> 85 + var threshold = min_bg - 0.5*(min_bg-40); + + //console.error(reservoir_data); + + rT = { + 'temp': 'absolute' + , 'bg': bg + , 'tick': tick + , 'eventualBG': eventualBG + //, 'snoozeBG': snoozeBG + , 'insulinReq': 0 + , 'reservoir' : reservoir_data // The expected reservoir volume at which to deliver the microbolus (the reservoir volume from right before the last pumphistory run) + , 'deliverAt' : deliverAt // The time at which the microbolus should be delivered + , 'sensitivityRatio' : sensitivityRatio // autosens ratio (fraction of normal basal) + }; + + // generate predicted future BGs based on IOB, COB, and current absorption rate + + var COBpredBGs = []; + var aCOBpredBGs = []; + var IOBpredBGs = []; + var UAMpredBGs = []; + var ZTpredBGs = []; + COBpredBGs.push(bg); + aCOBpredBGs.push(bg); + IOBpredBGs.push(bg); + ZTpredBGs.push(bg); + UAMpredBGs.push(bg); + + // enable SMB whenever we have COB or UAM is enabled + // SMB is disabled by default, unless explicitly enabled in preferences.json + var enableSMB=false; + // disable SMB when a high temptarget is set + if (! microBolusAllowed) { + console.error("SMB disabled (!microBolusAllowed)") + } else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) { + console.error("SMB disabled due to high temptarget of",target_bg); + enableSMB=false; + // enable SMB/UAM (if enabled in preferences) while we have COB + } else if (profile.enableSMB_with_COB === true && meal_data.mealCOB) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard COB"); + } + } else { + console.error("SMB enabled for COB of",meal_data.mealCOB); + enableSMB=true; + } + // enable SMB/UAM (if enabled in preferences) for a full 6 hours after any carb entry + // (6 hours is defined in carbWindow in lib/meal/total.js) + } else if (profile.enableSMB_after_carbs === true && meal_data.carbs ) { + if (meal_data.bwCarbs) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("SMB not enabled for Bolus Wizard carbs"); + } + } else { + console.error("SMB enabled for 6h after carb entry"); + enableSMB=true; + } + // enable SMB/UAM (if enabled in preferences) if a low temptarget is set + } else if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_with_temptarget not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile)); + enableSMB=true; + } + // enable SMB/UAM if always-on (unless previously disabled for high temptarget) + } else if (profile.enableSMB_always === true) { + if (meal_data.bwFound) { + if (profile.A52_risk_enable === true) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") + enableSMB=true; + } else { + console.error("enableSMB_always not supported within 6h of using Bolus Wizard"); + } + } else { + console.error("SMB enabled due to enableSMB_always"); + enableSMB=true; + } + } else { + console.error("SMB disabled (no enableSMB preferences active)"); + } + // enable UAM (if enabled in preferences) if SMB is enabled + var enableUAM=(profile.enableUAM && enableSMB); + + + //console.error(meal_data); + // carb impact and duration are 0 unless changed below + var ci = 0; + var cid = 0; + // calculate current carb absorption rate, and how long to absorb all carbs + // CI = current carb impact on BG in mg/dL/5m + ci = round((minDelta - bgi),1); + uci = round((minDelta - bgi),1); + // ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g) + if (profile.temptargetSet) { + // if temptargetSet, use unadjusted profile.sens to allow activity mode sensitivityRatio to adjust CR + var csf = profile.sens / profile.carb_ratio; + } else { + // otherwise, use autosens-adjusted sens to counteract autosens meal insulin dosing adjustments + // so that autotuned CR is still in effect even when basals and ISF are being adjusted by autosens + var csf = sens / profile.carb_ratio; + } + var maxCarbAbsorptionRate = 30; // g/h; maximum rate to assume carbs will absorb if no CI observed + // limit Carb Impact to maxCarbAbsorptionRate * csf in mg/dL per 5m + maxCI = round(maxCarbAbsorptionRate*csf*5/60,1) + if (ci > maxCI) { + console.error("Limiting carb impact from",ci,"to",maxCI,"mg/dL/5m (",maxCarbAbsorptionRate,"g/h )"); + ci = maxCI; + } + // set meal_carbimpact high enough to absorb all meal carbs over 6 hours + // total_impact (mg/dL) = CSF (mg/dL/g) * carbs (g) + //console.error(csf * meal_data.carbs); + // meal_carbimpact (mg/dL/5m) = CSF (mg/dL/g) * carbs (g) / 6 (h) * (1h/60m) * 5 (m/5m) * 2 (for linear decay) + //var meal_carbimpact = round((csf * meal_data.carbs / 6 / 60 * 5 * 2),1) + var remainingCATimeMin = 3; // h; before carb absorption starts + // adjust remainingCATime (instead of CR) for autosens + remainingCATimeMin = remainingCATimeMin / sensitivityRatio; + // 20 g/h means that anything <= 60g will get a remainingCATimeMin, 80g will get 4h, and 120g 6h + // when actual absorption ramps up it will take over from remainingCATime + var assumedCarbAbsorptionRate = 20; // g/h; maximum rate to assume carbs will absorb if no CI observed + var remainingCATime = remainingCATimeMin; // added by mike https://github.com/openaps/oref0/issues/884 + if (meal_data.carbs) { + // if carbs * assumedCarbAbsorptionRate > remainingCATimeMin, raise it + // so <= 90g is assumed to take 3h, and 120g=4h + remainingCATimeMin = Math.max(remainingCATimeMin, meal_data.mealCOB/assumedCarbAbsorptionRate); + var lastCarbAge = round(( new Date().getTime() - meal_data.lastCarbTime ) / 60000); + //console.error(meal_data.lastCarbTime, lastCarbAge); + + fractionCOBAbsorbed = ( meal_data.carbs - meal_data.mealCOB ) / meal_data.carbs; + remainingCATime = remainingCATimeMin + 1.5 * lastCarbAge/60; + remainingCATime = round(remainingCATime,1); + //console.error(fractionCOBAbsorbed, remainingCATimeAdjustment, remainingCATime) + console.error("Last carbs",lastCarbAge,"minutes ago; remainingCATime:",remainingCATime,"hours;",round(fractionCOBAbsorbed*100)+"% carbs absorbed"); + } + + // calculate the number of carbs absorbed over remainingCATime hours at current CI + // CI (mg/dL/5m) * (5m)/5 (m) * 60 (min/hr) * 4 (h) / 2 (linear decay factor) = total carb impact (mg/dL) + var totalCI = Math.max(0, ci / 5 * 60 * remainingCATime / 2); + // totalCI (mg/dL) / CSF (mg/dL/g) = total carbs absorbed (g) + var totalCA = totalCI / csf; + var remainingCarbsCap = 90; // default to 90 + var remainingCarbsFraction = 1; + if (profile.remainingCarbsCap) { remainingCarbsCap = Math.min(90,profile.remainingCarbsCap); } + if (profile.remainingCarbsFraction) { remainingCarbsFraction = Math.min(1,profile.remainingCarbsFraction); } + var remainingCarbsIgnore = 1 - remainingCarbsFraction; + var remainingCarbs = Math.max(0, meal_data.mealCOB - totalCA - meal_data.carbs*remainingCarbsIgnore); + remainingCarbs = Math.min(remainingCarbsCap,remainingCarbs); + // assume remainingCarbs will absorb in a /\ shaped bilinear curve + // peaking at remainingCATime / 2 and ending at remainingCATime hours + // area of the /\ triangle is the same as a remainingCIpeak-height rectangle out to remainingCATime/2 + // remainingCIpeak (mg/dL/5m) = remainingCarbs (g) * CSF (mg/dL/g) * 5 (m/5m) * 1h/60m / (remainingCATime/2) (h) + var remainingCIpeak = remainingCarbs * csf * 5 / 60 / (remainingCATime/2); + //console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI,remainingCATime); + //if (meal_data.mealCOB * 3 > meal_data.carbs) { } + + // calculate peak deviation in last hour, and slope from that to current deviation + var slopeFromMaxDeviation = round(meal_data.slopeFromMaxDeviation,2); + // calculate lowest deviation in last hour, and slope from that to current deviation + var slopeFromMinDeviation = round(meal_data.slopeFromMinDeviation,2); + // assume deviations will drop back down at least at 1/3 the rate they ramped up + var slopeFromDeviations = Math.min(slopeFromMaxDeviation,-slopeFromMinDeviation/3); + //console.error(slopeFromMaxDeviation); + + aci = 10; + //5m data points = g * (1U/10g) * (40mg/dL/1U) / (mg/dL/5m) + // duration (in 5m data points) = COB (g) * CSF (mg/dL/g) / ci (mg/dL/5m) + // limit cid to remainingCATime hours: the reset goes to remainingCI + if (ci == 0) { + // avoid divide by zero + cid = 0; + } else { + cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci )); + } + acid = Math.max(0, meal_data.mealCOB * csf / aci ); + // duration (hours) = duration (5m) * 5 / 60 * 2 (to account for linear decay) + console.error("Carb Impact:",ci,"mg/dL per 5m; CI Duration:",round(cid*5/60*2,1),"hours; remaining CI (~2h peak):",round(remainingCIpeak,1),"mg/dL per 5m"); + //console.error("Accel. Carb Impact:",aci,"mg/dL per 5m; ACI Duration:",round(acid*5/60*2,1),"hours"); + var minIOBPredBG = 999; + var minCOBPredBG = 999; + var minUAMPredBG = 999; + var minGuardBG = bg; + var minCOBGuardBG = 999; + var minUAMGuardBG = 999; + var minIOBGuardBG = 999; + var minZTGuardBG = 999; + var minPredBG; + var avgPredBG; + var IOBpredBG = eventualBG; + var maxIOBPredBG = bg; + var maxCOBPredBG = bg; + var maxUAMPredBG = bg; + //var maxPredBG = bg; + var eventualPredBG = bg; + var lastIOBpredBG; + var lastCOBpredBG; + var lastUAMpredBG; + var lastZTpredBG; + var UAMduration = 0; + var remainingCItotal = 0; + var remainingCIs = []; + var predCIs = []; + try { + iobArray.forEach(function(iobTick) { + //console.error(iobTick); + predBGI = round(( -iobTick.activity * sens * 5 ), 2); + predZTBGI = round(( -iobTick.iobWithZeroTemp.activity * sens * 5 ), 2); + // for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero + // over 60 minutes (data points every 5m) + predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) ); + IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev; + // calculate predBGs with long zero temp without deviations + ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; + // for COBpredBGs, predicted carb impact drops linearly from current carb impact down to zero + // eventually accounting for all carbs (if they can be absorbed over DIA) + predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); + predACI = Math.max(0, Math.max(0,aci) * ( 1 - COBpredBGs.length/Math.max(acid*2,1) ) ); + // if any carbs aren't absorbed after remainingCATime hours, assume they'll absorb in a /\ shaped + // bilinear curve peaking at remainingCIpeak at remainingCATime/2 hours (remainingCATime/2*12 * 5m) + // and ending at remainingCATime h (remainingCATime*12 * 5m intervals) + var intervals = Math.min( COBpredBGs.length, (remainingCATime*12)-COBpredBGs.length ); + var remainingCI = Math.max(0, intervals / (remainingCATime/2*12) * remainingCIpeak ); + remainingCItotal += predCI+remainingCI; + remainingCIs.push(round(remainingCI,0)); + predCIs.push(round(predCI,0)); + //console.error(round(predCI,1)+"+"+round(remainingCI,1)+" "); + COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI; + aCOBpredBG = aCOBpredBGs[aCOBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predACI; + // for UAMpredBGs, predicted carb impact drops at slopeFromDeviations + // calculate predicted CI from UAM based on slopeFromDeviations + predUCIslope = Math.max(0, uci + ( UAMpredBGs.length*slopeFromDeviations ) ); + // if slopeFromDeviations is too flat, predicted deviation impact drops linearly from + // current deviation down to zero over 3h (data points every 5m) + predUCImax = Math.max(0, uci * ( 1 - UAMpredBGs.length/Math.max(3*60/5,1) ) ); + //console.error(predUCIslope, predUCImax); + // predicted CI from UAM is the lesser of CI based on deviationSlope or DIA + predUCI = Math.min(predUCIslope, predUCImax); + if(predUCI>0) { + //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); + UAMduration=round((UAMpredBGs.length+1)*5/60,1); + } + UAMpredBG = UAMpredBGs[UAMpredBGs.length-1] + predBGI + Math.min(0, predDev) + predUCI; + //console.error(predBGI, predCI, predUCI); + // truncate all BG predictions at 4 hours + if ( IOBpredBGs.length < 48) { IOBpredBGs.push(IOBpredBG); } + if ( COBpredBGs.length < 48) { COBpredBGs.push(COBpredBG); } + if ( aCOBpredBGs.length < 48) { aCOBpredBGs.push(aCOBpredBG); } + if ( UAMpredBGs.length < 48) { UAMpredBGs.push(UAMpredBG); } + if ( ZTpredBGs.length < 48) { ZTpredBGs.push(ZTpredBG); } + // calculate minGuardBGs without a wait from COB, UAM, IOB predBGs + if ( COBpredBG < minCOBGuardBG ) { minCOBGuardBG = round(COBpredBG); } + if ( UAMpredBG < minUAMGuardBG ) { minUAMGuardBG = round(UAMpredBG); } + if ( IOBpredBG < minIOBGuardBG ) { minIOBGuardBG = round(IOBpredBG); } + if ( ZTpredBG < minZTGuardBG ) { minZTGuardBG = round(ZTpredBG); } + + // set minPredBGs starting when currently-dosed insulin activity will peak + // look ahead 60m (regardless of insulin type) so as to be less aggressive on slower insulins + var insulinPeakTime = 60; + // add 30m to allow for insluin delivery (SMBs or temps) + insulinPeakTime = 90; + var insulinPeak5m = (insulinPeakTime/60)*12; + //console.error(insulinPeakTime, insulinPeak5m, profile.insulinPeakTime, profile.curve); + + // wait 90m before setting minIOBPredBG + if ( IOBpredBGs.length > insulinPeak5m && (IOBpredBG < minIOBPredBG) ) { minIOBPredBG = round(IOBpredBG); } + if ( IOBpredBG > maxIOBPredBG ) { maxIOBPredBG = IOBpredBG; } + // wait 85-105m before setting COB and 60m for UAM minPredBGs + if ( (cid || remainingCIpeak > 0) && COBpredBGs.length > insulinPeak5m && (COBpredBG < minCOBPredBG) ) { minCOBPredBG = round(COBpredBG); } + if ( (cid || remainingCIpeak > 0) && COBpredBG > maxIOBPredBG ) { maxCOBPredBG = COBpredBG; } + if ( enableUAM && UAMpredBGs.length > 12 && (UAMpredBG < minUAMPredBG) ) { minUAMPredBG = round(UAMpredBG); } + if ( enableUAM && UAMpredBG > maxIOBPredBG ) { maxUAMPredBG = UAMpredBG; } + }); + // set eventualBG to include effect of carbs + //console.error("PredBGs:",JSON.stringify(predBGs)); + } catch (e) { + console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled:",e); + } + if (meal_data.mealCOB) { + console.error("predCIs (mg/dL/5m):",predCIs.join(" ")); + console.error("remainingCIs: ",remainingCIs.join(" ")); + } + //,"totalCA:",round(totalCA,2),"remainingCItotal/csf+totalCA:",round(remainingCItotal/csf+totalCA,2)); + rT.predBGs = {}; + IOBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=IOBpredBGs.length-1; i > 12; i--) { + if (IOBpredBGs[i-1] != IOBpredBGs[i]) { break; } + else { IOBpredBGs.pop(); } + } + rT.predBGs.IOB = IOBpredBGs; + lastIOBpredBG=round(IOBpredBGs[IOBpredBGs.length-1]); + ZTpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=ZTpredBGs.length-1; i > 6; i--) { + //if (ZTpredBGs[i-1] != ZTpredBGs[i]) { break; } + // stop displaying ZTpredBGs once they're rising and above target + if (ZTpredBGs[i-1] >= ZTpredBGs[i] || ZTpredBGs[i] < target_bg) { break; } + else { ZTpredBGs.pop(); } + } + rT.predBGs.ZT = ZTpredBGs; + lastZTpredBG=round(ZTpredBGs[ZTpredBGs.length-1]); + if (meal_data.mealCOB > 0) { + aCOBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=aCOBpredBGs.length-1; i > 12; i--) { + if (aCOBpredBGs[i-1] != aCOBpredBGs[i]) { break; } + else { aCOBpredBGs.pop(); } + } + // disable for now. may want to add a preference to re-enable + //rT.predBGs.aCOB = aCOBpredBGs; + } + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { + COBpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=COBpredBGs.length-1; i > 12; i--) { + if (COBpredBGs[i-1] != COBpredBGs[i]) { break; } + else { COBpredBGs.pop(); } + } + rT.predBGs.COB = COBpredBGs; + lastCOBpredBG=round(COBpredBGs[COBpredBGs.length-1]); + eventualBG = Math.max(eventualBG, round(COBpredBGs[COBpredBGs.length-1]) ); + } + if (ci > 0 || remainingCIpeak > 0) { + if (enableUAM) { + UAMpredBGs.forEach(function(p, i, theArray) { + theArray[i] = round(Math.min(401,Math.max(39,p))); + }); + for (var i=UAMpredBGs.length-1; i > 12; i--) { + if (UAMpredBGs[i-1] != UAMpredBGs[i]) { break; } + else { UAMpredBGs.pop(); } + } + rT.predBGs.UAM = UAMpredBGs; + lastUAMpredBG=round(UAMpredBGs[UAMpredBGs.length-1]); + if (UAMpredBGs[UAMpredBGs.length-1]) { + eventualBG = Math.max(eventualBG, round(UAMpredBGs[UAMpredBGs.length-1]) ); + } + } + + // set eventualBG and snoozeBG based on COB or UAM predBGs + rT.eventualBG = eventualBG; + } + + console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); + + + minIOBPredBG = Math.max(39,minIOBPredBG); + minCOBPredBG = Math.max(39,minCOBPredBG); + minUAMPredBG = Math.max(39,minUAMPredBG); + minPredBG = round(minIOBPredBG); + + var fractionCarbsLeft = meal_data.mealCOB/meal_data.carbs; + // if we have COB and UAM is enabled, average both + if ( minUAMPredBG < 999 && minCOBPredBG < 999 ) { + // weight COBpredBG vs. UAMpredBG based on how many carbs remain as COB + avgPredBG = round( (1-fractionCarbsLeft)*UAMpredBG + fractionCarbsLeft*COBpredBG ); + // if UAM is disabled, average IOB and COB + } else if ( minCOBPredBG < 999 ) { + avgPredBG = round( (IOBpredBG + COBpredBG)/2 ); + // if we have UAM but no COB, average IOB and UAM + } else if ( minUAMPredBG < 999 ) { + avgPredBG = round( (IOBpredBG + UAMpredBG)/2 ); + } else { + avgPredBG = round( IOBpredBG ); + } + // if avgPredBG is below minZTGuardBG, bring it up to that level + if ( minZTGuardBG > avgPredBG ) { + avgPredBG = minZTGuardBG; + } + + // if we have both minCOBGuardBG and minUAMGuardBG, blend according to fractionCarbsLeft + if ( (cid || remainingCIpeak > 0) ) { + if ( enableUAM ) { + minGuardBG = fractionCarbsLeft*minCOBGuardBG + (1-fractionCarbsLeft)*minUAMGuardBG; + } else { + minGuardBG = minCOBGuardBG; + } + } else if ( enableUAM ) { + minGuardBG = minUAMGuardBG; + } else { + minGuardBG = minIOBGuardBG; + } + minGuardBG = round(minGuardBG); + //console.error(minCOBGuardBG, minUAMGuardBG, minIOBGuardBG, minGuardBG); + + var minZTUAMPredBG = minUAMPredBG; + // if minZTGuardBG is below threshold, bring down any super-high minUAMPredBG by averaging + // this helps prevent UAM from giving too much insulin in case absorption falls off suddenly + if ( minZTGuardBG < threshold ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + // if minZTGuardBG is between threshold and target, blend in the averaging + } else if ( minZTGuardBG < target_bg ) { + // target 100, threshold 70, minZTGuardBG 85 gives 50%: (85-70) / (100-70) + var blendPct = (minZTGuardBG-threshold) / (target_bg-threshold); + var blendedMinZTGuardBG = minUAMPredBG*blendPct + minZTGuardBG*(1-blendPct); + minZTUAMPredBG = (minUAMPredBG + blendedMinZTGuardBG) / 2; + //minZTUAMPredBG = minUAMPredBG - target_bg + minZTGuardBG; + // if minUAMPredBG is below minZTGuardBG, bring minUAMPredBG up by averaging + // this allows more insulin if lastUAMPredBG is below target, but minZTGuardBG is still high + } else if ( minZTGuardBG > minUAMPredBG ) { + minZTUAMPredBG = (minUAMPredBG + minZTGuardBG) / 2; + } + minZTUAMPredBG = round(minZTUAMPredBG); + //console.error("minUAMPredBG:",minUAMPredBG,"minZTGuardBG:",minZTGuardBG,"minZTUAMPredBG:",minZTUAMPredBG); + // if any carbs have been entered recently + if (meal_data.carbs) { + // average the minIOBPredBG and minUAMPredBG if available + /* + if ( minUAMPredBG < 999 ) { + avgMinPredBG = round( (minIOBPredBG+minUAMPredBG)/2 ); + } else { + avgMinPredBG = minIOBPredBG; + } + */ + + // if UAM is disabled, use max of minIOBPredBG, minCOBPredBG + if ( ! enableUAM && minCOBPredBG < 999 ) { + minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG)); + // if we have COB, use minCOBPredBG, or blendedMinPredBG if it's higher + } else if ( minCOBPredBG < 999 ) { + // calculate blendedMinPredBG based on how many carbs remain as COB + //blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minUAMPredBG; + blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG; + // if blendedMinPredBG > minCOBPredBG, use that instead + minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG, blendedMinPredBG)); + // if carbs have been entered, but have expired, use minUAMPredBG + } else { + //minPredBG = minUAMPredBG; + minPredBG = minZTUAMPredBG; + } + // in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG + } else if ( enableUAM ) { + //minPredBG = round(Math.max(minIOBPredBG,minUAMPredBG)); + minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); + } + + // make sure minPredBG isn't higher than avgPredBG + minPredBG = Math.min( minPredBG, avgPredBG ); + + console.error("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); + if (minCOBPredBG < 999) { + console.error(" minCOBPredBG: "+minCOBPredBG); + } + if (minUAMPredBG < 999) { + console.error(" minUAMPredBG: "+minUAMPredBG); + } + console.error(" avgPredBG:",avgPredBG,"COB:",meal_data.mealCOB,"/",meal_data.carbs); + // But if the COB line falls off a cliff, don't trust UAM too much: + // use maxCOBPredBG if it's been set and lower than minPredBG + if ( maxCOBPredBG > bg ) { + minPredBG = Math.min(minPredBG, maxCOBPredBG); + } + + rT.COB=meal_data.mealCOB; + rT.IOB=iob_data.iob; + rT.reason="COB: " + meal_data.mealCOB + ", Dev: " + convert_bg(deviation, profile) + ", BGI: " + convert_bg(bgi, profile) + ", ISF: " + convert_bg(sens, profile) + ", CR: " + round(profile.carb_ratio, 2) + ", Target: " + convert_bg(target_bg, profile) + ", minPredBG " + convert_bg(minPredBG, profile) + ", minGuardBG " + convert_bg(minGuardBG, profile) + ", IOBpredBG " + convert_bg(lastIOBpredBG, profile); + if (lastCOBpredBG > 0) { + rT.reason += ", COBpredBG " + convert_bg(lastCOBpredBG, profile); + } + if (lastUAMpredBG > 0) { + rT.reason += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile) + } + rT.reason += "; "; + //var bgUndershoot = threshold - Math.min(minGuardBG, Math.max( naive_eventualBG, eventualBG )); + // use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39 + //var carbsReqBG = Math.max( naive_eventualBG, eventualBG ); + var carbsReqBG = naive_eventualBG; + if ( carbsReqBG < 40 ) { + carbsReqBG = Math.min( minGuardBG, carbsReqBG ); + } + var bgUndershoot = threshold - carbsReqBG; + // calculate how long until COB (or IOB) predBGs drop below min_bg + var minutesAboveMinBG = 240; + var minutesAboveThreshold = 240; + if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { + for (var i=0; i 0.20 * bg ) { + console.error("maxDelta",convert_bg(maxDelta, profile),"> 20% of BG",convert_bg(bg, profile),"- disabling SMB"); + rT.reason += "maxDelta "+convert_bg(maxDelta, profile)+" > 20% of BG "+convert_bg(bg, profile)+": SMB disabled; "; + enableSMB = false; + } + + console.error("BG projected to remain above",convert_bg(min_bg, profile),"for",minutesAboveMinBG,"minutes"); + if ( minutesAboveThreshold < 240 || minutesAboveMinBG < 60 ) { + console.error("BG projected to remain above",convert_bg(threshold,profile),"for",minutesAboveThreshold,"minutes"); + } + // include at least minutesAboveMinBG worth of zero temps in calculating carbsReq + // always include at least 30m worth of zero temp (carbs to 80, low temp up to target) + //var zeroTempDuration = Math.max(30,minutesAboveMinBG); + var zeroTempDuration = minutesAboveThreshold; + // BG undershoot, minus effect of zero temps until hitting min_bg, converted to grams, minus COB + var zeroTempEffect = profile.current_basal*sens*zeroTempDuration/60; + // don't count the last 25% of COB against carbsReq + var COBforCarbsReq = Math.max(0, meal_data.mealCOB - 0.25*meal_data.carbs); + var carbsReq = (bgUndershoot - zeroTempEffect) / csf - COBforCarbsReq; + zeroTempEffect = round(zeroTempEffect); + carbsReq = round(carbsReq); + console.error("naive_eventualBG:",naive_eventualBG,"bgUndershoot:",bgUndershoot,"zeroTempDuration:",zeroTempDuration,"zeroTempEffect:",zeroTempEffect,"carbsReq:",carbsReq); + if ( carbsReq >= profile.carbsReqThreshold && minutesAboveThreshold <= 45 ) { + rT.carbsReq = carbsReq; + rT.reason += carbsReq + " add'l carbs req w/in " + minutesAboveThreshold + "m; "; + } + // don't low glucose suspend if IOB is already super negative and BG is rising faster than predicted + if (bg < threshold && iob_data.iob < -profile.current_basal*20/60 && minDelta > 0 && minDelta > expectedDelta) { + rT.reason += "IOB "+iob_data.iob+" < " + round(-profile.current_basal*20/60,2); + rT.reason += " and minDelta " + convert_bg(minDelta, profile) + " > " + "expectedDelta " + convert_bg(expectedDelta, profile) + "; "; + // predictive low glucose suspend mode: BG is / is projected to be < threshold + } else if ( bg < threshold || minGuardBG < threshold ) { + rT.reason += "minGuardBG " + convert_bg(minGuardBG, profile) + "<" + convert_bg(threshold, profile); + var bgUndershoot = target_bg - minGuardBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + durationReq = round(durationReq/30)*30; + // always set a 30-120m zero temp (oref0-pump-loop will let any longer SMB zero temp run) + durationReq = Math.min(120,Math.max(30,durationReq)); + return tempBasalFunctions.setTempBasal(0, durationReq, profile, rT, currenttemp); + } + + if (eventualBG < min_bg) { // if eventual BG is below target: + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " < " + convert_bg(min_bg, profile); + // if 5m or 30m avg BG is rising faster than expected delta + if ( minDelta > expectedDelta && minDelta > 0 && !carbsReq ) { + // if naive_eventualBG < 40, set a 30m zero temp (oref0-pump-loop will let any longer SMB zero temp run) + if (naive_eventualBG < 40) { + rT.reason += ", naive_eventualBG < 40. "; + return tempBasalFunctions.setTempBasal(0, 30, profile, rT, currenttemp); + } + if (glucose_status.delta > minDelta) { + rT.reason += ", but Delta " + convert_bg(tick, profile) + " > expectedDelta " + convert_bg(expectedDelta, profile); + } else { + rT.reason += ", but Min. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + convert_bg(expectedDelta, profile); + } + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + + // calculate 30m low-temp required to get projected BG up to target + // use snoozeBG to more gradually ramp in any counteraction of the user's boluses + // multiply by 2 to low-temp faster for increased hypo safety + //var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); + var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / sens); + insulinReq = round( insulinReq , 2); + // calculate naiveInsulinReq based on naive_eventualBG + var naiveInsulinReq = Math.min(0, (naive_eventualBG - target_bg) / sens); + naiveInsulinReq = round( naiveInsulinReq , 2); + if (minDelta < 0 && minDelta > expectedDelta) { + // if we're barely falling, newinsulinReq should be barely negative + //rT.reason += ", Snooze BG " + convert_bg(snoozeBG, profile); + var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2); + //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); + insulinReq = newinsulinReq; + } + // rate required to deliver insulinReq less insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = round_basal(rate, profile); + // if required temp < existing temp basal + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + // if current temp would deliver a lot (30% of basal) less than the required insulin, + // by both normal and naive calculations, then raise the rate + var minInsulinReq = Math.min(insulinReq,naiveInsulinReq); + if (insulinScheduled < minInsulinReq - basal*0.3) { + rT.reason += ", "+currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " is a lot less than needed. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate >= currenttemp.rate * 0.8)) { + rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr. "; + return rT; + } else { + // calculate a long enough zero temp to eventually correct back up to target + if ( rate <=0 ) { + var bgUndershoot = target_bg - naive_eventualBG; + var worstCaseInsulinReq = bgUndershoot / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + if (durationReq < 0) { + durationReq = 0; + // don't set a temp longer than 120 minutes + } else { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(120,Math.max(0,durationReq)); + } + //console.error(durationReq); + //rT.reason += "insulinReq " + insulinReq + "; " + if (durationReq > 0) { + rT.reason += ", setting " + durationReq + "m zero temp. "; + return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); + } + } else { + rT.reason += ", setting " + rate + "U/hr. "; + } + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + } + + // if eventual BG is above min but BG is falling faster than expected Delta + if (minDelta < expectedDelta) { + // if in SMB mode, don't cancel SMB zero temp + if (! (microBolusAllowed && enableSMB)) { + if (glucose_status.delta < minDelta) { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Delta " + convert_bg(tick, profile) + " < Exp. Delta " + convert_bg(expectedDelta, profile); + } else { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " > " + convert_bg(min_bg, profile) + " but Min. Delta " + minDelta.toFixed(2) + " < Exp. Delta " + convert_bg(expectedDelta, profile); + } + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + } + // eventualBG or minPredBG is below max_bg + if (Math.min(eventualBG,minPredBG) < max_bg) { + // if in SMB mode, don't cancel SMB zero temp + if (! (microBolusAllowed && enableSMB )) { + rT.reason += convert_bg(eventualBG, profile)+"-"+convert_bg(minPredBG, profile)+" in range: no temp required"; + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + } + + // eventual BG is at/above target + // if iob is over max, just cancel any temps + // if we're not here because of SMB, eventual BG is at/above target + if (! (microBolusAllowed && rT.COB)) { + rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", "; + } + if (iob_data.iob > max_iob) { + rT.reason += "IOB " + round(iob_data.iob,2) + " > max_iob " + max_iob; + if (currenttemp.duration > 15 && (round_basal(basal, profile) === round_basal(currenttemp.rate, profile))) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } else { // otherwise, calculate 30m high-temp required to get projected BG down to target + + // insulinReq is the additional insulin required to get minPredBG down to target_bg + //console.error(minPredBG,eventualBG); + //var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + // when dropping, but not as fast as expected, reduce insulinReq proportionally + // to the what fraction of expectedDelta we're dropping at + //if (minDelta < 0 && minDelta > expectedDelta) { + //var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2); + //console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq + " for minDelta " + minDelta + " vs. expectedDelta " + expectedDelta); + //insulinReq = newinsulinReq; + //} + // if that would put us over max_iob, then reduce accordingly + if (insulinReq > max_iob-iob_data.iob) { + rT.reason += "max_iob " + max_iob + ", "; + insulinReq = max_iob-iob_data.iob; + } + + // rate required to deliver insulinReq more insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = round_basal(rate, profile); + insulinReq = round(insulinReq,3); + rT.insulinReq = insulinReq; + //console.error(iob_data.lastBolusTime); + // minutes since last bolus + var lastBolusAge = round(( new Date().getTime() - iob_data.lastBolusTime ) / 60000,1); + //console.error(lastBolusAge); + //console.error(profile.temptargetSet, target_bg, rT.COB); + // only allow microboluses with COB or low temp targets, or within DIA hours of a bolus + if (microBolusAllowed && enableSMB && bg > threshold) { + // never bolus more than maxSMBBasalMinutes worth of basal + mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); + if (typeof profile.maxSMBBasalMinutes == 'undefined' ) { + maxBolus = round( profile.current_basal * 30 / 60 ,1); + console.error("profile.maxSMBBasalMinutes undefined: defaulting to 30m"); + // if IOB covers more than COB, limit maxBolus to 30m of basal + } else if ( iob_data.iob > mealInsulinReq && iob_data.iob > 0 ) { + console.error("IOB",iob_data.iob,"> COB",meal_data.mealCOB+"; mealInsulinReq =",mealInsulinReq); + maxBolus = round( profile.current_basal * 30 / 60 ,1); + } else { + console.error("profile.maxSMBBasalMinutes:",profile.maxSMBBasalMinutes,"profile.current_basal:",profile.current_basal); + maxBolus = round( profile.current_basal * profile.maxSMBBasalMinutes / 60 ,1); + } + // bolus 1/2 the insulinReq, up to maxBolus, rounding down to nearest 0.1U + microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*10)/10; + // calculate a long enough zero temp to eventually correct back up to target + var smbTarget = target_bg; + var worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; + var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + + // if insulinReq > 0 but not enough for a microBolus, don't set an SMB zero temp + if (insulinReq > 0 && microBolus < 0.1) { + durationReq = 0; + } + + var smbLowTempReq = 0; + if (durationReq <= 0) { + durationReq = 0; + // don't set a temp longer than 120 minutes + } else if (durationReq >= 30) { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(120,Math.max(0,durationReq)); + } else { + // if SMB durationReq is less than 30m, set a nonzero low temp + smbLowTempReq = round( basal * durationReq/30 ,2); + durationReq = 30; + } + rT.reason += " insulinReq " + insulinReq; + if (microBolus >= maxBolus) { + rT.reason += "; maxBolus " + maxBolus; + } + if (durationReq > 0) { + rT.reason += "; setting " + durationReq + "m low temp of " + smbLowTempReq + "U/h"; + } + rT.reason += ". "; + + //allow SMBs every 3 minutes + var nextBolusMins = round(3-lastBolusAge,1); + //console.error(naive_eventualBG, insulinReq, worstCaseInsulinReq, durationReq); + console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m "+smbLowTempReq+"U/h temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus); + if (lastBolusAge > 3) { + if (microBolus > 0) { + rT.units = microBolus; + rT.reason += "Microbolusing " + microBolus + "U. "; + } + } else { + rT.reason += "Waiting " + nextBolusMins + "m to microbolus again. "; + } + //rT.reason += ". "; + + // if no zero temp is required, don't return yet; allow later code to set a high temp + if (durationReq > 0) { + rT.rate = smbLowTempReq; + rT.duration = durationReq; + return rT; + } + + // if insulinReq is negative, snoozeBG > target_bg, and lastCOBpredBG > target_bg, set a neutral temp + //if (insulinReq < 0 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { + //rT.reason += "; SMB bolus snooze: setting current basal of " + basal + " as temp. "; + //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + //} + } + + var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); + + if (rate > maxSafeBasal) { + rT.reason += "adj. req. rate: "+rate+" to maxSafeBasal: "+maxSafeBasal+", "; + rate = round_basal(maxSafeBasal, profile); + } + + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + if (insulinScheduled >= insulinReq * 2) { // if current temp would deliver >2x more than the required insulin, lower the rate + rT.reason += currenttemp.duration + "m@" + (currenttemp.rate).toFixed(2) + " > 2 * insulinReq. Setting temp basal of " + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + + if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set + rT.reason += "no temp, setting " + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + + if (currenttemp.duration > 5 && (round_basal(rate, profile) <= round_basal(currenttemp.rate, profile))) { // if required temp <~ existing temp basal + rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr. "; + return rT; + } + + // required temp > existing temp basal + rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr. "; + return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); + } + +}; + +module.exports = determine_basal; diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index 3c8d6095ed..6775b8dac6 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -24,6 +24,8 @@ public class Config { public static final boolean SMSCOMMUNICATORENABLED = !BuildConfig.NSCLIENTOLNY && !BuildConfig.G5UPLOADER; + public static final boolean displayDeviationSlope = true; + public static final boolean detailedLog = true; public static final boolean logFunctionCalls = true; public static final boolean logIncommingData = true; diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index b702897ba4..e77be36ba6 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -82,7 +82,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe Manifest.permission.WRITE_EXTERNAL_STORAGE}, CASE_STORAGE); } askForBatteryOptimizationPermission(); - checkUpgradeToProfileTarget(); + doMigrations(); if (Config.logFunctionCalls) log.debug("onCreate"); @@ -163,6 +163,19 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe } } + 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) { diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 44dbfa514e..ea037b362e 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -44,6 +44,7 @@ import info.nightscout.androidaps.plugins.NSClientInternal.NSClientInternalPlugi import info.nightscout.androidaps.plugins.NSClientInternal.receivers.AckAlarmReceiver; import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.OpenAPSMA.OpenAPSMAPlugin; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; import info.nightscout.androidaps.plugins.Persistentnotification.PersistentNotificationPlugin; import info.nightscout.androidaps.plugins.ProfileCircadianPercentage.CircadianPercentageProfileFragment; @@ -136,6 +137,7 @@ public class MainApp extends Application { 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()); @@ -235,6 +237,10 @@ public class MainApp extends Application { return sResources.getString(id); } + public static String gs(int id, Object... args) { + return sResources.getString(id, args); + } + public static MainApp instance() { return sInstance; } diff --git a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java index 5a0d84d1f7..ae3982ad68 100644 --- a/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/PreferencesActivity.java @@ -16,6 +16,7 @@ import android.text.TextUtils; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventRefreshGui; import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.Careportal.CareportalPlugin; import info.nightscout.androidaps.plugins.ConstraintsSafety.SafetyPlugin; import info.nightscout.androidaps.plugins.Insulin.InsulinOrefFreePeakPlugin; @@ -145,6 +146,7 @@ public class PreferencesActivity extends PreferenceActivity implements SharedPre addPreferencesFromResourceIfEnabled(LoopPlugin.getPlugin(), PluginBase.LOOP); addPreferencesFromResourceIfEnabled(OpenAPSMAPlugin.getPlugin(), PluginBase.APS); addPreferencesFromResourceIfEnabled(OpenAPSAMAPlugin.getPlugin(), PluginBase.APS); + addPreferencesFromResourceIfEnabled(OpenAPSSMBPlugin.getPlugin(), PluginBase.APS); } addPreferencesFromResourceIfEnabled(SensitivityAAPSPlugin.getPlugin(), PluginBase.SENSITIVITY); diff --git a/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java b/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java index 60dd92a2c1..f453b04d36 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/AlarmSoundService.java @@ -1,8 +1,10 @@ package info.nightscout.androidaps.Services; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; import android.media.MediaPlayer; import android.os.IBinder; @@ -53,7 +55,10 @@ public class AlarmSoundService extends Service { log.error("Unhandled exception", e); } player.setLooping(true); // Set looping - player.setVolume(100, 100); + AudioManager manager = (AudioManager)this.getSystemService(Context.AUDIO_SERVICE); + if (manager == null || !manager.isMusicActive()) { + player.setVolume(100, 100); + } try { player.prepare(); diff --git a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java index e4d65eadc0..b242c4c099 100644 --- a/app/src/main/java/info/nightscout/androidaps/Services/DataService.java +++ b/app/src/main/java/info/nightscout/androidaps/Services/DataService.java @@ -15,20 +15,22 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.events.EventNewBasalProfile; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConstraintsObjectives.ObjectivesPlugin; +import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSMbg; -import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv; -import info.nightscout.androidaps.data.ProfileStore; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSettingsStatus; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; +import info.nightscout.androidaps.plugins.ProfileNS.events.EventNSProfileUpdateGUI; import info.nightscout.androidaps.plugins.PumpDanaR.activities.DanaRNSHistorySync; import info.nightscout.androidaps.plugins.SmsCommunicator.events.EventNewSMS; import info.nightscout.androidaps.plugins.SourceDexcomG5.SourceDexcomG5Plugin; @@ -37,7 +39,6 @@ import info.nightscout.androidaps.plugins.SourceMM640g.SourceMM640gPlugin; import info.nightscout.androidaps.plugins.SourceNSClient.SourceNSClientPlugin; import info.nightscout.androidaps.plugins.SourceXdrip.SourceXdripPlugin; import info.nightscout.androidaps.receivers.DataReceiver; -import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; import info.nightscout.utils.BundleLogger; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; @@ -365,8 +366,10 @@ public class DataService extends IntentService { String profile = bundles.getString("profile"); ProfileStore profileStore = new ProfileStore(new JSONObject(profile)); NSProfilePlugin.storeNewProfile(profileStore); - MainApp.bus().post(new EventNewBasalProfile()); - + MainApp.bus().post(new EventNSProfileUpdateGUI()); + // if there are no profile switches this should lead to profile update + if (MainApp.getConfigBuilder().getProfileSwitchesFromHistory().size() == 0) + MainApp.bus().post(new EventNewBasalProfile()); if (Config.logIncommingData) log.debug("Received profileStore: " + activeProfile + " " + profile); } catch (JSONException e) { diff --git a/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java b/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java index 7720b03d3e..7d1dcac9ad 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java +++ b/app/src/main/java/info/nightscout/androidaps/data/DetailedBolusInfo.java @@ -29,6 +29,7 @@ public class DetailedBolusInfo { public Context context = null; // context for progress dialog public long pumpId = 0; // id of record if comming from pump history (not a newly created treatment) public boolean isSMB = false; // is a Super-MicroBolus + public long deliverAt = 0; // SMB should be delivered within 1 min from this time @Override public String toString() { @@ -37,6 +38,7 @@ public class DetailedBolusInfo { " carbs: " + carbs + " isValid: " + isValid + " carbTime: " + carbTime + - " isSMB: " + isSMB; + " isSMB: " + isSMB + + " deliverAt: " + new Date(deliverAt).toLocaleString(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java index ad4f161799..1a5094253f 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/data/GlucoseStatus.java @@ -35,6 +35,7 @@ public class GlucoseStatus { public double avgdelta = 0d; public double short_avgdelta = 0d; public double long_avgdelta = 0d; + public long date = 0L; @Override @@ -133,6 +134,7 @@ public class GlucoseStatus { GlucoseStatus status = new GlucoseStatus(); status.glucose = now.value; + status.date = now_date; status.short_avgdelta = average(short_deltas); diff --git a/app/src/main/java/info/nightscout/androidaps/data/Iob.java b/app/src/main/java/info/nightscout/androidaps/data/Iob.java index ee70699604..762352782b 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Iob.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Iob.java @@ -12,4 +12,26 @@ public class Iob { activityContrib += iob.activityContrib; return this; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Iob iob = (Iob) o; + + if (Double.compare(iob.iobContrib, iobContrib) != 0) return false; + return Double.compare(iob.activityContrib, activityContrib) == 0; + } + + @Override + public int hashCode() { + int result; + long temp; + temp = Double.doubleToLongBits(iobContrib); + result = (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(activityContrib); + result = 31 * result + (int) (temp ^ (temp >>> 32)); + return result; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java index 3240e79327..4bb55b0095 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java +++ b/app/src/main/java/info/nightscout/androidaps/data/IobTotal.java @@ -21,8 +21,11 @@ public class IobTotal { public double hightempinsulin; // oref1 - public double microBolusInsulin; - public double microBolusIOB; + public long lastBolusTime; + public long lastTempDate; + public int lastTempDuration; + public double lastTempRate; + public IobTotal iobWithZeroTemp; public double netInsulin = 0d; // for calculations from temp basals only public double netRatio = 0d; // net ratio at start of temp basal @@ -31,6 +34,23 @@ public class IobTotal { long time; + + public IobTotal clone() { + IobTotal copy = new IobTotal(time); + copy.iob = iob; + copy.activity = activity; + copy.bolussnooze = bolussnooze; + copy.basaliob = basaliob; + copy.netbasalinsulin = netbasalinsulin; + copy.hightempinsulin = hightempinsulin; + copy.lastBolusTime = lastBolusTime; + copy.lastTempDate = lastTempDate; + copy.lastTempDuration = lastTempDuration; + copy.lastTempRate = lastTempRate; + copy.iobWithZeroTemp = iobWithZeroTemp; + return copy; + } + public IobTotal(long time) { this.iob = 0d; this.activity = 0d; @@ -38,8 +58,7 @@ public class IobTotal { this.basaliob = 0d; this.netbasalinsulin = 0d; this.hightempinsulin = 0d; - this.microBolusInsulin = 0d; - this.microBolusIOB = 0d; + this.lastBolusTime = 0; this.time = time; } @@ -52,8 +71,6 @@ public class IobTotal { hightempinsulin += other.hightempinsulin; netInsulin += other.netInsulin; extendedBolusInsulin += other.extendedBolusInsulin; - microBolusInsulin += other.microBolusInsulin; - microBolusIOB += other.microBolusIOB; return this; } @@ -62,11 +79,14 @@ public class IobTotal { result.iob = bolusIOB.iob + basalIob.basaliob; result.activity = bolusIOB.activity + basalIob.activity; result.bolussnooze = bolusIOB.bolussnooze; - result.basaliob = basalIob.basaliob; - result.netbasalinsulin = basalIob.netbasalinsulin; - result.hightempinsulin = basalIob.hightempinsulin; - result.microBolusInsulin = bolusIOB.microBolusInsulin + basalIob.microBolusInsulin; - result.microBolusIOB = bolusIOB.microBolusIOB + basalIob.microBolusIOB; + result.basaliob = bolusIOB.basaliob + basalIob.basaliob; + result.netbasalinsulin = bolusIOB.netbasalinsulin + basalIob.netbasalinsulin; + result.hightempinsulin = basalIob.hightempinsulin + bolusIOB.hightempinsulin; + result.lastBolusTime = bolusIOB.lastBolusTime; + result.lastTempDate = basalIob.lastTempDate; + result.lastTempRate = basalIob.lastTempRate; + result.lastTempDuration = basalIob.lastTempDuration; + result.iobWithZeroTemp = basalIob.iobWithZeroTemp; return result; } @@ -77,8 +97,6 @@ public class IobTotal { this.basaliob = Round.roundTo(this.basaliob, 0.001); this.netbasalinsulin = Round.roundTo(this.netbasalinsulin, 0.001); this.hightempinsulin = Round.roundTo(this.hightempinsulin, 0.001); - this.microBolusInsulin = Round.roundTo(this.microBolusInsulin, 0.001); - this.microBolusIOB = Round.roundTo(this.microBolusIOB, 0.001); return this; } @@ -102,7 +120,24 @@ public class IobTotal { json.put("basaliob", basaliob); json.put("bolussnooze", bolussnooze); json.put("activity", activity); + json.put("lastBolusTime", lastBolusTime); json.put("time", DateUtil.toISOString(new Date(time))); + /* + + This is requested by SMB determine_basal but by based on Scott's info + it's MDT specific safety check only + It's causing rounding issues in determine_basal + + JSONObject lastTemp = new JSONObject(); + lastTemp.put("date", lastTempDate); + lastTemp.put("rate", lastTempRate); + lastTemp.put("duration", lastTempDuration); + json.put("lastTemp", lastTemp); + */ + if (iobWithZeroTemp != null) { + JSONObject iwzt = iobWithZeroTemp.determineBasalJson(); + json.put("iobWithZeroTemp", iwzt); + } } catch (JSONException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/data/MealData.java b/app/src/main/java/info/nightscout/androidaps/data/MealData.java index 292d2272d5..054c76d602 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/MealData.java +++ b/app/src/main/java/info/nightscout/androidaps/data/MealData.java @@ -7,4 +7,8 @@ public class MealData { public double boluses = 0d; public double carbs = 0d; public double mealCOB = 0.0d; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; + public long lastBolusTime; + public long lastCarbTime = 0L; } 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 5235545362..7bb693ef23 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -239,6 +239,7 @@ public class Profile { // if pump not available (at start) // do not store converted array basal_v = null; + isValidated = false; } } diff --git a/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java b/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java index c50166d5a4..d19b3316ac 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java +++ b/app/src/main/java/info/nightscout/androidaps/data/PumpEnactResult.java @@ -57,6 +57,10 @@ public class PumpEnactResult extends Object { return this; } + public PumpEnactResult percent(Integer percent) { + this.percent = percent; + return this; + } public PumpEnactResult isPercent(boolean isPercent) { this.isPercent = isPercent; return this; @@ -111,7 +115,7 @@ public class PumpEnactResult extends Object { } public Spanned toSpanned() { - String ret = MainApp.sResources.getString(R.string.success) + ": " + success; + String ret = "" + MainApp.sResources.getString(R.string.success) + ": " + success; if (queued) { ret = MainApp.sResources.getString(R.string.waitingforpumpresult); } else if (enacted) { @@ -119,17 +123,20 @@ public class PumpEnactResult extends Object { ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment + "
" + MainApp.sResources.getString(R.string.canceltemp); - } else if (isPercent) { + } else if (isPercent && percent != -1) { ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; ret += "
" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; ret += "
" + MainApp.sResources.getString(R.string.percent) + ": " + percent + "%"; - } else { + } else if (absolute != -1) { ret += "
" + MainApp.sResources.getString(R.string.enacted) + ": " + enacted; ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; ret += "
" + MainApp.sResources.getString(R.string.duration) + ": " + duration + " min"; ret += "
" + MainApp.sResources.getString(R.string.absolute) + ": " + DecimalFormatter.to2Decimal(absolute) + " U/h"; } + if (bolusDelivered > 0) { + ret += "
" + MainApp.sResources.getString(R.string.bolus) + ": " + DecimalFormatter.to2Decimal(bolusDelivered) + " U"; + } } else { ret += "
" + MainApp.sResources.getString(R.string.comment) + ": " + comment; } diff --git a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java index c8d5295811..e842979ecb 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/BgReading.java +++ b/app/src/main/java/info/nightscout/androidaps/db/BgReading.java @@ -17,7 +17,6 @@ import info.nightscout.androidaps.plugins.NSClientInternal.data.NSSgv; import info.nightscout.androidaps.plugins.Overview.OverviewPlugin; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.PointsWithLabelGraphSeries; -import info.nightscout.utils.DateUtil; import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.SP; @@ -43,7 +42,11 @@ public class BgReading implements DataPointWithLabelInterface { @DatabaseField public String _id = null; // NS _id - public boolean isPrediction = false; // true when drawing predictions as bg points + public boolean isCOBPrediction = false; // true when drawing predictions as bg points (COB) + public boolean isaCOBPrediction = false; // true when drawing predictions as bg points (aCOB) + public boolean isIOBPrediction = false; // true when drawing predictions as bg points (IOB) + public boolean isUAMPrediction = false; // true when drawing predictions as bg points (UAM) + public boolean isZTPrediction = false; // true when drawing predictions as bg points (ZT) public BgReading() { } @@ -184,7 +187,10 @@ public class BgReading implements DataPointWithLabelInterface { @Override public PointsWithLabelGraphSeries.Shape getShape() { - return PointsWithLabelGraphSeries.Shape.POINT; + if (isPrediction()) + return PointsWithLabelGraphSeries.Shape.PREDICTION; + else + return PointsWithLabelGraphSeries.Shape.BG; } @Override @@ -205,7 +211,7 @@ public class BgReading implements DataPointWithLabelInterface { highLine = Profile.fromMgdlToUnits(OverviewPlugin.bgTargetHigh, units); } int color = MainApp.sResources.getColor(R.color.inrange); - if (isPrediction) + if (isPrediction()) color = MainApp.sResources.getColor(R.color.prediction); else if (valueToUnits(units) < lowLine) color = MainApp.sResources.getColor(R.color.low); @@ -214,4 +220,23 @@ public class BgReading implements DataPointWithLabelInterface { return color; } + @Override + public int getSecondColor() { + if (isIOBPrediction) + return MainApp.sResources.getColor(R.color.iob); + if (isCOBPrediction) + return MainApp.sResources.getColor(R.color.cob); + if (isaCOBPrediction) + return 0x80FFFFFF & MainApp.sResources.getColor(R.color.cob); + if (isUAMPrediction) + return MainApp.sResources.getColor(R.color.uam); + if (isZTPrediction) + return MainApp.sResources.getColor(R.color.zt); + return R.color.mdtp_white; + } + + private boolean isPrediction() { + return isaCOBPrediction || isCOBPrediction || isIOBPrediction || isUAMPrediction || isZTPrediction; + } + } diff --git a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java index b80f4f932e..9f49b64280 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java +++ b/app/src/main/java/info/nightscout/androidaps/db/CareportalEvent.java @@ -191,6 +191,17 @@ public class CareportalEvent implements DataPointWithLabelInterface { return Translator.translate(eventType); } + public String getNotes() { + try { + JSONObject object = new JSONObject(json); + if (object.has("notes")) + return object.getString("notes"); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + return ""; + } + @Override public long getDuration() { try { @@ -242,4 +253,9 @@ public class CareportalEvent implements DataPointWithLabelInterface { return Color.GRAY; return Color.GRAY; } + + @Override + public int getSecondColor() { + return 0; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java index 69bc542dae..fe59b49137 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/DatabaseHelper.java @@ -225,9 +225,9 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { log.error("Unhandled exception", e); } VirtualPumpPlugin.setFakingStatus(true); - scheduleBgChange(); // trigger refresh + scheduleBgChange(null); // trigger refresh scheduleTemporaryBasalChange(); - scheduleTreatmentChange(); + scheduleTreatmentChange(null); scheduleExtendedBolusChange(); scheduleTemporaryTargetChange(); scheduleCareportalEventChange(); @@ -252,7 +252,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } catch (SQLException e) { log.error("Unhandled exception", e); } - scheduleTreatmentChange(); + scheduleTreatmentChange(null); } public void resetTempTargets() { @@ -358,7 +358,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { if (old == null) { getDaoBgReadings().create(bgReading); log.debug("BG: New record from: " + from + " " + bgReading.toString()); - scheduleBgChange(); + scheduleBgChange(bgReading); return true; } if (!old.isEqual(bgReading)) { @@ -366,7 +366,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { old.copyFrom(bgReading); getDaoBgReadings().update(old); log.debug("BG: Updating record from: " + from + " New data: " + old.toString()); - scheduleBgChange(); + scheduleBgChange(bgReading); return false; } } catch (SQLException e) { @@ -384,11 +384,11 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } - private static void scheduleBgChange() { + private static void scheduleBgChange(@Nullable final BgReading bgReading) { class PostRunnable implements Runnable { public void run() { log.debug("Firing EventNewBg"); - MainApp.bus().post(new EventNewBG()); + MainApp.bus().post(new EventNewBG(bgReading)); scheduledBgPost = null; } } @@ -507,7 +507,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { return 0; } - public int deleteDbRequestbyMongoId(String action, String id) { + public void deleteDbRequestbyMongoId(String action, String id) { try { QueryBuilder queryBuilder = getDaoDbRequest().queryBuilder(); Where where = queryBuilder.where(); @@ -515,16 +515,13 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { queryBuilder.limit(10L); PreparedQuery preparedQuery = queryBuilder.prepare(); List dbList = getDaoDbRequest().query(preparedQuery); - if (dbList.size() != 1) { - log.error("deleteDbRequestbyMongoId query size: " + dbList.size()); - } else { - //log.debug("Treatment findTreatmentById found: " + trList.get(0).log()); - return delete(dbList.get(0)); + log.error("deleteDbRequestbyMongoId query size: " + dbList.size()); + for (DbRequest r : dbList) { + delete(r); } } catch (SQLException e) { log.error("Unhandled exception", e); } - return 0; } public void deleteAllDbRequests() { @@ -566,7 +563,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { getDaoTreatments().create(treatment); log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); return true; } if (treatment.source == Source.NIGHTSCOUT) { @@ -583,7 +580,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { updateEarliestDataChange(oldDate); updateEarliestDataChange(old.date); } - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); return true; } return false; @@ -608,7 +605,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { updateEarliestDataChange(oldDate); updateEarliestDataChange(old.date); } - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); return true; } } @@ -616,14 +613,14 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { getDaoTreatments().create(treatment); log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); return true; } if (treatment.source == Source.USER) { getDaoTreatments().create(treatment); log.debug("TREATMENT: New record from: " + Source.getString(treatment.source) + " " + treatment.toString()); updateEarliestDataChange(treatment.date); - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); return true; } } catch (SQLException e) { @@ -639,7 +636,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } catch (SQLException e) { log.error("Unhandled exception", e); } - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); } public void update(Treatment treatment) { @@ -649,7 +646,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } catch (SQLException e) { log.error("Unhandled exception", e); } - scheduleTreatmentChange(); + scheduleTreatmentChange(treatment); } public void deleteTreatmentById(String _id) { @@ -658,12 +655,12 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { log.debug("TREATMENT: Removing Treatment record from database: " + stored.toString()); delete(stored); updateEarliestDataChange(stored.date); - scheduleTreatmentChange(); + scheduleTreatmentChange(null); } } @Nullable - public Treatment findTreatmentById(String _id) { + private Treatment findTreatmentById(String _id) { try { Dao daoTreatments = getDaoTreatments(); QueryBuilder queryBuilder = daoTreatments.queryBuilder(); @@ -695,11 +692,11 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } } - private static void scheduleTreatmentChange() { + private static void scheduleTreatmentChange(@Nullable final Treatment treatment) { class PostRunnable implements Runnable { public void run() { log.debug("Firing EventTreatmentChange"); - MainApp.bus().post(new EventReloadTreatmentData(new EventTreatmentChange())); + MainApp.bus().post(new EventReloadTreatmentData(new EventTreatmentChange(treatment))); if (earliestDataChange != null) MainApp.bus().post(new EventNewHistoryData(earliestDataChange)); earliestDataChange = null; @@ -1473,7 +1470,21 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } catch (SQLException e) { log.error("Unhandled exception", e); } - return new ArrayList(); + return new ArrayList<>(); + } + + public List getCareportalEventsFromTime(boolean ascending) { + try { + List careportalEvents; + QueryBuilder queryBuilder = getDaoCareportalEvents().queryBuilder(); + queryBuilder.orderBy("date", ascending); + PreparedQuery preparedQuery = queryBuilder.prepare(); + careportalEvents = getDaoCareportalEvents().query(preparedQuery); + return careportalEvents; + } catch (SQLException e) { + log.error("Unhandled exception", e); + } + return new ArrayList<>(); } public void deleteCareportalEventById(String _id) { @@ -1731,4 +1742,4 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } // ---------------- Food handling --------------- -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java b/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java index 4232a2fc86..7bd60fe496 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ExtendedBolus.java @@ -285,4 +285,9 @@ public class ExtendedBolus implements Interval, DataPointWithLabelInterface { public int getColor() { return Color.CYAN; } + + @Override + public int getSecondColor() { + return 0; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java b/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java index 992e328532..a933c2166e 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java +++ b/app/src/main/java/info/nightscout/androidaps/db/FoodHelper.java @@ -70,7 +70,7 @@ public class FoodHelper { public boolean createOrUpdate(Food food) { try { // find by NS _id - if (food._id != null) { + if (food._id != null && !food._id.equals("")) { Food old; QueryBuilder queryBuilder = getDaoFood().queryBuilder(); @@ -90,12 +90,13 @@ public class FoodHelper { } else { return false; } + } else { + getDaoFood().createOrUpdate(food); + log.debug("FOOD: New record: " + food.toString()); + scheduleFoodChange(); + return true; } } - getDaoFood().createOrUpdate(food); - log.debug("FOOD: New record: " + food.toString()); - scheduleFoodChange(); - return true; } catch (SQLException e) { log.error("Unhandled exception", e); } diff --git a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java index dcce69639f..ba2f2758e2 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java +++ b/app/src/main/java/info/nightscout/androidaps/db/ProfileSwitch.java @@ -238,6 +238,11 @@ public class ProfileSwitch implements Interval, DataPointWithLabelInterface { return Color.CYAN; } + @Override + public int getSecondColor() { + return 0; + } + public String toString() { return "ProfileSwitch{" + "date=" + date + diff --git a/app/src/main/java/info/nightscout/androidaps/db/Treatment.java b/app/src/main/java/info/nightscout/androidaps/db/Treatment.java index 9de76e6bf8..78e1fb6282 100644 --- a/app/src/main/java/info/nightscout/androidaps/db/Treatment.java +++ b/app/src/main/java/info/nightscout/androidaps/db/Treatment.java @@ -12,6 +12,7 @@ import java.util.Objects; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Iob; import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; @@ -139,7 +140,10 @@ public class Treatment implements DataPointWithLabelInterface { @Override public PointsWithLabelGraphSeries.Shape getShape() { - return PointsWithLabelGraphSeries.Shape.BOLUS; + if (isSMB) + return PointsWithLabelGraphSeries.Shape.SMB; + else + return PointsWithLabelGraphSeries.Shape.BOLUS; } @Override @@ -149,12 +153,19 @@ public class Treatment implements DataPointWithLabelInterface { @Override public int getColor() { - if (isValid) + if (isSMB) + return MainApp.sResources.getColor(R.color.tempbasal); + else if (isValid) return Color.CYAN; else return MainApp.instance().getResources().getColor(android.R.color.holo_red_light); } + @Override + public int getSecondColor() { + return 0; + } + @Override public void setY(double y) { yValue = y; diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java b/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java new file mode 100644 index 0000000000..17262cfb85 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/events/EventAppInitialized.java @@ -0,0 +1,8 @@ +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/EventNewBG.java b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java index 2fb9919b00..dc4d434e0a 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNewBG.java @@ -1,7 +1,17 @@ package info.nightscout.androidaps.events; +import android.support.annotation.Nullable; + +import info.nightscout.androidaps.db.BgReading; + /** * Created by mike on 05.06.2016. */ public class EventNewBG extends EventLoop { + @Nullable + public final BgReading bgReading; + + public EventNewBG(BgReading bgReading) { + this.bgReading = bgReading; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java index 439d9a7124..2a3581f9bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java +++ b/app/src/main/java/info/nightscout/androidaps/events/EventTreatmentChange.java @@ -1,7 +1,17 @@ package info.nightscout.androidaps.events; +import android.support.annotation.Nullable; + +import info.nightscout.androidaps.db.Treatment; + /** * Created by mike on 04.06.2016. */ public class EventTreatmentChange extends EventLoop { + @Nullable + public final Treatment treatment; + + public EventTreatmentChange(Treatment treatment) { + this.treatment = treatment; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java index 7ecc89ef66..a45ab083e7 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/BgSourceInterface.java @@ -4,4 +4,5 @@ package info.nightscout.androidaps.interfaces; * Created by mike on 20.06.2016. */ public interface BgSourceInterface { + boolean advancedFilteringSupported(); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java index 48efa587d8..0114b8a48d 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/InsulinInterface.java @@ -10,16 +10,16 @@ import info.nightscout.androidaps.db.Treatment; */ public interface InsulinInterface { - final int FASTACTINGINSULIN = 0; - final int FASTACTINGINSULINPROLONGED = 1; - final int OREF_RAPID_ACTING = 2; - final int OREF_ULTRA_RAPID_ACTING = 3; - final int OREF_FREE_PEAK = 4; + int FASTACTINGINSULIN = 0; + int FASTACTINGINSULINPROLONGED = 1; + int OREF_RAPID_ACTING = 2; + int OREF_ULTRA_RAPID_ACTING = 3; + int OREF_FREE_PEAK = 4; int getId(); String getFriendlyName(); String getComment(); double getDia(); - public Iob iobCalcForTreatment(Treatment treatment, long time, double dia); + Iob iobCalcForTreatment(Treatment treatment, long time, double dia); } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java index d37693d494..701b226031 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/PumpDescription.java @@ -35,4 +35,6 @@ public class PumpDescription { public double basalMinimumRate = 0.04d; public boolean isRefillingCapable = false; + + public boolean storesCarbInfo = true; } diff --git a/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java b/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java index 044b259ac9..134b419240 100644 --- a/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java @@ -30,6 +30,7 @@ public interface TreatmentsInterface { List getTreatmentsFromHistory(); List getTreatments5MinBackFromHistory(long time); + long getLastBolusTime(); // real basals (not faked by extended bolus) boolean isInHistoryRealTempBasalInProgress(); 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 index 74abc837fe..5ca9232646 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Common/SubscriberFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Common/SubscriberFragment.java @@ -2,9 +2,12 @@ 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(); @@ -18,5 +21,12 @@ abstract public class SubscriberFragment extends Fragment { updateGUI(); } + @Override public 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/ConfigBuilderPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/ConfigBuilder/ConfigBuilderPlugin.java index 99ff546eac..1f0563bb0a 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 @@ -23,11 +23,14 @@ import info.nightscout.androidaps.data.MealData; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileIntervals; import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; +import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.events.EventAppInitialized; import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.BgSourceInterface; import info.nightscout.androidaps.interfaces.ConstraintsInterface; @@ -144,6 +147,7 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr public void initialize() { pluginList = MainApp.getPluginsList(); loadSettings(); + MainApp.bus().post(new EventAppInitialized()); } public void storeSettings() { @@ -351,33 +355,19 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr return found; } - /* - * Ex Pump interface - * - * Config builder return itself as a pump and check constraints before it passes command to pump driver - */ - - /** * expect absolute request and allow both absolute and percent response based on pump capabilities - * - * @param request - * @return - * true if command is going to be executed - * false if error */ - - public boolean applyAPSRequest(APSResult request, Callback callback) { + public void applyAPSRequest(APSResult request, Callback callback) { PumpInterface pump = getActivePump(); request.rate = applyBasalConstraints(request.rate); - PumpEnactResult result; if (!pump.isInitialized()) { log.debug("applyAPSRequest: " + MainApp.sResources.getString(R.string.pumpNotInitialized)); if (callback != null) { callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpNotInitialized)).enacted(false).success(false)).run(); } - return false; + return; } if (pump.isSuspended()) { @@ -385,43 +375,56 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr if (callback != null) { callback.result(new PumpEnactResult().comment(MainApp.sResources.getString(R.string.pumpsuspended)).enacted(false).success(false)).run(); } - return false; + return; } if (Config.logCongigBuilderActions) log.debug("applyAPSRequest: " + request.toString()); - if ((request.rate == 0 && request.duration == 0) || Math.abs(request.rate - pump.getBaseBasalRate()) < pump.getPumpDescription().basalStep) { - if (isTempBasalInProgress()) { + + if (request.tempBasalReqested) { + if ((request.rate == 0 && request.duration == 0) || Math.abs(request.rate - pump.getBaseBasalRate()) < pump.getPumpDescription().basalStep) { + if (isTempBasalInProgress()) { + if (Config.logCongigBuilderActions) + log.debug("applyAPSRequest: cancelTempBasal()"); + getCommandQueue().cancelTempBasal(false, callback); + } else { + if (Config.logCongigBuilderActions) + log.debug("applyAPSRequest: Basal set correctly"); + if (callback != null) { + callback.result(new PumpEnactResult().absolute(request.rate).duration(0).enacted(false).success(true).comment("Basal set correctly")).run(); + } + } + } else if (isTempBasalInProgress() + && getTempBasalRemainingMinutesFromHistory() > 5 + && Math.abs(request.rate - getTempBasalAbsoluteRateHistory()) < pump.getPumpDescription().basalStep) { if (Config.logCongigBuilderActions) - log.debug("applyAPSRequest: cancelTempBasal()"); - getCommandQueue().cancelTempBasal(false, callback); - return true; + log.debug("applyAPSRequest: Temp basal set correctly"); + if (callback != null) { + callback.result(new PumpEnactResult().absolute(getTempBasalAbsoluteRateHistory()).duration(getTempBasalFromHistory(System.currentTimeMillis()).getPlannedRemainingMinutes()).enacted(false).success(true).comment("Temp basal set correctly")).run(); + } } else { if (Config.logCongigBuilderActions) - log.debug("applyAPSRequest: Basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult().absolute(request.rate).duration(0).enacted(false).success(true).comment("Basal set correctly")).run(); - } - return false; + log.debug("applyAPSRequest: setTempBasalAbsolute()"); + getCommandQueue().tempBasalAbsolute(request.rate, request.duration, false, callback); } - } else if (isTempBasalInProgress() - && getTempBasalRemainingMinutesFromHistory() > 5 - && Math.abs(request.rate - getTempBasalAbsoluteRateHistory()) < pump.getPumpDescription().basalStep) { - if (Config.logCongigBuilderActions) - log.debug("applyAPSRequest: Temp basal set correctly"); - if (callback != null) { - callback.result(new PumpEnactResult().absolute(getTempBasalAbsoluteRateHistory()).duration(getTempBasalFromHistory(System.currentTimeMillis()).getPlannedRemainingMinutes()).enacted(false).success(true).comment("Temp basal set correctly")).run(); + } + + if (request.bolusRequested) { + long lastBolusTime = getLastBolusTime(); + if (lastBolusTime != 0 && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { + log.debug("SMB requsted but still in 3 min interval"); + } else { + DetailedBolusInfo detailedBolusInfo = new DetailedBolusInfo(); + detailedBolusInfo.eventType = CareportalEvent.CORRECTIONBOLUS; + detailedBolusInfo.insulin = request.smb; + detailedBolusInfo.isSMB = true; + detailedBolusInfo.source = Source.USER; + detailedBolusInfo.deliverAt = request.deliverAt; + getCommandQueue().bolus(detailedBolusInfo, callback); } - return false; - } else { - if (Config.logCongigBuilderActions) - log.debug("applyAPSRequest: setTempBasalAbsolute()"); - getCommandQueue().tempBasalAbsolute(request.rate, request.duration, false, callback); - return true; } } - /** * Constraints interface **/ @@ -596,6 +599,11 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr return activeTreatments.getTreatments5MinBackFromHistory(time); } + @Override + public long getLastBolusTime() { + return activeTreatments.getLastBolusTime(); + } + @Override public boolean isInHistoryRealTempBasalInProgress() { return activeTreatments.isInHistoryRealTempBasalInProgress(); @@ -786,10 +794,10 @@ public class ConfigBuilderPlugin implements PluginBase, ConstraintsInterface, Tr return profile; } } - // Unable to determine profile, failover to default - if (activeProfile.getProfile() == null) - return null; //app not initialized } + // Unable to determine profile, failover to default + if (activeProfile.getProfile() == null) + return null; //app not initialized Profile defaultProfile = activeProfile.getProfile().getDefaultProfile(); if (defaultProfile != null) return defaultProfile; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java index 558cf0d02f..a5c00aab7f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Food/FoodFragment.java @@ -61,7 +61,6 @@ public class FoodFragment extends SubscriberFragment { Bundle savedInstanceState) { try { View view = inflater.inflate(R.layout.food_fragment, container, false); - filter = (EditText) view.findViewById(R.id.food_filter); clearFilter = (ImageView) view.findViewById(R.id.food_clearfilter); category = new SpinnerHelper(view.findViewById(R.id.food_category)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java index cbe5f2ff64..b91eefdc9c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Insulin/InsulinOrefBasePlugin.java @@ -1,5 +1,7 @@ package info.nightscout.androidaps.plugins.Insulin; +import com.squareup.otto.Bus; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Iob; @@ -44,38 +46,53 @@ public abstract class InsulinOrefBasePlugin implements PluginBase, InsulinInterf return true; } + public Bus getBus() { + return MainApp.bus(); + } + @Override public double getDia() { double dia = getUserDefinedDia(); if(dia >= MIN_DIA){ return dia; } else { - if((System.currentTimeMillis() - lastWarned) > 60*1000) { - lastWarned = System.currentTimeMillis(); - Notification notification = new Notification(Notification.SHORT_DIA, String.format(MainApp.sResources.getString(R.string.dia_too_short), dia, MIN_DIA), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - } + sendShortDiaNotification(dia); return MIN_DIA; } } + void sendShortDiaNotification(double dia) { + if((System.currentTimeMillis() - lastWarned) > 60*1000) { + lastWarned = System.currentTimeMillis(); + Notification notification = new Notification(Notification.SHORT_DIA, String.format(this.getNotificationPattern(), dia, MIN_DIA), Notification.URGENT); + this.getBus().post(new EventNewNotification(notification)); + } + } + + public String getNotificationPattern() { + return MainApp.sResources.getString(R.string.dia_too_short); + } + public double getUserDefinedDia() { return MainApp.getConfigBuilder().getProfile() != null ? MainApp.getConfigBuilder().getProfile().getDia() : MIN_DIA; } + public Iob iobCalcForTreatment(Treatment treatment, long time) { + return this.iobCalcForTreatment(treatment, time, 0d); + } + @Override public Iob iobCalcForTreatment(Treatment treatment, long time, double dia) { Iob result = new Iob(); int peak = getPeak(); - if (treatment.insulin != 0d) { long bolusTime = treatment.date; double t = (time - bolusTime) / 1000d / 60d; - double td = getDia()*60; //getDIA() always > 5 + double td = getDia()*60; //getDIA() always >= MIN_DIA double tp = peak; // force the IOB to 0 if over DIA hours have passed diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java index e6d58178f5..dddea570be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/AutosensData.java @@ -57,11 +57,15 @@ public class AutosensData { public double cob = 0; public double bgi = 0d; public double delta = 0d; + public double avgDelta = 0d; + public double avgDeviation = 0d; public double autosensRatio = 1d; + public double slopeFromMaxDeviation = 0; + public double slopeFromMinDeviation = 999; public String log(long time) { - return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " Bgi=" + bgi + " Deviation=" + deviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio; + return "AutosensData: " + new Date(time).toLocaleString() + " " + pastSensitivity + " Delta=" + delta + " avgDelta=" + avgDelta + " Bgi=" + bgi + " Deviation=" + deviation + " avgDeviation=" + avgDeviation + " Absorbed=" + absorbed + " CarbsFromBolus=" + carbsFromBolus + " COB=" + cob + " autosensRatio=" + autosensRatio + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation =" + slopeFromMinDeviation ; } public int minOld() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java index fbbbe43ccc..c891f60d49 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobCalculatorPlugin.java @@ -1,8 +1,6 @@ package info.nightscout.androidaps.plugins.IobCobCalculator; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; +import android.os.SystemClock; import android.support.annotation.Nullable; import android.support.v4.util.LongSparseArray; @@ -24,15 +22,17 @@ import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.db.TemporaryBasal; -import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.events.EventAppInitialized; import info.nightscout.androidaps.events.EventConfigBuilderChange; import info.nightscout.androidaps.events.EventNewBG; import info.nightscout.androidaps.events.EventNewBasalProfile; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventNewHistoryData; +import info.nightscout.androidaps.plugins.OpenAPSSMB.OpenAPSSMBPlugin; +import info.nightscout.utils.DateUtil; /** * Created by mike on 24.04.2017. @@ -50,10 +50,10 @@ public class IobCobCalculatorPlugin implements PluginBase { private static double dia = Constants.defaultDIA; - private static Handler sHandler = null; - private static HandlerThread sHandlerThread = null; + static final Object dataLock = new Object(); - private static final Object dataLock = new Object(); + boolean stopCalculationTrigger = false; + IobCobThread thread = null; private static IobCobCalculatorPlugin plugin = null; @@ -131,14 +131,8 @@ public class IobCobCalculatorPlugin implements PluginBase { return -1; } - IobCobCalculatorPlugin() { + private IobCobCalculatorPlugin() { MainApp.bus().register(this); - if (sHandlerThread == null) { - sHandlerThread = new HandlerThread(IobCobCalculatorPlugin.class.getSimpleName(), Process.THREAD_PRIORITY_LOWEST); - sHandlerThread.start(); - sHandler = new Handler(sHandlerThread.getLooper()); - } - onNewBg(new EventNewBG()); } @Nullable @@ -175,14 +169,9 @@ public class IobCobCalculatorPlugin implements PluginBase { return rouded; } - private void loadBgData() { - //log.debug("Locking loadBgData"); - synchronized (dataLock) { - onNewProfile(null); - bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)), false); - log.debug("BG data loaded. Size: " + bgReadings.size()); - } - //log.debug("Releasing loadBgData"); + void loadBgData() { + bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime((long) (System.currentTimeMillis() - 60 * 60 * 1000L * (24 + dia)), false); + log.debug("BG data loaded. Size: " + bgReadings.size()); } private boolean isAbout5minData() { @@ -209,7 +198,7 @@ public class IobCobCalculatorPlugin implements PluginBase { } } - private void createBucketedData() { + void createBucketedData() { if (isAbout5minData()) createBucketedData5min(); else @@ -241,223 +230,97 @@ public class IobCobCalculatorPlugin implements PluginBase { } private void createBucketedDataRecalculated() { - synchronized (dataLock) { - if (bgReadings == null || bgReadings.size() < 3) { - bucketed_data = null; - return; + if (bgReadings == null || bgReadings.size() < 3) { + bucketed_data = null; + return; + } + + bucketed_data = new ArrayList<>(); + long currentTime = bgReadings.get(0).date + 5 * 60 * 1000 - bgReadings.get(0).date % (5 * 60 * 1000) - 5 * 60 * 1000L; + //log.debug("First reading: " + new Date(currentTime).toLocaleString()); + + while (true) { + // test if current value is older than current time + BgReading newer = findNewer(currentTime); + BgReading older = findOlder(currentTime); + if (newer == null || older == null) + break; + + double bgDelta = newer.value - older.value; + long timeDiffToNew = newer.date - currentTime; + + double currentBg = newer.value - (double) timeDiffToNew / (newer.date - older.date) * bgDelta; + BgReading newBgreading = new BgReading(); + newBgreading.date = currentTime; + newBgreading.value = Math.round(currentBg); + bucketed_data.add(newBgreading); + //log.debug("BG: " + newBgreading.value + " (" + new Date(newBgreading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")"); + currentTime -= 5 * 60 * 1000L; + + } + } + + + private void createBucketedData5min() { + if (bgReadings == null || bgReadings.size() < 3) { + bucketed_data = null; + return; + } + + bucketed_data = new ArrayList<>(); + bucketed_data.add(bgReadings.get(0)); + int j = 0; + for (int i = 1; i < bgReadings.size(); ++i) { + long bgTime = bgReadings.get(i).date; + long lastbgTime = bgReadings.get(i - 1).date; + //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value); + if (bgReadings.get(i).value < 39 || bgReadings.get(i - 1).value < 39) { + continue; } - bucketed_data = new ArrayList<>(); - long currentTime = bgReadings.get(0).date + 5 * 60 * 1000 - bgReadings.get(0).date % (5 * 60 * 1000) - 5 * 60 * 1000L; - //log.debug("First reading: " + new Date(currentTime).toLocaleString()); + long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000); + if (Math.abs(elapsed_minutes) > 8) { + // interpolate missing data points + double lastbg = bgReadings.get(i - 1).value; + elapsed_minutes = Math.abs(elapsed_minutes); + //console.error(elapsed_minutes); + long nextbgTime; + while (elapsed_minutes > 5) { + nextbgTime = lastbgTime - 5 * 60 * 1000; + j++; + BgReading newBgreading = new BgReading(); + newBgreading.date = nextbgTime; + double gapDelta = bgReadings.get(i).value - lastbg; + //console.error(gapDelta, lastbg, elapsed_minutes); + double nextbg = lastbg + (5d / elapsed_minutes * gapDelta); + newBgreading.value = Math.round(nextbg); + //console.error("Interpolated", bucketed_data[j]); + bucketed_data.add(newBgreading); + //log.error("******************************************************************************************************* Adding:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); - while (true) { - // test if current value is older than current time - BgReading newer = findNewer(currentTime); - BgReading older = findOlder(currentTime); - if (newer == null || older == null) - break; - - double bgDelta = newer.value - older.value; - long timeDiffToNew = newer.date - currentTime; - - double currentBg = newer.value - (double) timeDiffToNew / (newer.date - older.date) * bgDelta; + elapsed_minutes = elapsed_minutes - 5; + lastbg = nextbg; + lastbgTime = nextbgTime; + } + j++; BgReading newBgreading = new BgReading(); - newBgreading.date = currentTime; - newBgreading.value = Math.round(currentBg); + newBgreading.value = bgReadings.get(i).value; + newBgreading.date = bgTime; bucketed_data.add(newBgreading); - //log.debug("BG: " + newBgreading.value + " (" + new Date(newBgreading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")"); - currentTime -= 5 * 60 * 1000L; - + //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); + } else if (Math.abs(elapsed_minutes) > 2) { + j++; + BgReading newBgreading = new BgReading(); + newBgreading.value = bgReadings.get(i).value; + newBgreading.date = bgTime; + bucketed_data.add(newBgreading); + //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); + } else { + bucketed_data.get(j).value = (bucketed_data.get(j).value + bgReadings.get(i).value) / 2; + //log.error("***** Average"); } } - } - - - public void createBucketedData5min() { - //log.debug("Locking createBucketedData"); - synchronized (dataLock) { - if (bgReadings == null || bgReadings.size() < 3) { - bucketed_data = null; - return; - } - - bucketed_data = new ArrayList<>(); - bucketed_data.add(bgReadings.get(0)); - int j = 0; - for (int i = 1; i < bgReadings.size(); ++i) { - long bgTime = bgReadings.get(i).date; - long lastbgTime = bgReadings.get(i - 1).date; - //log.error("Processing " + i + ": " + new Date(bgTime).toString() + " " + bgReadings.get(i).value + " Previous: " + new Date(lastbgTime).toString() + " " + bgReadings.get(i - 1).value); - if (bgReadings.get(i).value < 39 || bgReadings.get(i - 1).value < 39) { - continue; - } - - long elapsed_minutes = (bgTime - lastbgTime) / (60 * 1000); - if (Math.abs(elapsed_minutes) > 8) { - // interpolate missing data points - double lastbg = bgReadings.get(i - 1).value; - elapsed_minutes = Math.abs(elapsed_minutes); - //console.error(elapsed_minutes); - long nextbgTime; - while (elapsed_minutes > 5) { - nextbgTime = lastbgTime - 5 * 60 * 1000; - j++; - BgReading newBgreading = new BgReading(); - newBgreading.date = nextbgTime; - double gapDelta = bgReadings.get(i).value - lastbg; - //console.error(gapDelta, lastbg, elapsed_minutes); - double nextbg = lastbg + (5d / elapsed_minutes * gapDelta); - newBgreading.value = Math.round(nextbg); - //console.error("Interpolated", bucketed_data[j]); - bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Adding:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); - - elapsed_minutes = elapsed_minutes - 5; - lastbg = nextbg; - lastbgTime = nextbgTime; - } - j++; - BgReading newBgreading = new BgReading(); - newBgreading.value = bgReadings.get(i).value; - newBgreading.date = bgTime; - bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); - } else if (Math.abs(elapsed_minutes) > 2) { - j++; - BgReading newBgreading = new BgReading(); - newBgreading.value = bgReadings.get(i).value; - newBgreading.date = bgTime; - bucketed_data.add(newBgreading); - //log.error("******************************************************************************************************* Copying:" + new Date(newBgreading.date).toString() + " " + newBgreading.value); - } else { - bucketed_data.get(j).value = (bucketed_data.get(j).value + bgReadings.get(i).value) / 2; - //log.error("***** Average"); - } - } - log.debug("Bucketed data created. Size: " + bucketed_data.size()); - } - //log.debug("Releasing createBucketedData"); - } - - private void calculateSensitivityData() { - if (MainApp.getConfigBuilder() == null) - return; // app still initializing - if (MainApp.getConfigBuilder().getProfile() == null) - return; // app still initializing - //log.debug("Locking calculateSensitivityData"); - long oldestTimeWithData = oldestDataAvailable(); - - synchronized (dataLock) { - - if (bucketed_data == null || bucketed_data.size() < 3) { - log.debug("calculateSensitivityData: No bucketed data available"); - return; - } - - long prevDataTime = roundUpTime(bucketed_data.get(bucketed_data.size() - 3).date); - log.debug("Prev data time: " + new Date(prevDataTime).toLocaleString()); - AutosensData previous = autosensDataTable.get(prevDataTime); - // start from oldest to be able sub cob - for (int i = bucketed_data.size() - 4; i >= 0; i--) { - // check if data already exists - long bgTime = bucketed_data.get(i).date; - bgTime = roundUpTime(bgTime); - if (bgTime > System.currentTimeMillis()) - continue; - Profile profile = MainApp.getConfigBuilder().getProfile(bgTime); - - AutosensData existing; - if ((existing = autosensDataTable.get(bgTime)) != null) { - previous = existing; - continue; - } - - if (profile.getIsf(bgTime) == null) - return; // profile not set yet - - double sens = Profile.toMgdl(profile.getIsf(bgTime), profile.getUnits()); - - AutosensData autosensData = new AutosensData(); - autosensData.time = bgTime; - if (previous != null) - autosensData.activeCarbsList = new ArrayList<>(previous.activeCarbsList); - else - autosensData.activeCarbsList = new ArrayList<>(); - - //console.error(bgTime , bucketed_data[i].glucose); - double bg; - double avgDelta; - double delta; - bg = bucketed_data.get(i).value; - if (bg < 39 || bucketed_data.get(i + 3).value < 39) { - log.error("! value < 39"); - continue; - } - delta = (bg - bucketed_data.get(i + 1).value); - - IobTotal iob = calculateFromTreatmentsAndTemps(bgTime); - - double bgi = -iob.activity * sens * 5; - double deviation = delta - bgi; - - List recentTreatments = MainApp.getConfigBuilder().getTreatments5MinBackFromHistory(bgTime); - for (int ir = 0; ir < recentTreatments.size(); ir++) { - autosensData.carbsFromBolus += recentTreatments.get(ir).carbs; - autosensData.activeCarbsList.add(new AutosensData.CarbsInPast(recentTreatments.get(ir))); - } - - - // if we are absorbing carbs - if (previous != null && previous.cob > 0) { - // calculate sum of min carb impact from all active treatments - double totalMinCarbsImpact = 0d; - for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { - AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); - totalMinCarbsImpact += c.min5minCarbImpact; - } - - // figure out how many carbs that represents - // but always assume at least 3mg/dL/5m (default) absorption per active treatment - double ci = Math.max(deviation, totalMinCarbsImpact); - autosensData.absorbed = ci * profile.getIc(bgTime) / sens; - // and add that to the running total carbsAbsorbed - autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); - autosensData.substractAbosorbedCarbs(); - } - autosensData.removeOldCarbs(bgTime); - autosensData.cob += autosensData.carbsFromBolus; - autosensData.deviation = deviation; - autosensData.bgi = bgi; - autosensData.delta = delta; - - // calculate autosens only without COB - if (autosensData.cob <= 0) { - if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { - autosensData.pastSensitivity += "="; - autosensData.nonEqualDeviation = true; - } else if (deviation > 0) { - autosensData.pastSensitivity += "+"; - autosensData.nonEqualDeviation = true; - } else { - autosensData.pastSensitivity += "-"; - autosensData.nonEqualDeviation = true; - } - autosensData.nonCarbsDeviation = true; - } else { - autosensData.pastSensitivity += "C"; - } - //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); - - previous = autosensData; - autosensDataTable.put(bgTime, autosensData); - autosensData.autosensRatio = detectSensitivity(oldestTimeWithData, bgTime).ratio; - if (Config.logAutosensData) - log.debug(autosensData.log(bgTime)); - } - } - MainApp.bus().post(new EventAutosensCalculationFinished()); - //log.debug("Releasing calculateSensitivityData"); + log.debug("Bucketed data created. Size: " + bucketed_data.size()); } public static long oldestDataAvailable() { @@ -486,6 +349,21 @@ public class IobCobCalculatorPlugin implements PluginBase { } IobTotal bolusIob = MainApp.getConfigBuilder().getCalculationToTimeTreatments(time).round(); IobTotal basalIob = MainApp.getConfigBuilder().getCalculationToTimeTempBasals(time).round(); + if (OpenAPSSMBPlugin.getPlugin().isEnabled(PluginBase.APS)) { + // Add expected zere temp basal for next 240 mins + IobTotal basalIobWithZeroTemp = basalIob.clone(); + TemporaryBasal t = new TemporaryBasal(); + t.date = now + 60 * 1000L; + t.durationInMinutes = 240; + t.isAbsolute = true; + t.absoluteRate = 0; + if (t.date < time) { + IobTotal calc = t.iobCalc(time); + basalIobWithZeroTemp.plus(calc); + } + + basalIob.iobWithZeroTemp = basalIobWithZeroTemp; + } IobTotal iobTotal = IobTotal.combine(bolusIob, basalIob).round(); if (time < System.currentTimeMillis()) { @@ -580,11 +458,11 @@ public class IobCobCalculatorPlugin implements PluginBase { return null; } if (data.time < System.currentTimeMillis() - 11 * 60 * 1000) { - log.debug("AUTOSENSDATA null: data is old (" + reason + ")"); + log.debug("AUTOSENSDATA null: data is old (" + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + DateUtil.dateAndTimeString(data.time)); return null; } else { if (data == null) - log.debug("AUTOSENSDATA null: data == null (" + " " + reason + ")"); + log.debug("AUTOSENSDATA null: data == null (" + " " + reason + ") size()=" + autosensDataTable.size() + " lastdata=" + DateUtil.dateAndTimeString(data.time)); return data; } } @@ -606,13 +484,30 @@ public class IobCobCalculatorPlugin implements PluginBase { return array; } + public static IobTotal[] calculateIobArrayForSMB() { + Profile profile = MainApp.getConfigBuilder().getProfile(); + // predict IOB out to DIA plus 30m + long time = System.currentTimeMillis(); + time = roundUpTime(time); + int len = (4 * 60) / 5; + IobTotal[] array = new IobTotal[len]; + int pos = 0; + for (int i = 0; i < len; i++) { + long t = time + i * 5 * 60000; + IobTotal iob = calculateFromTreatmentsAndTempsSynchronized(t); + array[pos] = iob; + pos++; + } + return array; + } + public static AutosensResult detectSensitivityWithLock(long fromTime, long toTime) { synchronized (dataLock) { return detectSensitivity(fromTime, toTime); } } - private static AutosensResult detectSensitivity(long fromTime, long toTime) { + static AutosensResult detectSensitivity(long fromTime, long toTime) { return ConfigBuilderPlugin.getActiveSensitivity().detectSensitivity(fromTime, toTime); } @@ -625,15 +520,33 @@ public class IobCobCalculatorPlugin implements PluginBase { } @Subscribe - public void onNewBg(EventNewBG ev) { - sHandler.post(new Runnable() { - @Override - public void run() { - loadBgData(); - createBucketedData(); - calculateSensitivityData(); + public void onEventAppInitialized(EventAppInitialized ev) { + runCalculation("onEventAppInitialized", true, ev); + } + + @Subscribe + public void onEventNewBG(EventNewBG ev) { + stopCalculation("onEventNewBG"); + runCalculation("onEventNewBG", true, ev); + } + + private void stopCalculation(String from) { + if (thread != null && thread.getState() != Thread.State.TERMINATED) { + stopCalculationTrigger = true; + log.debug("Stopping calculation thread: " + from); + while (thread.getState() != Thread.State.TERMINATED) { + SystemClock.sleep(100); } - }); + log.debug("Calculation thread stopped: " + from); + } + } + + private void runCalculation(String from, boolean bgDataReload, Event cause) { + log.debug("Starting calculation thread: " + from); + if (thread == null || thread.getState() == Thread.State.TERMINATED) { + thread = new IobCobThread(this, from, bgDataReload, cause); + thread.start(); + } } @Subscribe @@ -647,66 +560,55 @@ public class IobCobCalculatorPlugin implements PluginBase { if (ev == null) { // on init no need of reset return; } + stopCalculation("onNewProfile"); synchronized (dataLock) { log.debug("Invalidating cached data because of new profile. IOB: " + iobTable.size() + " Autosens: " + autosensDataTable.size() + " records"); iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - sHandler.post(new Runnable() { - @Override - public void run() { - calculateSensitivityData(); - } - }); + runCalculation("onNewProfile", false, ev); } @Subscribe - public void onStatusEvent(EventPreferenceChange ev) { + public void onEventPreferenceChange(EventPreferenceChange ev) { if (ev.isChanged(R.string.key_openapsama_autosens_period) || ev.isChanged(R.string.key_age) || ev.isChanged(R.string.key_absorption_maxtime) ) { + stopCalculation("onEventPreferenceChange"); synchronized (dataLock) { log.debug("Invalidating cached data because of preference change. IOB: " + iobTable.size() + " Autosens: " + autosensDataTable.size() + " records"); iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - sHandler.post(new Runnable() { - @Override - public void run() { - calculateSensitivityData(); - } - }); + runCalculation("onEventPreferenceChange", false, ev); } } @Subscribe - public void onStatusEvent(EventConfigBuilderChange ev) { + public void onEventConfigBuilderChange(EventConfigBuilderChange ev) { + stopCalculation("onEventConfigBuilderChange"); synchronized (dataLock) { log.debug("Invalidating cached data because of configuration change. IOB: " + iobTable.size() + " Autosens: " + autosensDataTable.size() + " records"); iobTable = new LongSparseArray<>(); autosensDataTable = new LongSparseArray<>(); } - sHandler.post(new Runnable() { - @Override - public void run() { - calculateSensitivityData(); - } - }); + runCalculation("onEventConfigBuilderChange", false, ev); } // When historical data is changed (comming from NS etc) finished calculations after this date must be invalidated @Subscribe - public void onNewHistoryData(EventNewHistoryData ev) { + public void onEventNewHistoryData(EventNewHistoryData ev) { //log.debug("Locking onNewHistoryData"); + stopCalculation("onEventNewHistoryData"); synchronized (dataLock) { - long time = ev.time; + // clear up 5 min back for proper COB calculation + long time = ev.time - 5 * 60 * 1000L; log.debug("Invalidating cached data to: " + new Date(time).toLocaleString()); for (int index = iobTable.size() - 1; index >= 0; index--) { if (iobTable.keyAt(index) > time) { if (Config.logAutosensData) - if (Config.logAutosensData) - log.debug("Removing from iobTable: " + new Date(iobTable.keyAt(index)).toLocaleString()); + log.debug("Removing from iobTable: " + new Date(iobTable.keyAt(index)).toLocaleString()); iobTable.removeAt(index); } else { break; @@ -731,12 +633,7 @@ public class IobCobCalculatorPlugin implements PluginBase { } } } - sHandler.post(new Runnable() { - @Override - public void run() { - calculateSensitivityData(); - } - }); + runCalculation("onEventNewHistoryData", false, ev); //log.debug("Releasing onNewHistoryData"); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java new file mode 100644 index 0000000000..4f63cacf62 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/IobCobThread.java @@ -0,0 +1,248 @@ +package info.nightscout.androidaps.plugins.IobCobCalculator; + +import android.content.Context; +import android.os.PowerManager; +import android.support.v4.util.LongSparseArray; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.BgReading; +import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.events.Event; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; +import info.nightscout.androidaps.queue.QueueThread; + +import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.getBucketedData; +import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.oldestDataAvailable; +import static info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin.roundUpTime; + +/** + * Created by mike on 23.01.2018. + */ + +public class IobCobThread extends Thread { + private static Logger log = LoggerFactory.getLogger(QueueThread.class); + private final Event cause; + + private IobCobCalculatorPlugin iobCobCalculatorPlugin; + private boolean bgDataReload; + private String from; + + private PowerManager.WakeLock mWakeLock; + + public IobCobThread(IobCobCalculatorPlugin plugin, String from, boolean bgDataReload, Event cause) { + super(); + + this.iobCobCalculatorPlugin = plugin; + this.bgDataReload = bgDataReload; + this.from = from; + this.cause = cause; + + PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE); + mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "iobCobThread"); + } + + @Override + public final void run() { + mWakeLock.acquire(); + try { + if (MainApp.getConfigBuilder() == null) { + log.debug("Aborting calculation thread (ConfigBuilder not ready): " + from); + return; // app still initializing + } + if (MainApp.getConfigBuilder().getProfile() == null) { + log.debug("Aborting calculation thread (No profile): " + from); + return; // app still initializing + } + //log.debug("Locking calculateSensitivityData"); + + Object dataLock = iobCobCalculatorPlugin.dataLock; + + long oldestTimeWithData = oldestDataAvailable(); + + synchronized (dataLock) { + if (bgDataReload) { + iobCobCalculatorPlugin.loadBgData(); + iobCobCalculatorPlugin.createBucketedData(); + } + List bucketed_data = getBucketedData(); + LongSparseArray autosensDataTable = iobCobCalculatorPlugin.getAutosensDataTable(); + + if (bucketed_data == null || bucketed_data.size() < 3) { + log.debug("Aborting calculation thread (No bucketed data available): " + from); + return; + } + + long prevDataTime = roundUpTime(bucketed_data.get(bucketed_data.size() - 3).date); + log.debug("Prev data time: " + new Date(prevDataTime).toLocaleString()); + AutosensData previous = autosensDataTable.get(prevDataTime); + // start from oldest to be able sub cob + for (int i = bucketed_data.size() - 4; i >= 0; i--) { + if (iobCobCalculatorPlugin.stopCalculationTrigger) { + iobCobCalculatorPlugin.stopCalculationTrigger = false; + log.debug("Aborting calculation thread (trigger): " + from); + return; + } + // check if data already exists + long bgTime = bucketed_data.get(i).date; + bgTime = roundUpTime(bgTime); + if (bgTime > System.currentTimeMillis()) + continue; + Profile profile = MainApp.getConfigBuilder().getProfile(bgTime); + + AutosensData existing; + if ((existing = autosensDataTable.get(bgTime)) != null) { + previous = existing; + continue; + } + + if (profile == null) { + log.debug("Aborting calculation thread (no profile): " + from); + return; // profile not set yet + } + + if (profile.getIsf(bgTime) == null) { + log.debug("Aborting calculation thread (no ISF): " + from); + return; // profile not set yet + } + + if (Config.logAutosensData) + log.debug("Processing calculation thread: " + from + " (" + i + "/" + bucketed_data.size() + ")"); + + double sens = Profile.toMgdl(profile.getIsf(bgTime), profile.getUnits()); + + AutosensData autosensData = new AutosensData(); + autosensData.time = bgTime; + if (previous != null) + autosensData.activeCarbsList = new ArrayList<>(previous.activeCarbsList); + else + autosensData.activeCarbsList = new ArrayList<>(); + + //console.error(bgTime , bucketed_data[i].glucose); + double bg; + double avgDelta; + double delta; + bg = bucketed_data.get(i).value; + if (bg < 39 || bucketed_data.get(i + 3).value < 39) { + log.error("! value < 39"); + continue; + } + delta = (bg - bucketed_data.get(i + 1).value); + avgDelta = (bg - bucketed_data.get(i + 3).value) / 3; + + IobTotal iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTemps(bgTime); + + double bgi = -iob.activity * sens * 5; + double deviation = delta - bgi; + double avgDeviation = Math.round((avgDelta - bgi) * 1000) / 1000; + + double currentDeviation; + double slopeFromMaxDeviation = 0; + double slopeFromMinDeviation = 999; + double maxDeviation = 0; + double minDeviation = 999; + + // https://github.com/openaps/oref0/blob/master/lib/determine-basal/cob-autosens.js#L169 + if (i < bucketed_data.size() - 16) { // we need 1h of data to calculate minDeviationSlope + long hourago = bgTime + 10 * 1000 - 60 * 60 * 1000L; + AutosensData hourAgoData = iobCobCalculatorPlugin.getAutosensData(hourago); + if (hourAgoData != null) { + currentDeviation = hourAgoData.avgDeviation; + int initialIndex = autosensDataTable.indexOfKey(hourAgoData.time); + + for (int past = 1; past < 12; past++) { + AutosensData ad = autosensDataTable.valueAt(initialIndex + past); + double deviationSlope = (ad.avgDeviation - currentDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; + if (ad.avgDeviation > maxDeviation) { + slopeFromMaxDeviation = Math.min(0, deviationSlope); + maxDeviation = ad.avgDeviation; + } + if (avgDeviation < minDeviation) { + slopeFromMinDeviation = Math.max(0, deviationSlope); + minDeviation = avgDeviation; + } + + //if (Config.logAutosensData) + // log.debug("Deviations: " + new Date(bgTime) + new Date(ad.time) + " avgDeviation=" + avgDeviation + " deviationSlope=" + deviationSlope + " slopeFromMaxDeviation=" + slopeFromMaxDeviation + " slopeFromMinDeviation=" + slopeFromMinDeviation); + } + } + } + + List recentTreatments = MainApp.getConfigBuilder().getTreatments5MinBackFromHistory(bgTime); + for (int ir = 0; ir < recentTreatments.size(); ir++) { + autosensData.carbsFromBolus += recentTreatments.get(ir).carbs; + autosensData.activeCarbsList.add(new AutosensData.CarbsInPast(recentTreatments.get(ir))); + } + + + // if we are absorbing carbs + if (previous != null && previous.cob > 0) { + // calculate sum of min carb impact from all active treatments + double totalMinCarbsImpact = 0d; + for (int ii = 0; ii < autosensData.activeCarbsList.size(); ++ii) { + AutosensData.CarbsInPast c = autosensData.activeCarbsList.get(ii); + totalMinCarbsImpact += c.min5minCarbImpact; + } + + // figure out how many carbs that represents + // but always assume at least 3mg/dL/5m (default) absorption per active treatment + double ci = Math.max(deviation, totalMinCarbsImpact); + autosensData.absorbed = ci * profile.getIc(bgTime) / sens; + // and add that to the running total carbsAbsorbed + autosensData.cob = Math.max(previous.cob - autosensData.absorbed, 0d); + autosensData.substractAbosorbedCarbs(); + } + autosensData.removeOldCarbs(bgTime); + autosensData.cob += autosensData.carbsFromBolus; + autosensData.deviation = deviation; + autosensData.bgi = bgi; + autosensData.delta = delta; + autosensData.avgDelta = avgDelta; + autosensData.avgDeviation = avgDeviation; + autosensData.slopeFromMaxDeviation = slopeFromMaxDeviation; + autosensData.slopeFromMinDeviation = slopeFromMinDeviation; + + + // calculate autosens only without COB + if (autosensData.cob <= 0) { + if (Math.abs(deviation) < Constants.DEVIATION_TO_BE_EQUAL) { + autosensData.pastSensitivity += "="; + autosensData.nonEqualDeviation = true; + } else if (deviation > 0) { + autosensData.pastSensitivity += "+"; + autosensData.nonEqualDeviation = true; + } else { + autosensData.pastSensitivity += "-"; + autosensData.nonEqualDeviation = true; + } + autosensData.nonCarbsDeviation = true; + } else { + autosensData.pastSensitivity += "C"; + } + //log.debug("TIME: " + new Date(bgTime).toString() + " BG: " + bg + " SENS: " + sens + " DELTA: " + delta + " AVGDELTA: " + avgDelta + " IOB: " + iob.iob + " ACTIVITY: " + iob.activity + " BGI: " + bgi + " DEVIATION: " + deviation); + + previous = autosensData; + autosensDataTable.put(bgTime, autosensData); + autosensData.autosensRatio = iobCobCalculatorPlugin.detectSensitivity(oldestTimeWithData, bgTime).ratio; + if (Config.logAutosensData) + log.debug(autosensData.log(bgTime)); + } + } + MainApp.bus().post(new EventAutosensCalculationFinished(cause)); + log.debug("Finishing calculation thread: " + from); + } finally { + mWakeLock.release(); + } + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java index ea73915436..2cb9b7ca4c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/IobCobCalculator/events/EventAutosensCalculationFinished.java @@ -1,5 +1,6 @@ package info.nightscout.androidaps.plugins.IobCobCalculator.events; +import info.nightscout.androidaps.events.Event; import info.nightscout.androidaps.events.EventLoop; /** @@ -7,4 +8,9 @@ import info.nightscout.androidaps.events.EventLoop; */ public class EventAutosensCalculationFinished extends EventLoop { + public Event cause; + + public EventAutosensCalculationFinished(Event cause) { + this.cause = cause; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java index f8ca0ab8c4..1550edd192 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/APSResult.java @@ -1,17 +1,22 @@ package info.nightscout.androidaps.plugins.Loop; -import android.os.Parcel; -import android.os.Parcelable; import android.text.Html; import android.text.Spanned; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.utils.DecimalFormatter; @@ -22,36 +27,64 @@ import info.nightscout.utils.DecimalFormatter; public class APSResult { private static Logger log = LoggerFactory.getLogger(APSResult.class); + public Date date; public String reason; public double rate; public int duration; - public boolean changeRequested = false; + public boolean tempBasalReqested = false; + public boolean bolusRequested = false; + public IobTotal iob; + public JSONObject json = new JSONObject(); + public boolean hasPredictions = false; + public double smb = 0d; // super micro bolus in units + public long deliverAt = 0; @Override public String toString() { final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - if (changeRequested) { + if (isChangeRequested()) { + String ret; + // rate if (rate == 0 && duration == 0) - return MainApp.sResources.getString(R.string.canceltemp); + ret = MainApp.sResources.getString(R.string.canceltemp) + "\n"; + else if (rate == -1) + ret = MainApp.sResources.getString(R.string.let_temp_basal_run) + "\n"; else - return MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + - "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%)\n" + - MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to0Decimal(duration) + " min\n" + - MainApp.sResources.getString(R.string.reason) + ": " + reason; + ret = MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + + "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%) \n" + + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min\n"; + + // smb + if (smb != 0) + ret += ("SMB: " + DecimalFormatter.to2Decimal(smb) + " U\n"); + + // reason + ret += MainApp.sResources.getString(R.string.reason) + ": " + reason; + return ret; } else return MainApp.sResources.getString(R.string.nochangerequested); } public Spanned toSpanned() { final PumpInterface pump = ConfigBuilderPlugin.getActivePump(); - if (changeRequested) { - String ret = ""; - if (rate == 0 && duration == 0) ret = MainApp.sResources.getString(R.string.canceltemp); + if (isChangeRequested()) { + String ret; + // rate + if (rate == 0 && duration == 0) + ret = MainApp.sResources.getString(R.string.canceltemp) + "
"; + else if (rate == -1) + ret = MainApp.sResources.getString(R.string.let_temp_basal_run) + "
"; else ret = "" + MainApp.sResources.getString(R.string.rate) + ": " + DecimalFormatter.to2Decimal(rate) + " U/h " + - "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%)
" + - "" + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min
" + - "" + MainApp.sResources.getString(R.string.reason) + ": " + reason.replace("<", "<").replace(">", ">"); + "(" + DecimalFormatter.to2Decimal(rate / pump.getBaseBasalRate() * 100) + "%)
" + + "" + MainApp.sResources.getString(R.string.duration) + ": " + DecimalFormatter.to2Decimal(duration) + " min
"; + + // smb + if (smb != 0) + ret += ("" + "SMB" + ": " + DecimalFormatter.to2Decimal(smb) + " U
"); + + // reason + ret += "" + MainApp.sResources.getString(R.string.reason) + ": " + reason.replace("<", "<").replace(">", ">"); return Html.fromHtml(ret); } else return Html.fromHtml(MainApp.sResources.getString(R.string.nochangerequested)); @@ -62,17 +95,19 @@ public class APSResult { public APSResult clone() { APSResult newResult = new APSResult(); - newResult.reason = new String(reason); + newResult.reason = reason; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalReqested = tempBasalReqested; + newResult.bolusRequested = bolusRequested; + newResult.iob = iob; return newResult; } public JSONObject json() { JSONObject json = new JSONObject(); try { - if (changeRequested) { + if (isChangeRequested()) { json.put("rate", rate); json.put("duration", duration); json.put("reason", reason); @@ -82,4 +117,105 @@ public class APSResult { } return json; } + + public List getPredictions() { + List array = new ArrayList<>(); + try { + long startTime = date.getTime(); + if (json.has("predBGs")) { + JSONObject predBGs = json.getJSONObject("predBGs"); + if (predBGs.has("IOB")) { + JSONArray iob = predBGs.getJSONArray("IOB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isIOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("aCOB")) { + JSONArray iob = predBGs.getJSONArray("aCOB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isaCOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("COB")) { + JSONArray iob = predBGs.getJSONArray("COB"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isCOBPrediction = true; + array.add(bg); + } + } + if (predBGs.has("UAM")) { + JSONArray iob = predBGs.getJSONArray("UAM"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isUAMPrediction = true; + array.add(bg); + } + } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + for (int i = 1; i < iob.length(); i++) { + BgReading bg = new BgReading(); + bg.value = iob.getInt(i); + bg.date = startTime + i * 5 * 60 * 1000L; + bg.isZTPrediction = true; + array.add(bg); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + return array; + } + + public long getLatestPredictionsTime() { + long latest = 0; + try { + long startTime = date.getTime(); + if (json.has("predBGs")) { + JSONObject predBGs = json.getJSONObject("predBGs"); + if (predBGs.has("IOB")) { + JSONArray iob = predBGs.getJSONArray("IOB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("aCOB")) { + JSONArray iob = predBGs.getJSONArray("aCOB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("COB")) { + JSONArray iob = predBGs.getJSONArray("COB"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("UAM")) { + JSONArray iob = predBGs.getJSONArray("UAM"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + if (predBGs.has("ZT")) { + JSONArray iob = predBGs.getJSONArray("ZT"); + latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return latest; + } + + public boolean isChangeRequested() { + return tempBasalReqested || bolusRequested; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java index 539b9991a3..2debfa34c5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java @@ -6,7 +6,7 @@ import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; -import android.support.v7.app.NotificationCompat; +import android.support.v4.app.NotificationCompat; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; @@ -30,9 +30,12 @@ import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.events.EventAutosensCalculationFinished; +import info.nightscout.androidaps.plugins.Loop.events.EventLoopResult; import info.nightscout.androidaps.plugins.Loop.events.EventLoopSetLastRunGui; import info.nightscout.androidaps.plugins.Loop.events.EventLoopUpdateGui; import info.nightscout.androidaps.plugins.Loop.events.EventNewOpenLoopNotification; +import info.nightscout.androidaps.plugins.Treatments.TreatmentsPlugin; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; @@ -148,12 +151,16 @@ public class LoopPlugin implements PluginBase { @Subscribe public void onStatusEvent(final EventTreatmentChange ev) { - invoke("EventTreatmentChange", true); + if (ev.treatment == null || !ev.treatment.isSMB){ + invoke("EventTreatmentChange", true); + } } @Subscribe - public void onStatusEvent(final EventNewBG ev) { - invoke("EventNewBG", true); + public void onStatusEvent(final EventAutosensCalculationFinished ev) { + if (ev.cause instanceof EventNewBG) { + invoke(ev.getClass().getSimpleName() + "(" + ev.cause.getClass().getSimpleName() + ")", true); + } } public long suspendedTo() { @@ -283,6 +290,14 @@ public class LoopPlugin implements PluginBase { // check rate for constrais final APSResult resultAfterConstraints = result.clone(); resultAfterConstraints.rate = constraintsInterface.applyBasalConstraints(resultAfterConstraints.rate); + resultAfterConstraints.smb = constraintsInterface.applyBolusConstraints(resultAfterConstraints.smb); + + // safety check for multiple SMBs + long lastBolusTime = TreatmentsPlugin.getPlugin().getLastBolusTime(); + if (lastBolusTime != 0 && lastBolusTime + 3 * 60 * 1000 > System.currentTimeMillis()) { + log.debug("SMB requsted but still in 3 min interval"); + resultAfterConstraints.smb = 0; + } if (lastRun == null) lastRun = new LastRun(); lastRun.request = result; @@ -305,8 +320,10 @@ public class LoopPlugin implements PluginBase { return; } + MainApp.bus().post(new EventLoopResult(resultAfterConstraints)); + if (constraintsInterface.isClosedModeEnabled()) { - if (result.changeRequested) { + if (result.isChangeRequested()) { final PumpEnactResult waiting = new PumpEnactResult(); final PumpEnactResult previousResult = lastRun.setByPump; waiting.queued = true; @@ -330,7 +347,7 @@ public class LoopPlugin implements PluginBase { lastRun.source = null; } } else { - if (result.changeRequested && allowNotification) { + if (result.isChangeRequested() && allowNotification) { NotificationCompat.Builder builder = new NotificationCompat.Builder(MainApp.instance().getApplicationContext()); builder.setSmallIcon(R.drawable.notif_icon) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/events/EventLoopResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/events/EventLoopResult.java new file mode 100644 index 0000000000..5cbb24ea22 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/events/EventLoopResult.java @@ -0,0 +1,12 @@ +package info.nightscout.androidaps.plugins.Loop.events; + +import info.nightscout.androidaps.plugins.Loop.APSResult; + +public class EventLoopResult { + public final APSResult apsResult; + + public EventLoopResult(APSResult apsResult) { + this.apsResult = apsResult; + } +} + diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java index 315e9724ce..d1755dd4c1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/NSClientInternal/data/NSDeviceStatus.java @@ -124,7 +124,7 @@ public class NSDeviceStatus { static DeviceStatusPumpData deviceStatusPumpData = null; public Spanned getExtendedPumpStatus() { - if (deviceStatusPumpData.extended != null) + if (deviceStatusPumpData != null && deviceStatusPumpData.extended != null) return deviceStatusPumpData.extended; return Html.fromHtml(""); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java index 466026e986..66d3ab2b96 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalAdapterAMAJS.java @@ -29,6 +29,7 @@ import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.Loop.ScriptReader; import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; +import info.nightscout.androidaps.plugins.OpenAPSSMB.SMBDefaults; import info.nightscout.utils.SP; public class DetermineBasalAdapterAMAJS { @@ -189,8 +190,7 @@ public class DetermineBasalAdapterAMAJS { GlucoseStatus glucoseStatus, MealData mealData, double autosensDataRatio, - boolean tempTargetSet, - double min_5m_carbimpact) throws JSONException { + boolean tempTargetSet) throws JSONException { String units = profile.getUnits(); @@ -211,7 +211,7 @@ public class DetermineBasalAdapterAMAJS { mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); mProfile.put("autosens_adjust_targets", SP.getBoolean("openapsama_autosens_adjusttargets", true)); - mProfile.put("min_5m_carbimpact", SP.getDouble("openapsama_min_5m_carbimpact", 3d)); + mProfile.put("min_5m_carbimpact", SP.getInt("openapsama_min_5m_carbimpact", SMBDefaults.min_5m_carbimpact)); if (units.equals(Constants.MMOL)) { mProfile.put("out_units", "mmol/L"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java index e4c23ae043..50dde42674 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/DetermineBasalResultAMA.java @@ -1,35 +1,28 @@ package info.nightscout.androidaps.plugins.OpenAPSAMA; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.Date; -import java.util.List; -import info.nightscout.androidaps.db.BgReading; import info.nightscout.androidaps.plugins.Loop.APSResult; -import info.nightscout.androidaps.data.IobTotal; public class DetermineBasalResultAMA extends APSResult { private static Logger log = LoggerFactory.getLogger(DetermineBasalResultAMA.class); - public Date date; - public JSONObject json = new JSONObject(); public double eventualBG; public double snoozeBG; - public IobTotal iob; public DetermineBasalResultAMA(NativeObject result, JSONObject j) { + this(); date = new Date(); json = j; if (result.containsKey("error")) { reason = result.get("error").toString(); - changeRequested = false; + tempBasalReqested = false; rate = -1; duration = -1; } else { @@ -37,36 +30,37 @@ public class DetermineBasalResultAMA extends APSResult { if (result.containsKey("eventualBG")) eventualBG = (Double) result.get("eventualBG"); if (result.containsKey("snoozeBG")) snoozeBG = (Double) result.get("snoozeBG"); if (result.containsKey("rate")) { - rate = (Double) result.get("rate"); + rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; - changeRequested = true; + tempBasalReqested = true; } else { rate = -1; - changeRequested = false; + tempBasalReqested = false; } if (result.containsKey("duration")) { - duration = ((Double)result.get("duration")).intValue(); + duration = ((Double) result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; - changeRequested = false; + tempBasalReqested = false; } } + bolusRequested = false; } public DetermineBasalResultAMA() { + hasPredictions = true; } @Override public DetermineBasalResultAMA clone() { DetermineBasalResultAMA newResult = new DetermineBasalResultAMA(); - newResult.reason = new String(reason); + newResult.reason = reason; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalReqested = tempBasalReqested; newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; try { newResult.json = new JSONObject(json.toString()); @@ -90,72 +84,4 @@ public class DetermineBasalResultAMA extends APSResult { return null; } - public List getPredictions() { - List array = new ArrayList<>(); - try { - long startTime = date.getTime(); - if (json.has("predBGs")) { - JSONObject predBGs = json.getJSONObject("predBGs"); - if (predBGs.has("IOB")) { - JSONArray iob = predBGs.getJSONArray("IOB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - if (predBGs.has("aCOB")) { - JSONArray iob = predBGs.getJSONArray("aCOB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - if (predBGs.has("COB")) { - JSONArray iob = predBGs.getJSONArray("COB"); - for (int i = 1; i < iob.length(); i ++) { - BgReading bg = new BgReading(); - bg.value = iob.getInt(i); - bg.date = startTime + i * 5 * 60 * 1000L; - bg.isPrediction = true; - array.add(bg); - } - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return array; - } - - public long getLatestPredictionsTime() { - long latest = 0; - try { - long startTime = date.getTime(); - if (json.has("predBGs")) { - JSONObject predBGs = json.getJSONObject("predBGs"); - if (predBGs.has("IOB")) { - JSONArray iob = predBGs.getJSONArray("IOB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - if (predBGs.has("aCOB")) { - JSONArray iob = predBGs.getJSONArray("aCOB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - if (predBGs.has("COB")) { - JSONArray iob = predBGs.getJSONArray("COB"); - latest = Math.max(latest, startTime + (iob.length() - 1) * 5 * 60 * 1000L); - } - } - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - - return latest; - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java index 4c14f33533..14d5396817 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSAMA/OpenAPSAMAPlugin.java @@ -30,7 +30,6 @@ import info.nightscout.utils.NSUpload; import info.nightscout.utils.Profiler; import info.nightscout.utils.Round; import info.nightscout.utils.SP; -import info.nightscout.utils.SafeParse; import info.nightscout.utils.ToastUtils; /** @@ -233,8 +232,7 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { try { determineBasalAdapterAMAJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, lastAutosensResult.ratio, //autosensDataRatio - isTempTarget, - SafeParse.stringToDouble(SP.getString("openapsama_min_5m_carbimpact", "3.0"))//min_5m_carbimpact + isTempTarget ); } catch (JSONException e) { log.error("Unable to set data: " + e.toString()); @@ -245,15 +243,15 @@ public class OpenAPSAMAPlugin implements PluginBase, APSInterface { Profiler.log(log, "AMA calculation", start); // Fix bug determine basal if (determineBasalResultAMA.rate == 0d && determineBasalResultAMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) - determineBasalResultAMA.changeRequested = false; + determineBasalResultAMA.tempBasalReqested = false; // limit requests on openloop mode if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultAMA.rate == 0 && determineBasalResultAMA.duration == 0) { // going to cancel } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { - determineBasalResultAMA.changeRequested = false; + determineBasalResultAMA.tempBasalReqested = false; } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultAMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) - determineBasalResultAMA.changeRequested = false; + determineBasalResultAMA.tempBasalReqested = false; } determineBasalResultAMA.iob = iobArray[0]; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java index e132c9f953..3f40c02980 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/DetermineBasalResultMA.java @@ -6,7 +6,6 @@ import org.mozilla.javascript.NativeObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.data.IobTotal; import info.nightscout.androidaps.plugins.Loop.APSResult; public class DetermineBasalResultMA extends APSResult { @@ -16,13 +15,12 @@ public class DetermineBasalResultMA extends APSResult { public double eventualBG; public double snoozeBG; public String mealAssist; - public IobTotal iob; public DetermineBasalResultMA(NativeObject result, JSONObject j) { json = j; if (result.containsKey("error")) { reason = (String) result.get("error"); - changeRequested = false; + tempBasalReqested = false; rate = -1; duration = -1; mealAssist = ""; @@ -33,17 +31,17 @@ public class DetermineBasalResultMA extends APSResult { if (result.containsKey("rate")) { rate = (Double) result.get("rate"); if (rate < 0d) rate = 0d; - changeRequested = true; + tempBasalReqested = true; } else { rate = -1; - changeRequested = false; + tempBasalReqested = false; } if (result.containsKey("duration")) { duration = ((Double) result.get("duration")).intValue(); //changeRequested as above } else { duration = -1; - changeRequested = false; + tempBasalReqested = false; } if (result.containsKey("mealAssist")) { mealAssist = result.get("mealAssist").toString(); @@ -60,10 +58,10 @@ public class DetermineBasalResultMA extends APSResult { newResult.reason = new String(reason); newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalReqested = isChangeRequested(); newResult.rate = rate; newResult.duration = duration; - newResult.changeRequested = changeRequested; + newResult.tempBasalReqested = isChangeRequested(); try { newResult.json = new JSONObject(json.toString()); @@ -72,7 +70,7 @@ public class DetermineBasalResultMA extends APSResult { } newResult.eventualBG = eventualBG; newResult.snoozeBG = snoozeBG; - newResult.mealAssist = new String(mealAssist); + newResult.mealAssist = mealAssist; return newResult; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java index d56dc44b89..3119a17a96 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSMA/OpenAPSMAPlugin.java @@ -229,15 +229,15 @@ public class OpenAPSMAPlugin implements PluginBase, APSInterface { DetermineBasalResultMA determineBasalResultMA = determineBasalAdapterMAJS.invoke(); // Fix bug determinef basal if (determineBasalResultMA.rate == 0d && determineBasalResultMA.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) - determineBasalResultMA.changeRequested = false; + determineBasalResultMA.tempBasalReqested = false; // limit requests on openloop mode if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultMA.rate == 0 && determineBasalResultMA.duration == 0) { // going to cancel } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { - determineBasalResultMA.changeRequested = false; + determineBasalResultMA.tempBasalReqested = false; } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultMA.rate - ConfigBuilderPlugin.getActivePump().getBaseBasalRate()) < 0.1) - determineBasalResultMA.changeRequested = false; + determineBasalResultMA.tempBasalReqested = false; } determineBasalResultMA.iob = iobTotal; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java new file mode 100644 index 0000000000..57c879a5cd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalAdapterSMBJS.java @@ -0,0 +1,344 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.javascript.Callable; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeJSON; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Loop.ScriptReader; +import info.nightscout.androidaps.plugins.OpenAPSMA.LoggerCallback; +import info.nightscout.androidaps.plugins.SourceDexcomG5.SourceDexcomG5Plugin; +import info.nightscout.utils.SP; +import info.nightscout.utils.SafeParse; + +public class DetermineBasalAdapterSMBJS { + private static Logger log = LoggerFactory.getLogger(DetermineBasalAdapterSMBJS.class); + + + private ScriptReader mScriptReader = null; + private JSONObject mProfile; + private JSONObject mGlucoseStatus; + private JSONArray mIobData; + private JSONObject mMealData; + private JSONObject mCurrentTemp; + private JSONObject mAutosensData = null; + private boolean mMicrobolusAllowed; + + private String storedCurrentTemp = null; + private String storedIobData = null; + + private String storedGlucoseStatus = null; + private String storedProfile = null; + private String storedMeal_data = null; + private String storedAutosens_data = null; + private String storedMicroBolusAllowed = null; + + private String scriptDebug = ""; + + /** + * Main code + */ + + public DetermineBasalAdapterSMBJS(ScriptReader scriptReader) throws IOException { + mScriptReader = scriptReader; + } + + + public DetermineBasalResultSMB invoke() { + + + log.debug(">>> Invoking detemine_basal <<<"); + log.debug("Glucose status: " + (storedGlucoseStatus = mGlucoseStatus.toString())); + log.debug("IOB data: " + (storedIobData = mIobData.toString())); + log.debug("Current temp: " + (storedCurrentTemp = mCurrentTemp.toString())); + log.debug("Profile: " + (storedProfile = mProfile.toString())); + log.debug("Meal data: " + (storedMeal_data = mMealData.toString())); + if (mAutosensData != null) + log.debug("Autosens data: " + (storedAutosens_data = mAutosensData.toString())); + else + log.debug("Autosens data: " + (storedAutosens_data = "undefined")); + log.debug("Reservoir data: " + "undefined"); + log.debug("MicroBolusAllowed: " + (storedMicroBolusAllowed = "" + mMicrobolusAllowed)); + + DetermineBasalResultSMB determineBasalResultSMB = null; + + Context rhino = Context.enter(); + Scriptable scope = rhino.initStandardObjects(); + // Turn off optimization to make Rhino Android compatible + rhino.setOptimizationLevel(-1); + + try { + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback.class); + Scriptable myLogger = rhino.newObject(scope, "LoggerCallback", null); + scope.put("console2", scope, myLogger); + rhino.evaluateString(scope, readFile("OpenAPSAMA/loggerhelper.js"), "JavaScript", 0, null); + + //set module parent + rhino.evaluateString(scope, "var module = {\"parent\":Boolean(1)};", "JavaScript", 0, null); + rhino.evaluateString(scope, "var round_basal = function round_basal(basal, profile) { return basal; };", "JavaScript", 0, null); + rhino.evaluateString(scope, "require = function() {return round_basal;};", "JavaScript", 0, null); + + //generate functions "determine_basal" and "setTempBasal" + rhino.evaluateString(scope, readFile("OpenAPSSMB/determine-basal.js"), "JavaScript", 0, null); + rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null); + Object determineBasalObj = scope.get("determine_basal", scope); + Object setTempBasalFunctionsObj = scope.get("tempBasalFunctions", scope); + + //call determine-basal + if (determineBasalObj instanceof Function && setTempBasalFunctionsObj instanceof NativeObject) { + Function determineBasalJS = (Function) determineBasalObj; + + //prepare parameters + Object[] params = new Object[]{ + makeParam(mGlucoseStatus, rhino, scope), + makeParam(mCurrentTemp, rhino, scope), + makeParamArray(mIobData, rhino, scope), + makeParam(mProfile, rhino, scope), + makeParam(mAutosensData, rhino, scope), + makeParam(mMealData, rhino, scope), + setTempBasalFunctionsObj, + new Boolean(mMicrobolusAllowed), + makeParam(null, rhino, scope) // reservoir data as undefined + }; + + + NativeObject jsResult = (NativeObject) determineBasalJS.call(rhino, scope, scope, params); + scriptDebug = LoggerCallback.getScriptDebug(); + + // Parse the jsResult object to a JSON-String + String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); + if (Config.logAPSResult) + log.debug("Result: " + result); + try { + determineBasalResultSMB = new DetermineBasalResultSMB(new JSONObject(result)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + } else { + log.debug("Problem loading JS Functions"); + } + } catch (IOException e) { + log.debug("IOException"); + } catch (RhinoException e) { + log.error("RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()); + } catch (IllegalAccessException e) { + log.error(e.toString()); + } catch (InstantiationException e) { + log.error(e.toString()); + } catch (InvocationTargetException e) { + log.error(e.toString()); + } finally { + Context.exit(); + } + + storedGlucoseStatus = mGlucoseStatus.toString(); + storedIobData = mIobData.toString(); + storedCurrentTemp = mCurrentTemp.toString(); + storedProfile = mProfile.toString(); + storedMeal_data = mMealData.toString(); + + return determineBasalResultSMB; + + } + + String getGlucoseStatusParam() { + return storedGlucoseStatus; + } + + String getCurrentTempParam() { + return storedCurrentTemp; + } + + String getIobDataParam() { + return storedIobData; + } + + String getProfileParam() { + return storedProfile; + } + + String getMealDataParam() { + return storedMeal_data; + } + + String getAutosensDataParam() { + return storedAutosens_data; + } + + String getMicroBolusAllowedParam() { + return storedMicroBolusAllowed; + } + + String getScriptDebug() { + return scriptDebug; + } + + public void setData(Profile profile, + double maxIob, + double maxBasal, + double minBg, + double maxBg, + double targetBg, + double basalrate, + IobTotal[] iobArray, + GlucoseStatus glucoseStatus, + MealData mealData, + double autosensDataRatio, + boolean tempTargetSet, + boolean microBolusAllowed + ) throws JSONException { + + String units = profile.getUnits(); + + mProfile = new JSONObject(); + + mProfile.put("max_iob", maxIob); + mProfile.put("dia", profile.getDia()); + mProfile.put("type", "current"); + mProfile.put("max_daily_basal", profile.getMaxDailyBasal()); + mProfile.put("max_basal", maxBasal); + mProfile.put("min_bg", minBg); + mProfile.put("max_bg", maxBg); + mProfile.put("target_bg", targetBg); + mProfile.put("carb_ratio", profile.getIc()); + mProfile.put("sens", Profile.toMgdl(profile.getIsf().doubleValue(), units)); + mProfile.put("max_daily_safety_multiplier", SP.getInt("openapsama_max_daily_safety_multiplier", 3)); + mProfile.put("current_basal_safety_multiplier", SP.getInt("openapsama_current_basal_safety_multiplier", 4)); + + mProfile.put("high_temptarget_raises_sensitivity", SMBDefaults.high_temptarget_raises_sensitivity); + mProfile.put("low_temptarget_lowers_sensitivity", SMBDefaults.low_temptarget_lowers_sensitivity); + mProfile.put("sensitivity_raises_target", SMBDefaults.sensitivity_raises_target); + mProfile.put("resistance_lowers_target", SMBDefaults.resistance_lowers_target); + mProfile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments); + mProfile.put("exercise_mode", SMBDefaults.exercise_mode); + mProfile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target); + mProfile.put("maxCOB", SMBDefaults.maxCOB); + mProfile.put("skip_neutral_temps", SMBDefaults.skip_neutral_temps); + mProfile.put("min_5m_carbimpact", SP.getInt("openapsama_min_5m_carbimpact", SMBDefaults.min_5m_carbimpact));; + mProfile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap); + mProfile.put("enableUAM", SP.getBoolean(R.string.key_use_uam, false)); + mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); + mProfile.put("enableSMB_with_COB", SP.getBoolean(R.string.key_use_smb, false) && SP.getBoolean(R.string.key_enableSMB_with_COB, false)); + mProfile.put("enableSMB_with_temptarget", SP.getBoolean(R.string.key_use_smb, false) && SP.getBoolean(R.string.key_enableSMB_with_temptarget, false)); + mProfile.put("allowSMB_with_high_temptarget", SP.getBoolean(R.string.key_use_smb, false) && SP.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)); + mProfile.put("enableSMB_always", SP.getBoolean(R.string.key_use_smb, false) && SP.getBoolean(R.string.key_enableSMB_always, false) && ConfigBuilderPlugin.getActiveBgSource().advancedFilteringSupported()); + mProfile.put("enableSMB_after_carbs", SP.getBoolean(R.string.key_use_smb, false) && SP.getBoolean(R.string.key_enableSMB_after_carbs, false) && ConfigBuilderPlugin.getActiveBgSource().advancedFilteringSupported()); + mProfile.put("maxSMBBasalMinutes", SP.getInt("key_smbmaxminutes", SMBDefaults.maxSMBBasalMinutes)); + mProfile.put("carbsReqThreshold", SMBDefaults.carbsReqThreshold); + + mProfile.put("current_basal", basalrate); + mProfile.put("temptargetSet", tempTargetSet); + mProfile.put("autosens_max", SafeParse.stringToDouble(SP.getString("openapsama_autosens_max", "1.2"))); + + if (units.equals(Constants.MMOL)) { + mProfile.put("out_units", "mmol/L"); + } + + + mCurrentTemp = new JSONObject(); + mCurrentTemp.put("temp", "absolute"); + mCurrentTemp.put("duration", MainApp.getConfigBuilder().getTempBasalRemainingMinutesFromHistory()); + mCurrentTemp.put("rate", MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()); + + // as we have non default temps longer than 30 mintues + TemporaryBasal tempBasal = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); + if (tempBasal != null) { + mCurrentTemp.put("minutesrunning", tempBasal.getRealDuration()); + } + + mIobData = IobCobCalculatorPlugin.convertToJSONArray(iobArray); + + mGlucoseStatus = new JSONObject(); + mGlucoseStatus.put("glucose", glucoseStatus.glucose); + + if (SP.getBoolean("always_use_shortavg", false)) { + mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); + } else { + mGlucoseStatus.put("delta", glucoseStatus.delta); + } + mGlucoseStatus.put("short_avgdelta", glucoseStatus.short_avgdelta); + mGlucoseStatus.put("long_avgdelta", glucoseStatus.long_avgdelta); + mGlucoseStatus.put("date", glucoseStatus.date); + + mMealData = new JSONObject(); + mMealData.put("carbs", mealData.carbs); + mMealData.put("boluses", mealData.boluses); + mMealData.put("mealCOB", mealData.mealCOB); + mMealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation); + mMealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation); + mMealData.put("lastBolusTime", mealData.lastBolusTime); + mMealData.put("lastCarbTime", mealData.lastCarbTime); + + + if (MainApp.getConfigBuilder().isAMAModeEnabled()) { + mAutosensData = new JSONObject(); + mAutosensData.put("ratio", autosensDataRatio); + } else { + mAutosensData = new JSONObject(); + mAutosensData.put("ratio", 1.0); + } + mMicrobolusAllowed = microBolusAllowed; + + } + + public Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { + + if (jsonObject == null) return Undefined.instance; + + Object param = NativeJSON.parse(rhino, scope, jsonObject.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + + public Object makeParamArray(JSONArray jsonArray, Context rhino, Scriptable scope) { + //Object param = NativeJSON.parse(rhino, scope, "{myarray: " + jsonArray.toString() + " }", new Callable() { + Object param = NativeJSON.parse(rhino, scope, jsonArray.toString(), new Callable() { + @Override + public Object call(Context context, Scriptable scriptable, Scriptable scriptable1, Object[] objects) { + return objects[1]; + } + }); + return param; + } + + public String readFile(String filename) throws IOException { + byte[] bytes = mScriptReader.readFile(filename); + String string = new String(bytes, "UTF-8"); + if (string.startsWith("#!/usr/bin/env node")) { + string = string.substring(20); + } + return string; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java new file mode 100644 index 0000000000..0320b3633d --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/DetermineBasalResultSMB.java @@ -0,0 +1,104 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; + +import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.utils.DateUtil; + +public class DetermineBasalResultSMB extends APSResult { + private static final Logger log = LoggerFactory.getLogger(DetermineBasalResultSMB.class); + + public double eventualBG; + public double snoozeBG; + public double insulinReq; + public double carbsReq; + + public DetermineBasalResultSMB(JSONObject result) { + this(); + date = new Date(); + json = result; + try { + if (result.has("error")) { + reason = result.getString("error"); + return; + } + + reason = result.getString("reason"); + if (result.has("eventualBG")) eventualBG = result.getDouble("eventualBG"); + if (result.has("snoozeBG")) snoozeBG = result.getDouble("snoozeBG"); + if (result.has("insulinReq")) insulinReq = result.getDouble("insulinReq"); + if (result.has("carbsReq")) carbsReq = result.getDouble("carbsReq"); + + if (result.has("rate") && result.has("duration")) { + tempBasalReqested = true; + rate = result.getDouble("rate"); + if (rate < 0d) rate = 0d; + duration = result.getInt("duration"); + } else { + rate = -1; + duration = -1; + } + + if (result.has("units")) { + bolusRequested = true; + smb = result.getDouble("units"); + } else { + smb = 0d; + } + + if (result.has("deliverAt")) { + String date = result.getString("deliverAt"); + try { + deliverAt = DateUtil.fromISODateString(date).getTime(); + } catch (Exception e) { + log.warn("Error parsing 'deliverAt' date: " + date, e); + } + } + } catch (JSONException e) { + log.error("Error parsing determine-basal result JSON", e); + } + } + + public DetermineBasalResultSMB() { + hasPredictions = true; + } + + @Override + public DetermineBasalResultSMB clone() { + DetermineBasalResultSMB newResult = new DetermineBasalResultSMB(); + newResult.reason = reason; + newResult.rate = rate; + newResult.duration = duration; + newResult.tempBasalReqested = tempBasalReqested; + newResult.bolusRequested = bolusRequested; + newResult.rate = rate; + newResult.duration = duration; + newResult.smb = smb; + newResult.deliverAt = deliverAt; + + try { + newResult.json = new JSONObject(json.toString()); + } catch (JSONException e) { + log.error("Error clone parsing determine-basal result", e); + } + newResult.eventualBG = eventualBG; + newResult.snoozeBG = snoozeBG; + newResult.date = date; + return newResult; + } + + @Override + public JSONObject json() { + try { + return new JSONObject(this.json.toString()); + } catch (JSONException e) { + log.error("Error converting determine-basal result to JSON", e); + } + return null; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java new file mode 100644 index 0000000000..ef515c68f2 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBFragment.java @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import com.crashlytics.android.answers.Answers; +import com.crashlytics.android.answers.CustomEvent; +import com.squareup.otto.Subscribe; + +import org.json.JSONArray; +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.plugins.Common.SubscriberFragment; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.utils.JSONFormatter; + +public class OpenAPSSMBFragment extends SubscriberFragment implements View.OnClickListener { + private static Logger log = LoggerFactory.getLogger(OpenAPSSMBFragment.class); + + 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: + OpenAPSSMBPlugin.getPlugin().invoke("OpenAPSSMB button"); + Answers.getInstance().logCustom(new CustomEvent("OpenAPS_SMB_Run")); + 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(new Runnable() { + @Override + public void run() { + OpenAPSSMBPlugin plugin = OpenAPSSMBPlugin.getPlugin(); + DetermineBasalResultSMB lastAPSResult = plugin.lastAPSResult; + if (lastAPSResult != null) { + resultView.setText(JSONFormatter.format(lastAPSResult.json)); + requestView.setText(lastAPSResult.toSpanned()); + } + DetermineBasalAdapterSMBJS determineBasalAdapterSMBJS = plugin.lastDetermineBasalAdapterSMBJS; + if (determineBasalAdapterSMBJS != null) { + glucoseStatusView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getGlucoseStatusParam())); + currentTempView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getCurrentTempParam())); + try { + JSONArray iobArray = new JSONArray(determineBasalAdapterSMBJS.getIobDataParam()); + iobDataView.setText(String.format(MainApp.sResources.getString(R.string.array_of_elements), iobArray.length()) + "\n" + JSONFormatter.format(iobArray.getString(0))); + } catch (JSONException e) { + e.printStackTrace(); + iobDataView.setText("JSONException"); + } + profileView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getProfileParam())); + mealDataView.setText(JSONFormatter.format(determineBasalAdapterSMBJS.getMealDataParam())); + scriptdebugView.setText(determineBasalAdapterSMBJS.getScriptDebug()); + } + if (plugin.lastAPSRun != null) { + lastRunView.setText(plugin.lastAPSRun.toLocaleString()); + } + if (plugin.lastAutosensResult != null) { + autosensDataView.setText(JSONFormatter.format(plugin.lastAutosensResult.json())); + } + } + }); + } + + void updateResultGUI(final String text) { + Activity activity = getActivity(); + if (activity != null) + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + resultView.setText(text); + glucoseStatusView.setText(""); + currentTempView.setText(""); + iobDataView.setText(""); + profileView.setText(""); + mealDataView.setText(""); + autosensDataView.setText(""); + scriptdebugView.setText(""); + requestView.setText(""); + lastRunView.setText(""); + } + }); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java new file mode 100644 index 0000000000..f056a9c607 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/OpenAPSSMBPlugin.java @@ -0,0 +1,301 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +import org.json.JSONException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Date; + +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.GlucoseStatus; +import info.nightscout.androidaps.data.IobTotal; +import info.nightscout.androidaps.data.MealData; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.db.TempTarget; +import info.nightscout.androidaps.interfaces.APSInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.plugins.Loop.APSResult; +import info.nightscout.androidaps.plugins.Loop.ScriptReader; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateGui; +import info.nightscout.androidaps.plugins.OpenAPSMA.events.EventOpenAPSUpdateResultGui; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.Profiler; +import info.nightscout.utils.Round; +import info.nightscout.utils.SP; +import info.nightscout.utils.ToastUtils; + +/** + * Created by mike on 05.08.2016. + */ +public class OpenAPSSMBPlugin implements PluginBase, APSInterface { + private static Logger log = LoggerFactory.getLogger(OpenAPSSMBPlugin.class); + + // last values + DetermineBasalAdapterSMBJS lastDetermineBasalAdapterSMBJS = null; + Date lastAPSRun = null; + DetermineBasalResultSMB lastAPSResult = null; + AutosensResult lastAutosensResult = null; + + boolean fragmentEnabled = false; + boolean fragmentVisible = true; + + private static OpenAPSSMBPlugin openAPSSMBPlugin; + + private OpenAPSSMBPlugin() { + } + + public static OpenAPSSMBPlugin getPlugin() { + if (openAPSSMBPlugin == null) { + openAPSSMBPlugin = new OpenAPSSMBPlugin(); + } + return openAPSSMBPlugin; + } + + @Override + public String getName() { + return MainApp.instance().getString(R.string.openapssmb); + } + + @Override + public String getNameShort() { + String name = MainApp.sResources.getString(R.string.smb_shortname); + if (!name.trim().isEmpty()) { + //only if translation exists + return name; + } + // use long name as fallback + return getName(); + } + + @Override + public boolean isEnabled(int type) { + boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; + return type == APS && fragmentEnabled && pumpCapable; + } + + @Override + public boolean isVisibleInTabs(int type) { + boolean pumpCapable = ConfigBuilderPlugin.getActivePump() == null || ConfigBuilderPlugin.getActivePump().getPumpDescription().isTempBasalCapable; + return type == APS && fragmentVisible && pumpCapable; + } + + @Override + public boolean canBeHidden(int type) { + return true; + } + + @Override + public boolean hasFragment() { + return true; + } + + @Override + public boolean showInList(int type) { + return true; + } + + @Override + public void setFragmentVisible(int type, boolean fragmentVisible) { + if (type == APS) this.fragmentVisible = fragmentVisible; + } + + @Override + public int getPreferencesId() { + return R.xml.pref_openapssmb; + } + + @Override + public void setFragmentEnabled(int type, boolean fragmentEnabled) { + if (type == APS) this.fragmentEnabled = fragmentEnabled; + } + + @Override + public int getType() { + return PluginBase.APS; + } + + @Override + public String getFragmentClass() { + return OpenAPSSMBFragment.class.getName(); + } + + @Override + public APSResult getLastAPSResult() { + return lastAPSResult; + } + + @Override + public Date getLastAPSRun() { + return lastAPSRun; + } + + @Override + public void invoke(String initiator) { + log.debug("invoke from " + initiator); + lastAPSResult = null; + DetermineBasalAdapterSMBJS determineBasalAdapterSMBJS = null; + try { + determineBasalAdapterSMBJS = new DetermineBasalAdapterSMBJS(new ScriptReader(MainApp.instance().getBaseContext())); + } catch (IOException e) { + log.error(e.getMessage(), e); + return; + } + + GlucoseStatus glucoseStatus = GlucoseStatus.getGlucoseStatusData(); + Profile profile = MainApp.getConfigBuilder().getProfile(); + PumpInterface pump = ConfigBuilderPlugin.getActivePump(); + + if (profile == null) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.noprofileselected))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.noprofileselected)); + return; + } + + if (!isEnabled(PluginBase.APS)) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_disabled))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.openapsma_disabled)); + return; + } + + if (glucoseStatus == null) { + MainApp.bus().post(new EventOpenAPSUpdateResultGui(MainApp.instance().getString(R.string.openapsma_noglucosedata))); + if (Config.logAPSResult) + log.debug(MainApp.instance().getString(R.string.openapsma_noglucosedata)); + return; + } + + String units = profile.getUnits(); + + double maxIob = SP.getDouble("openapsma_max_iob", 1.5d); + double maxBasal = SP.getDouble("openapsma_max_basal", 1d); + double minBg = Profile.toMgdl(profile.getTargetLow(), units); + double maxBg = Profile.toMgdl(profile.getTargetHigh(), units); + double targetBg = (minBg + maxBg) / 2; + + minBg = Round.roundTo(minBg, 0.1d); + maxBg = Round.roundTo(maxBg, 0.1d); + + Date start = new Date(); + Date startPart = new Date(); + IobTotal[] iobArray = IobCobCalculatorPlugin.calculateIobArrayForSMB(); + Profiler.log(log, "calculateIobArrayInDia()", startPart); + + startPart = new Date(); + MealData mealData = MainApp.getConfigBuilder().getMealData(); + Profiler.log(log, "getMealData()", startPart); + + maxIob = MainApp.getConfigBuilder().applyMaxIOBConstraints(maxIob); + + minBg = verifyHardLimits(minBg, "minBg", Constants.VERY_HARD_LIMIT_MIN_BG[0], Constants.VERY_HARD_LIMIT_MIN_BG[1]); + maxBg = verifyHardLimits(maxBg, "maxBg", Constants.VERY_HARD_LIMIT_MAX_BG[0], Constants.VERY_HARD_LIMIT_MAX_BG[1]); + targetBg = verifyHardLimits(targetBg, "targetBg", Constants.VERY_HARD_LIMIT_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TARGET_BG[1]); + + boolean isTempTarget = false; + TempTarget tempTarget = MainApp.getConfigBuilder().getTempTargetFromHistory(System.currentTimeMillis()); + if (tempTarget != null) { + isTempTarget = true; + minBg = verifyHardLimits(tempTarget.low, "minBg", Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MIN_BG[1]); + maxBg = verifyHardLimits(tempTarget.high, "maxBg", Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[0], Constants.VERY_HARD_LIMIT_TEMP_MAX_BG[1]); + targetBg = verifyHardLimits((tempTarget.low + tempTarget.high) / 2, "targetBg", Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[0], Constants.VERY_HARD_LIMIT_TEMP_TARGET_BG[1]); + } + + + maxIob = verifyHardLimits(maxIob, "maxIob", 0, 7); + maxBasal = verifyHardLimits(maxBasal, "max_basal", 0.1, 10); + + if (!checkOnlyHardLimits(profile.getDia(), "dia", 2, 7)) return; + if (!checkOnlyHardLimits(profile.getIc(profile.secondsFromMidnight()), "carbratio", 2, 100)) + return; + if (!checkOnlyHardLimits(Profile.toMgdl(profile.getIsf().doubleValue(), units), "sens", 2, 900)) + return; + if (!checkOnlyHardLimits(profile.getMaxDailyBasal(), "max_daily_basal", 0.1, 10)) return; + if (!checkOnlyHardLimits(pump.getBaseBasalRate(), "current_basal", 0.01, 5)) return; + + startPart = new Date(); + if (MainApp.getConfigBuilder().isAMAModeEnabled()) { + lastAutosensResult = IobCobCalculatorPlugin.detectSensitivityWithLock(IobCobCalculatorPlugin.oldestDataAvailable(), System.currentTimeMillis()); + } else { + lastAutosensResult = new AutosensResult(); + } + Profiler.log(log, "detectSensitivityandCarbAbsorption()", startPart); + Profiler.log(log, "SMB data gathering", start); + + start = new Date(); + try { + determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, ConfigBuilderPlugin.getActivePump().getBaseBasalRate(), iobArray, glucoseStatus, mealData, + lastAutosensResult.ratio, //autosensDataRatio + isTempTarget, + true //microBolusAllowed + ); + } catch (JSONException e) { + log.error(e.getMessage()); + return; + } + + + DetermineBasalResultSMB determineBasalResultSMB = determineBasalAdapterSMBJS.invoke(); + Profiler.log(log, "SMB calculation", start); + // TODO still needed with oref1? + // Fix bug determine basal + if (determineBasalResultSMB.rate == 0d && determineBasalResultSMB.duration == 0 && !MainApp.getConfigBuilder().isTempBasalInProgress()) + determineBasalResultSMB.tempBasalReqested = false; + // limit requests on openloop mode + if (!MainApp.getConfigBuilder().isClosedModeEnabled()) { + if (MainApp.getConfigBuilder().isTempBasalInProgress() && determineBasalResultSMB.rate == 0 && determineBasalResultSMB.duration == 0) { + // going to cancel + } else if (MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultSMB.rate - MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory()) < 0.1) { + determineBasalResultSMB.tempBasalReqested = false; + } else if (!MainApp.getConfigBuilder().isTempBasalInProgress() && Math.abs(determineBasalResultSMB.rate - pump.getBaseBasalRate()) < 0.1) { + determineBasalResultSMB.tempBasalReqested = false; + } + } + + determineBasalResultSMB.iob = iobArray[0]; + Date now = new Date(); + + try { + determineBasalResultSMB.json.put("timestamp", DateUtil.toISOString(now)); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + + lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS; + lastAPSResult = determineBasalResultSMB; + lastAPSRun = now; + MainApp.bus().post(new EventOpenAPSUpdateGui()); + + //deviceStatus.suggested = determineBasalResultAMA.json; + } + + // safety checks + private static boolean checkOnlyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + return value.equals(verifyHardLimits(value, valueName, lowLimit, highLimit)); + } + + private static Double verifyHardLimits(Double value, String valueName, double lowLimit, double highLimit) { + Double newvalue = value; + if (newvalue < lowLimit || newvalue > highLimit) { + newvalue = Math.max(newvalue, lowLimit); + newvalue = Math.min(newvalue, highLimit); + String msg = String.format(MainApp.sResources.getString(R.string.openapsma_valueoutofrange), valueName); + msg += ".\n"; + msg += String.format(MainApp.sResources.getString(R.string.openapsma_valuelimitedto), value, newvalue); + log.error(msg); + NSUpload.uploadError(msg); + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), msg, R.raw.error); + } + return newvalue; + } + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java new file mode 100644 index 0000000000..a7e690ce79 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/OpenAPSSMB/SMBDefaults.java @@ -0,0 +1,64 @@ +package info.nightscout.androidaps.plugins.OpenAPSSMB; + +/** + * Created by mike on 10.12.2017. + */ + +public class SMBDefaults { + // CALCULATED OR FROM PREFS + + // max_iob: 0 // if max_iob is not provided, will default to zero + // max_daily_safety_multiplier:3 + // current_basal_safety_multiplier:4 + // autosens_max:1.2 + // autosens_min:0.7 + + // USED IN AUTOSENS + public final static boolean rewind_resets_autosens = true; // reset autosensitivity to neutral for awhile after each pump rewind + + // USED IN TARGETS + // by default the higher end of the target range is used only for avoiding bolus wizard overcorrections + // use wide_bg_target_range: true to force neutral temps over a wider range of eventualBGs + public final static boolean wide_bg_target_range = false; // by default use only the low end of the pump's BG target range as OpenAPS target + + // USED IN AUTOTUNE + public final static double autotune_isf_adjustmentFraction = 1.0; // keep autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF. + public final static double remainingCarbsFraction = 1.0; // fraction of carbs we'll assume will absorb over 4h if we don't yet see carb absorption + + // USED IN DETERMINE_BASAL + public final static boolean low_temptarget_lowers_sensitivity = false; // lower sensitivity for temptargets <= 99. + public final static boolean high_temptarget_raises_sensitivity = false; // raise sensitivity for temptargets >= 111. synonym for exercise_mode + public final static boolean sensitivity_raises_target = true; // raise BG target when autosens detects sensitivity + public final static boolean resistance_lowers_target = false; // lower BG target when autosens detects resistance + public final static boolean adv_target_adjustments = false; // lower target automatically when BG and eventualBG are high + public final static boolean exercise_mode = false; // when true, > 105 mg/dL high temp target adjusts sensitivityRatio for exercise_mode. This majorly changes the behavior of high temp targets from before. synonmym for high_temptarget_raises_sensitivity + public final static int half_basal_exercise_target = 160; // when temptarget is 160 mg/dL *and* exercise_mode=true, run 50% basal at this level (120 = 75%; 140 = 60%) + // create maxCOB and default it to 120 because that's the most a typical body can absorb over 4 hours. + // (If someone enters more carbs or stacks more; OpenAPS will just truncate dosing based on 120. + // Essentially, this just limits AMA/SMB as a safety cap against excessive COB entry) + public final static int maxCOB = 120; + public final static boolean skip_neutral_temps = true; // ***** default false in oref1 ***** if true, don't set neutral temps + // unsuspend_if_no_temp:false // if true, pump will un-suspend after a zero temp finishes + // bolussnooze_dia_divisor:2 // bolus snooze decays after 1/2 of DIA + public final static int min_5m_carbimpact = 8; // mg/dL per 5m (8 mg/dL/5m corresponds to 24g/hr at a CSF of 4 mg/dL/g (x/5*60/4)) + public final static int remainingCarbsCap = 90; // max carbs we'll assume will absorb over 4h if we don't yet see carb absorption + // WARNING: use SMB with caution: it can and will automatically bolus up to max_iob worth of extra insulin + // enableUAM:true // enable detection of unannounced meal carb absorption + public final static boolean A52_risk_enable = false; + //public final static boolean enableSMB_with_COB = true; // ***** default false in oref1 ***** enable supermicrobolus while COB is positive + //public final static boolean enableSMB_with_temptarget = true; // ***** default false in oref1 ***** enable supermicrobolus for eating soon temp targets + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + // xDrip+, LimiTTer, etc. do not properly filter out high-noise SGVs + // Using SMB overnight with such data sources risks causing a dangerous overdose of insulin + // if the CGM sensor reads falsely high and doesn't come down as actual BG does + // public final static boolean enableSMB_always = false; // always enable supermicrobolus (unless disabled by high temptarget) + // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar + //public final static boolean enableSMB_after_carbs = false; // enable supermicrobolus for 6h after carbs, even with 0 COB + //public final static boolean allowSMB_with_high_temptarget = false; // allow supermicrobolus (if otherwise enabled) even with high temp targets + public final static int maxSMBBasalMinutes = 30; // maximum minutes of basal that can be delivered as a single SMB with uncovered COB + // curve:"rapid-acting" // Supported curves: "bilinear", "rapid-acting" (Novolog, Novorapid, Humalog, Apidra) and "ultra-rapid" (Fiasp) + // useCustomPeakTime:false // allows changing insulinPeakTime + // insulinPeakTime:75 // number of minutes after a bolus activity peaks. defaults to 55m for Fiasp if useCustomPeakTime: false + public final static int carbsReqThreshold = 1; // grams of carbsReq to trigger a pushover + // offline_hotspot:false // enabled an offline-only local wifi hotspot if no Internet available +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java index 775c97d810..69fbfdc149 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/NewTreatmentDialog.java @@ -159,19 +159,23 @@ public class NewTreatmentDialog extends DialogFragment implements OnClickListene detailedBolusInfo.carbs = finalCarbsAfterConstraints; detailedBolusInfo.context = context; detailedBolusInfo.source = Source.USER; - ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); + if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { + ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } } - } - }); + }); + } else { + MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + } Answers.getInstance().logCustom(new CustomEvent("Bolus")); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java index fd720e9499..d74c9caadf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/Dialogs/WizardDialog.java @@ -365,19 +365,23 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com detailedBolusInfo.carbTime = carbTime; detailedBolusInfo.boluscalc = boluscalcJSON; detailedBolusInfo.source = Source.USER; - ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { - @Override - public void run() { - if (!result.success) { - Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); - i.putExtra("soundid", R.raw.boluserror); - i.putExtra("status", result.comment); - i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - MainApp.instance().startActivity(i); + if (detailedBolusInfo.insulin > 0 || ConfigBuilderPlugin.getActivePump().getPumpDescription().storesCarbInfo) { + ConfigBuilderPlugin.getCommandQueue().bolus(detailedBolusInfo, new Callback() { + @Override + public void run() { + if (!result.success) { + Intent i = new Intent(MainApp.instance(), ErrorHelperActivity.class); + i.putExtra("soundid", R.raw.boluserror); + i.putExtra("status", result.comment); + i.putExtra("title", MainApp.sResources.getString(R.string.treatmentdeliveryerror)); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + MainApp.instance().startActivity(i); + } } - } - }); + }); + } else { + MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + } Answers.getInstance().logCustom(new CustomEvent("Wizard")); } } @@ -528,12 +532,12 @@ public class WizardDialog extends DialogFragment implements OnClickListener, Com if (calculatedTotalInsulin > 0d || calculatedCarbs > 0d) { String insulinText = calculatedTotalInsulin > 0d ? (DecimalFormatter.to2Decimal(calculatedTotalInsulin) + "U") : ""; String carbsText = calculatedCarbs > 0d ? (DecimalFormatter.to0Decimal(calculatedCarbs) + "g") : ""; - total.setText(getString(R.string.result) + ": " + insulinText + " " + carbsText); + total.setText(MainApp.gs(R.string.result) + ": " + insulinText + " " + carbsText); okButton.setVisibility(View.VISIBLE); } else { // TODO this should also be run when loading the dialog as the OK button is initially visible // but does nothing if neither carbs nor insulin is > 0 - total.setText(getString(R.string.missing) + " " + DecimalFormatter.to0Decimal(wizard.carbsEquivalent) + "g"); + total.setText(MainApp.gs(R.string.missing) + " " + DecimalFormatter.to0Decimal(wizard.carbsEquivalent) + "g"); okButton.setVisibility(View.INVISIBLE); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java index a8ad9c7074..13b81fe8be 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/OverviewFragment.java @@ -93,8 +93,6 @@ import info.nightscout.androidaps.plugins.Loop.LoopPlugin; import info.nightscout.androidaps.plugins.Loop.events.EventNewOpenLoopNotification; import info.nightscout.androidaps.plugins.NSClientInternal.broadcasts.BroadcastAckAlarm; import info.nightscout.androidaps.plugins.NSClientInternal.data.NSDeviceStatus; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; -import info.nightscout.androidaps.plugins.OpenAPSAMA.OpenAPSAMAPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.CalibrationDialog; import info.nightscout.androidaps.plugins.Overview.Dialogs.ErrorHelperActivity; import info.nightscout.androidaps.plugins.Overview.Dialogs.NewTreatmentDialog; @@ -148,12 +146,18 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, TextView sage; TextView pbage; - CheckBox showPredictionView; - CheckBox showBasalsView; - CheckBox showIobView; - CheckBox showCobView; - CheckBox showDeviationsView; - CheckBox showRatiosView; + TextView showPredictionLabel; + CheckBox showPredictionCheckbox; + TextView showBasalsLabel; + CheckBox showBasalsCheckbox; + TextView showIobLabel; + CheckBox showIobCheckbox; + TextView showCobLabel; + CheckBox showCobCheckbox; + TextView showDeviationsLabel; + CheckBox showDeviationsCheckbox; + TextView showRatiosLabel; + CheckBox showRatiosCheckbox; RecyclerView notificationsView; LinearLayoutManager llm; @@ -265,24 +269,37 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, acceptTempLayout = (LinearLayout) view.findViewById(R.id.overview_accepttemplayout); - showPredictionView = (CheckBox) view.findViewById(R.id.overview_showprediction); - showBasalsView = (CheckBox) view.findViewById(R.id.overview_showbasals); - showIobView = (CheckBox) view.findViewById(R.id.overview_showiob); - showCobView = (CheckBox) view.findViewById(R.id.overview_showcob); - showDeviationsView = (CheckBox) view.findViewById(R.id.overview_showdeviations); - showRatiosView = (CheckBox) view.findViewById(R.id.overview_showratios); - showPredictionView.setChecked(SP.getBoolean("showprediction", false)); - showBasalsView.setChecked(SP.getBoolean("showbasals", true)); - showIobView.setChecked(SP.getBoolean("showiob", false)); - showCobView.setChecked(SP.getBoolean("showcob", false)); - showDeviationsView.setChecked(SP.getBoolean("showdeviations", false)); - showRatiosView.setChecked(SP.getBoolean("showratios", false)); - showPredictionView.setOnCheckedChangeListener(this); - showBasalsView.setOnCheckedChangeListener(this); - showIobView.setOnCheckedChangeListener(this); - showCobView.setOnCheckedChangeListener(this); - showDeviationsView.setOnCheckedChangeListener(this); - showRatiosView.setOnCheckedChangeListener(this); + showPredictionCheckbox = (CheckBox) view.findViewById(R.id.overview_showprediction); + showBasalsCheckbox = (CheckBox) view.findViewById(R.id.overview_showbasals); + showIobCheckbox = (CheckBox) view.findViewById(R.id.overview_showiob); + showCobCheckbox = (CheckBox) view.findViewById(R.id.overview_showcob); + showDeviationsCheckbox = (CheckBox) view.findViewById(R.id.overview_showdeviations); + showRatiosCheckbox = (CheckBox) view.findViewById(R.id.overview_showratios); + showPredictionCheckbox.setChecked(SP.getBoolean("showprediction", false)); + showBasalsCheckbox.setChecked(SP.getBoolean("showbasals", true)); + showIobCheckbox.setChecked(SP.getBoolean("showiob", false)); + showCobCheckbox.setChecked(SP.getBoolean("showcob", false)); + showDeviationsCheckbox.setChecked(SP.getBoolean("showdeviations", false)); + showRatiosCheckbox.setChecked(SP.getBoolean("showratios", false)); + showPredictionCheckbox.setOnCheckedChangeListener(this); + showBasalsCheckbox.setOnCheckedChangeListener(this); + showIobCheckbox.setOnCheckedChangeListener(this); + showCobCheckbox.setOnCheckedChangeListener(this); + showDeviationsCheckbox.setOnCheckedChangeListener(this); + showRatiosCheckbox.setOnCheckedChangeListener(this); + + showPredictionLabel = (TextView) view.findViewById(R.id.overview_showprediction_label); + showPredictionLabel.setOnClickListener(this); + showBasalsLabel = (TextView) view.findViewById(R.id.overview_showbasals_label); + showBasalsLabel.setOnClickListener(this); + showIobLabel = (TextView) view.findViewById(R.id.overview_showiob_label); + showIobLabel.setOnClickListener(this); + showCobLabel = (TextView) view.findViewById(R.id.overview_showcob_label); + showCobLabel.setOnClickListener(this); + showDeviationsLabel = (TextView) view.findViewById(R.id.overview_showdeviations_label); + showDeviationsLabel.setOnClickListener(this); + showRatiosLabel = (TextView) view.findViewById(R.id.overview_showratios_label); + showRatiosLabel.setOnClickListener(this); notificationsView = (RecyclerView) view.findViewById(R.id.overview_notifications); notificationsView.setHasFixedSize(true); @@ -375,24 +392,24 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, case R.id.overview_showiob: break; case R.id.overview_showcob: - showDeviationsView.setOnCheckedChangeListener(null); - showDeviationsView.setChecked(false); - showDeviationsView.setOnCheckedChangeListener(this); + showDeviationsCheckbox.setOnCheckedChangeListener(null); + showDeviationsCheckbox.setChecked(false); + showDeviationsCheckbox.setOnCheckedChangeListener(this); break; case R.id.overview_showdeviations: - showCobView.setOnCheckedChangeListener(null); - showCobView.setChecked(false); - showCobView.setOnCheckedChangeListener(this); + showCobCheckbox.setOnCheckedChangeListener(null); + showCobCheckbox.setChecked(false); + showCobCheckbox.setOnCheckedChangeListener(this); break; case R.id.overview_showratios: break; } - SP.putBoolean("showiob", showIobView.isChecked()); - SP.putBoolean("showprediction", showPredictionView.isChecked()); - SP.putBoolean("showbasals", showBasalsView.isChecked()); - SP.putBoolean("showcob", showCobView.isChecked()); - SP.putBoolean("showdeviations", showDeviationsView.isChecked()); - SP.putBoolean("showratios", showRatiosView.isChecked()); + SP.putBoolean("showiob", showIobCheckbox.isChecked()); + SP.putBoolean("showprediction", showPredictionCheckbox.isChecked()); + SP.putBoolean("showbasals", showBasalsCheckbox.isChecked()); + SP.putBoolean("showcob", showCobCheckbox.isChecked()); + SP.putBoolean("showdeviations", showDeviationsCheckbox.isChecked()); + SP.putBoolean("showratios", showRatiosCheckbox.isChecked()); scheduleUpdateGUI("onGraphCheckboxesCheckedChanged"); } @@ -619,6 +636,24 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, if (ConfigBuilderPlugin.getActivePump().isSuspended() || !ConfigBuilderPlugin.getActivePump().isInitialized()) ConfigBuilderPlugin.getCommandQueue().readStatus("RefreshClicked", null); break; + case R.id.overview_showprediction_label: + showPredictionCheckbox.toggle(); + break; + case R.id.overview_showbasals_label: + showBasalsCheckbox.toggle(); + break; + case R.id.overview_showiob_label: + showIobCheckbox.toggle(); + break; + case R.id.overview_showcob_label: + showCobCheckbox.toggle(); + break; + case R.id.overview_showdeviations_label: + showDeviationsCheckbox.toggle(); + break; + case R.id.overview_showratios_label: + showRatiosCheckbox.toggle(); + break; } } @@ -638,7 +673,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, if (ConfigBuilderPlugin.getActiveLoop() != null) { ConfigBuilderPlugin.getActiveLoop().invoke("Accept temp button", false); final LoopPlugin.LastRun finalLastRun = LoopPlugin.lastRun; - if (finalLastRun != null && finalLastRun.lastAPSRun != null && finalLastRun.constraintsProcessed.changeRequested) { + if (finalLastRun != null && finalLastRun.lastAPSRun != null && finalLastRun.constraintsProcessed.isChangeRequested()) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setTitle(getContext().getString(R.string.confirmation)); builder.setMessage(getContext().getString(R.string.setbasalquestion) + "\n" + finalLastRun.constraintsProcessed); @@ -1069,7 +1104,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, boolean showAcceptButton = !MainApp.getConfigBuilder().isClosedModeEnabled(); // Open mode needed showAcceptButton = showAcceptButton && finalLastRun != null && finalLastRun.lastAPSRun != null; // aps result must exist showAcceptButton = showAcceptButton && (finalLastRun.lastOpenModeAccept == null || finalLastRun.lastOpenModeAccept.getTime() < finalLastRun.lastAPSRun.getTime()); // never accepted or before last result - showAcceptButton = showAcceptButton && finalLastRun.constraintsProcessed.changeRequested; // change is requested + showAcceptButton = showAcceptButton && finalLastRun.constraintsProcessed.isChangeRequested(); // change is requested if (showAcceptButton && pump.isInitialized() && !pump.isSuspended() && ConfigBuilderPlugin.getActiveLoop() != null) { acceptTempLayout.setVisibility(View.VISIBLE); @@ -1247,13 +1282,13 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, cobView.setText(cobText); } - final boolean showPrediction = showPredictionView.isChecked() && finalLastRun != null && finalLastRun.constraintsProcessed.getClass().equals(DetermineBasalResultAMA.class); - if (MainApp.getSpecificPlugin(OpenAPSAMAPlugin.class) != null && MainApp.getSpecificPlugin(OpenAPSAMAPlugin.class).isEnabled(PluginBase.APS)) { - showPredictionView.setVisibility(View.VISIBLE); - getActivity().findViewById(R.id.overview_showprediction_label).setVisibility(View.VISIBLE); + final boolean predictionsAvailable = finalLastRun != null && finalLastRun.request.hasPredictions; + if (predictionsAvailable) { + showPredictionCheckbox.setVisibility(View.VISIBLE); + showPredictionLabel.setVisibility(View.VISIBLE); } else { - showPredictionView.setVisibility(View.GONE); - getActivity().findViewById(R.id.overview_showprediction_label).setVisibility(View.GONE); + showPredictionCheckbox.setVisibility(View.GONE); + showPredictionLabel.setVisibility(View.GONE); } // pump status from ns @@ -1306,8 +1341,8 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, final long toTime; final long fromTime; final long endTime; - if (showPrediction) { - int predHours = (int) (Math.ceil(((DetermineBasalResultAMA) finalLastRun.constraintsProcessed).getLatestPredictionsTime() - System.currentTimeMillis()) / (60 * 60 * 1000)); + if (predictionsAvailable && showPredictionCheckbox.isChecked()) { + int predHours = (int) (Math.ceil(finalLastRun.constraintsProcessed.getLatestPredictionsTime() - System.currentTimeMillis()) / (60 * 60 * 1000)); predHours = Math.min(2, predHours); predHours = Math.max(0, predHours); hoursToFetch = rangeToDisplay - predHours; @@ -1333,8 +1368,8 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, graphData.addInRangeArea(fromTime, endTime, lowLine, highLine); // **** BG **** - if (showPrediction) - graphData.addBgReadings(fromTime, toTime, lowLine, highLine, (DetermineBasalResultAMA) finalLastRun.constraintsProcessed); + if (predictionsAvailable && showPredictionCheckbox.isChecked()) + graphData.addBgReadings(fromTime, toTime, lowLine, highLine, finalLastRun.constraintsProcessed); else graphData.addBgReadings(fromTime, toTime, lowLine, highLine, null); @@ -1345,7 +1380,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, graphData.addTreatments(fromTime, endTime); // add basal data - if (pump.getPumpDescription().isTempBasalCapable && showBasalsView.isChecked()) { + if (pump.getPumpDescription().isTempBasalCapable && showBasalsCheckbox.isChecked()) { graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d); } @@ -1361,25 +1396,30 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, boolean useCobForScale = false; boolean useDevForScale = false; boolean useRatioForScale = false; + boolean useDSForScale = false; - if (showIobView.isChecked()) { + if (showIobCheckbox.isChecked()) { useIobForScale = true; - } else if (showCobView.isChecked()) { + } else if (showCobCheckbox.isChecked()) { useCobForScale = true; - } else if (showDeviationsView.isChecked()) { + } else if (showDeviationsCheckbox.isChecked()) { useDevForScale = true; - } else if (showRatiosView.isChecked()) { + } else if (showRatiosCheckbox.isChecked()) { useRatioForScale = true; + } else if (Config.displayDeviationSlope) { + useDSForScale = true; } - if (showIobView.isChecked()) + if (showIobCheckbox.isChecked()) secondGraphData.addIob(fromTime, now, useIobForScale, 1d); - if (showCobView.isChecked()) + if (showCobCheckbox.isChecked()) secondGraphData.addCob(fromTime, now, useCobForScale, useCobForScale ? 1d : 0.5d); - if (showDeviationsView.isChecked()) + if (showDeviationsCheckbox.isChecked()) secondGraphData.addDeviations(fromTime, now, useDevForScale, 1d); - if (showRatiosView.isChecked()) + if (showRatiosCheckbox.isChecked()) secondGraphData.addRatio(fromTime, now, useRatioForScale, 1d); + if (Config.displayDeviationSlope) + secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1d); // **** NOW line **** // set manual x bounds to have nice steps @@ -1392,7 +1432,7 @@ public class OverviewFragment extends Fragment implements View.OnClickListener, activity.runOnUiThread(new Runnable() { @Override public void run() { - if (showIobView.isChecked() || showCobView.isChecked() || showDeviationsView.isChecked() || showRatiosView.isChecked()) { + if (showIobCheckbox.isChecked() || showCobCheckbox.isChecked() || showDeviationsCheckbox.isChecked() || showRatiosCheckbox.isChecked() || Config.displayDeviationSlope) { iobGraph.setVisibility(View.VISIBLE); } else { iobGraph.setVisibility(View.GONE); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java index e76714c5b2..f6cd2eecdc 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphData/GraphData.java @@ -12,7 +12,6 @@ import com.jjoe64.graphview.series.LineGraphSeries; import com.jjoe64.graphview.series.Series; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import info.nightscout.androidaps.Constants; @@ -28,7 +27,7 @@ import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.AutosensData; import info.nightscout.androidaps.plugins.IobCobCalculator.IobCobCalculatorPlugin; import info.nightscout.androidaps.plugins.IobCobCalculator.BasalData; -import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA; +import info.nightscout.androidaps.plugins.Loop.APSResult; import info.nightscout.androidaps.plugins.Overview.graphExtensions.AreaGraphSeries; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DataPointWithLabelInterface; import info.nightscout.androidaps.plugins.Overview.graphExtensions.DoubleDataPoint; @@ -56,7 +55,7 @@ public class GraphData { this.graph = graph; } - public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, DetermineBasalResultAMA amaResult) { + public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, APSResult apsResult) { double maxBgValue = 0d; bgReadingsArray = MainApp.getDbHelper().getBgreadingsDataFromTime(fromTime, true); List bgListArray = new ArrayList<>(); @@ -65,14 +64,12 @@ public class GraphData { return; } - Iterator it = bgReadingsArray.iterator(); - while (it.hasNext()) { - BgReading bg = it.next(); + for (BgReading bg : bgReadingsArray) { if (bg.value > maxBgValue) maxBgValue = bg.value; bgListArray.add(bg); } - if (amaResult != null) { - List predArray = amaResult.getPredictions(); + if (apsResult != null) { + List predArray = apsResult.getPredictions(); bgListArray.addAll(predArray); } @@ -125,7 +122,7 @@ public class GraphData { List basalLineArray = new ArrayList<>(); List absoluteBasalLineArray = new ArrayList<>(); double lastLineBasal = 0; - double lastAbsoluteLineBasal = 0; + double lastAbsoluteLineBasal = -1; double lastBaseBasal = 0; double lastTempBasal = 0; for (long time = fromTime; time < toTime; time += 60 * 1000L) { @@ -253,7 +250,7 @@ public class GraphData { } // Careportal - List careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime, true); + List careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true); for (int tx = 0; tx < careportalEvents.size(); tx++) { DataPointWithLabelInterface t = careportalEvents.get(tx); @@ -267,7 +264,7 @@ public class GraphData { addSeries(new PointsWithLabelGraphSeries<>(treatmentsArray)); } - double getNearestBg(long date) { + private double getNearestBg(long date) { double bg = 0; for (int r = bgReadingsArray.size() - 1; r >= 0; r--) { BgReading reading = bgReadingsArray.get(r); @@ -401,21 +398,21 @@ public class GraphData { // scale in % of vertical size (like 0.3) public void addRatio(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries ratioSeries; - List ratioArray = new ArrayList<>(); + LineGraphSeries ratioSeries; + List ratioArray = new ArrayList<>(); Double maxRatioValueFound = 0d; Scale ratioScale = new Scale(-1d); for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); if (autosensData != null) { - ratioArray.add(new DataPoint(time, autosensData.autosensRatio)); + ratioArray.add(new ScaledDataPoint(time, autosensData.autosensRatio, ratioScale)); maxRatioValueFound = Math.max(maxRatioValueFound, Math.abs(autosensData.autosensRatio)); } } // RATIOS - DataPoint[] ratioData = new DataPoint[ratioArray.size()]; + ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()]; ratioData = ratioArray.toArray(ratioData); ratioSeries = new LineGraphSeries<>(ratioData); ratioSeries.setColor(MainApp.sResources.getColor(R.color.ratio)); @@ -429,6 +426,50 @@ public class GraphData { addSeries(ratioSeries); } + // scale in % of vertical size (like 0.3) + public void addDeviationSlope(long fromTime, long toTime, boolean useForScale, double scale) { + LineGraphSeries dsMaxSeries; + LineGraphSeries dsMinSeries; + List dsMaxArray = new ArrayList<>(); + List dsMinArray = new ArrayList<>(); + Double maxFromMaxValueFound = 0d; + Double maxFromMinValueFound = 0d; + Scale dsMaxScale = new Scale(); + Scale dsMinScale = new Scale(); + + for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { + AutosensData autosensData = IobCobCalculatorPlugin.getAutosensData(time); + if (autosensData != null) { + dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)); + dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)); + maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation)); + maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation)); + } + } + + // Slopes + ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()]; + ratioMaxData = dsMaxArray.toArray(ratioMaxData); + dsMaxSeries = new LineGraphSeries<>(ratioMaxData); + dsMaxSeries.setColor(Color.MAGENTA); + dsMaxSeries.setThickness(3); + + ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()]; + ratioMinData = dsMinArray.toArray(ratioMinData); + dsMinSeries = new LineGraphSeries<>(ratioMinData); + dsMinSeries.setColor(Color.YELLOW); + dsMinSeries.setThickness(3); + + if (useForScale) + maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound); + + dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound); + dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound); + + addSeries(dsMaxSeries); + addSeries(dsMinSeries); + } + // scale in % of vertical size (like 0.3) public void addNowLine(long now) { LineGraphSeries seriesNow; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java index 20d478692a..3f6280431c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/DataPointWithLabelInterface.java @@ -55,4 +55,5 @@ public interface DataPointWithLabelInterface extends DataPointInterface{ PointsWithLabelGraphSeries.Shape getShape(); float getSize(); int getColor(); + int getSecondColor(); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java index 7606a71f17..0051518bb4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Overview/graphExtensions/PointsWithLabelGraphSeries.java @@ -60,17 +60,12 @@ public class PointsWithLabelGraphSeries e * You can also render a custom drawing via {@link com.jjoe64.graphview.series.PointsGraphSeries.CustomShape} */ public enum Shape { - /** - * draws a point / circle - */ - POINT, - - /** - * draws a triangle - */ + BG, + PREDICTION, TRIANGLE, RECTANGLE, BOLUS, + SMB, EXTENDEDBOLUS, PROFILE, MBG, @@ -141,9 +136,6 @@ public class PointsWithLabelGraphSeries e Iterator values = getValues(minX, maxX); // draw background - double lastEndY = 0; - double lastEndX = 0; - // draw data double diffY = maxY - minY; @@ -154,9 +146,8 @@ public class PointsWithLabelGraphSeries e float graphLeft = graphView.getGraphContentLeft(); float graphTop = graphView.getGraphContentTop(); - lastEndY = 0; - lastEndX = 0; - float firstX = 0; + float scaleX = (float) (graphWidth / diffX); + int i=0; while (values.hasNext()) { E value = values.next(); @@ -171,9 +162,6 @@ public class PointsWithLabelGraphSeries e double ratX = valX / diffX; double x = graphWidth * ratX; - double orgX = x; - double orgY = y; - // overdraw boolean overdraw = false; if (x > graphWidth) { // end right @@ -185,6 +173,14 @@ public class PointsWithLabelGraphSeries e if (y > graphHeight) { // end top overdraw = true; } + + long duration = value.getDuration(); + float endWithDuration = (float) (x + duration * scaleX + graphLeft + 1); + // cut off to graph start if needed + if (x < 0 && endWithDuration > 0) { + x = 0; + } + /* Fix a bug that continue to show the DOT after Y axis */ if(x < 0) { overdraw = true; @@ -195,15 +191,25 @@ public class PointsWithLabelGraphSeries e registerDataPoint(endX, endY, value); float xpluslength = 0; - if (value.getDuration() > 0) { - xpluslength = endX + Math.min((float) (value.getDuration() * graphWidth / diffX), graphLeft + graphWidth); + if (duration > 0) { + xpluslength = Math.min(endWithDuration, graphLeft + graphWidth); } // draw data point if (!overdraw) { - if (value.getShape() == Shape.POINT) { + if (value.getShape() == Shape.BG) { + mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); canvas.drawCircle(endX, endY, scaledPxSize, mPaint); + } else if (value.getShape() == Shape.PREDICTION) { + mPaint.setColor(value.getColor()); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeWidth(0); + canvas.drawCircle(endX, endY, scaledPxSize, mPaint); + mPaint.setColor(value.getSecondColor()); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setStrokeWidth(0); + canvas.drawCircle(endX, endY, scaledPxSize / 3, mPaint); } else if (value.getShape() == Shape.RECTANGLE) { canvas.drawRect(endX-scaledPxSize, endY-scaledPxSize, endX+scaledPxSize, endY+scaledPxSize, mPaint); } else if (value.getShape() == Shape.TRIANGLE) { @@ -224,6 +230,14 @@ public class PointsWithLabelGraphSeries e if (value.getLabel() != null) { drawLabel45(endX, endY, value, canvas); } + } else if (value.getShape() == Shape.SMB) { + mPaint.setStrokeWidth(2); + Point[] points = new Point[3]; + points[0] = new Point((int)endX, (int)(endY-value.getSize())); + points[1] = new Point((int)(endX+value.getSize()), (int)(endY+value.getSize()*0.67)); + points[2] = new Point((int)(endX-value.getSize()), (int)(endY+value.getSize()*0.67)); + mPaint.setStyle(Paint.Style.FILL_AND_STROKE); + drawArrows(points, canvas, mPaint); } else if (value.getShape() == Shape.EXTENDEDBOLUS) { mPaint.setStrokeWidth(0); if (value.getLabel() != null) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java index 9e631def18..558d01b94d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Persistentnotification/PersistentNotificationPlugin.java @@ -7,7 +7,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.support.v4.app.TaskStackBuilder; -import android.support.v7.app.NotificationCompat; +import android.support.v4.app.NotificationCompat; import com.squareup.otto.Subscribe; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java new file mode 100644 index 0000000000..ea47303fb6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/AbstractDanaRPlugin.java @@ -0,0 +1,557 @@ +package info.nightscout.androidaps.plugins.PumpDanaR; + +import android.support.annotation.Nullable; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; + +import java.util.Date; +import java.util.Objects; + +import info.nightscout.androidaps.BuildConfig; +import info.nightscout.androidaps.Config; +import info.nightscout.androidaps.Constants; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; +import info.nightscout.androidaps.data.ProfileStore; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.interfaces.ConstraintsInterface; +import info.nightscout.androidaps.interfaces.DanaRInterface; +import info.nightscout.androidaps.interfaces.PluginBase; +import info.nightscout.androidaps.interfaces.ProfileInterface; +import info.nightscout.androidaps.interfaces.PumpDescription; +import info.nightscout.androidaps.interfaces.PumpInterface; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractDanaRExecutionService; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.DecimalFormatter; +import info.nightscout.utils.Round; + +/** + * Created by mike on 28.01.2018. + */ + +public abstract class AbstractDanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { + protected Logger log; + + protected boolean mPluginPumpEnabled = false; + protected boolean mPluginProfileEnabled = false; + protected boolean mFragmentPumpVisible = true; + + protected AbstractDanaRExecutionService sExecutionService; + + protected DanaRPump pump = DanaRPump.getInstance(); + protected boolean useExtendedBoluses = false; + + public PumpDescription pumpDescription = new PumpDescription(); + + @Override + public String getFragmentClass() { + return DanaRFragment.class.getName(); + } + + // Plugin base interface + @Override + public int getType() { + return PluginBase.PUMP; + } + + @Override + public String getNameShort() { + String name = MainApp.sResources.getString(R.string.danarpump_shortname); + if (!name.trim().isEmpty()) { + //only if translation exists + return name; + } + // use long name as fallback + return getName(); + } + + @Override + public boolean isEnabled(int type) { + if (type == PluginBase.PROFILE) return mPluginProfileEnabled && mPluginPumpEnabled; + else if (type == PluginBase.PUMP) return mPluginPumpEnabled; + else if (type == PluginBase.CONSTRAINTS) return mPluginPumpEnabled; + return false; + } + + @Override + public boolean isVisibleInTabs(int type) { + if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; + else if (type == PluginBase.PUMP) return mFragmentPumpVisible; + return false; + } + + @Override + public boolean canBeHidden(int type) { + return true; + } + + @Override + public boolean hasFragment() { + return true; + } + + @Override + public boolean showInList(int type) { + return type == PUMP; + } + + @Override + public void setFragmentEnabled(int type, boolean fragmentEnabled) { + if (type == PluginBase.PROFILE) + mPluginProfileEnabled = fragmentEnabled; + else if (type == PluginBase.PUMP) + mPluginPumpEnabled = fragmentEnabled; + // if pump profile was enabled need to switch to another too + if (type == PluginBase.PUMP && !fragmentEnabled && mPluginProfileEnabled) { + setFragmentEnabled(PluginBase.PROFILE, false); + setFragmentVisible(PluginBase.PROFILE, false); + NSProfilePlugin.getPlugin().setFragmentEnabled(PluginBase.PROFILE, true); + NSProfilePlugin.getPlugin().setFragmentVisible(PluginBase.PROFILE, true); + } + } + + @Override + public void setFragmentVisible(int type, boolean fragmentVisible) { + if (type == PluginBase.PUMP) + mFragmentPumpVisible = fragmentVisible; + } + + @Override + public boolean isSuspended() { + return pump.pumpSuspended; + } + + @Override + public boolean isBusy() { + if (sExecutionService == null) return false; + return sExecutionService.isConnected() || sExecutionService.isConnecting(); + } + + // Pump interface + @Override + public PumpEnactResult setNewBasalProfile(Profile profile) { + PumpEnactResult result = new PumpEnactResult(); + + if (sExecutionService == null) { + log.error("setNewBasalProfile sExecutionService is null"); + result.comment = "setNewBasalProfile sExecutionService is null"; + return result; + } + if (!isInitialized()) { + log.error("setNewBasalProfile not initialized"); + Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); + return result; + } else { + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + } + if (!sExecutionService.updateBasalsInPump(profile)) { + Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); + MainApp.bus().post(new EventNewNotification(notification)); + result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); + return result; + } else { + MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); + MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); + Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.sResources.getString(R.string.profile_set_ok), Notification.INFO, 60); + MainApp.bus().post(new EventNewNotification(notification)); + result.success = true; + result.enacted = true; + result.comment = "OK"; + return result; + } + } + + @Override + public boolean isThisProfileSet(Profile profile) { + if (!isInitialized()) + return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS + if (pump.pumpProfiles == null) + return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS + int basalValues = pump.basal48Enable ? 48 : 24; + int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; + for (int h = 0; h < basalValues; h++) { + Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; + Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); + if (profileValue == null) return true; + if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { + log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); + return false; + } + } + return true; + } + + @Override + public Date lastDataTime() { + return new Date(pump.lastConnection); + } + + @Override + public double getBaseBasalRate() { + return pump.currentBasal; + } + + @Override + public void stopBolusDelivering() { + if (sExecutionService == null) { + log.error("stopBolusDelivering sExecutionService is null"); + return; + } + sExecutionService.bolusStop(); + } + + @Override + public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { + PumpEnactResult result = new PumpEnactResult(); + ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); + percent = configBuilderPlugin.applyBasalConstraints(percent); + if (percent < 0) { + result.isTempCancel = false; + result.enacted = false; + result.success = false; + result.comment = MainApp.instance().getString(R.string.danar_invalidinput); + log.error("setTempBasalPercent: Invalid input"); + return result; + } + if (percent > getPumpDescription().maxTempPercent) + percent = getPumpDescription().maxTempPercent; + TemporaryBasal runningTB = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); + if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { + result.enacted = false; + result.success = true; + result.isTempCancel = false; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.duration = pump.tempBasalRemainingMin; + result.percent = pump.tempBasalPercent; + result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); + result.isPercent = true; + if (Config.logPumpActions) + log.debug("setTempBasalPercent: Correct value already set"); + return result; + } + int durationInHours = Math.max(durationInMinutes / 60, 1); + boolean connectionOK = sExecutionService.tempBasal(percent, durationInHours); + if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { + result.enacted = true; + result.success = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.isTempCancel = false; + result.duration = pump.tempBasalRemainingMin; + result.percent = pump.tempBasalPercent; + result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); + result.isPercent = true; + if (Config.logPumpActions) + log.debug("setTempBasalPercent: OK"); + return result; + } + result.enacted = false; + result.success = false; + result.comment = MainApp.instance().getString(R.string.tempbasaldeliveryerror); + log.error("setTempBasalPercent: Failed to set temp basal"); + return result; + } + + @Override + public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { + ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); + insulin = configBuilderPlugin.applyBolusConstraints(insulin); + // needs to be rounded + int durationInHalfHours = Math.max(durationInMinutes / 30, 1); + insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); + + PumpEnactResult result = new PumpEnactResult(); + ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { + result.enacted = false; + result.success = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.duration = pump.extendedBolusRemainingMinutes; + result.absolute = pump.extendedBolusAbsoluteRate; + result.isPercent = false; + result.isTempCancel = false; + if (Config.logPumpActions) + log.debug("setExtendedBolus: Correct extended bolus already set. Current: " + pump.extendedBolusAmount + " Asked: " + insulin); + return result; + } + boolean connectionOK = sExecutionService.extendedBolus(insulin, durationInHalfHours); + if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAmount - insulin) < getPumpDescription().extendedBolusStep) { + result.enacted = true; + result.success = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + result.isTempCancel = false; + result.duration = pump.extendedBolusRemainingMinutes; + result.absolute = pump.extendedBolusAbsoluteRate; + result.bolusDelivered = pump.extendedBolusAmount; + result.isPercent = false; + if (Config.logPumpActions) + log.debug("setExtendedBolus: OK"); + return result; + } + result.enacted = false; + result.success = false; + result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + log.error("setExtendedBolus: Failed to extended bolus"); + return result; + } + + @Override + public PumpEnactResult cancelExtendedBolus() { + PumpEnactResult result = new PumpEnactResult(); + ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (runningEB != null) { + sExecutionService.extendedBolusStop(); + result.enacted = true; + result.isTempCancel = true; + } + if (!pump.isExtendedInProgress) { + result.success = true; + result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + if (Config.logPumpActions) + log.debug("cancelExtendedBolus: OK"); + return result; + } else { + result.success = false; + result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); + log.error("cancelExtendedBolus: Failed to cancel extended bolus"); + return result; + } + } + + @Override + public void connect(String from) { + if (sExecutionService != null) { + sExecutionService.connect(); + pumpDescription.basalStep = pump.basalStep; + pumpDescription.bolusStep = pump.bolusStep; + } + } + + @Override + public boolean isConnected() { + return sExecutionService != null && sExecutionService.isConnected(); + } + + @Override + public boolean isConnecting() { + return sExecutionService != null && sExecutionService.isConnecting(); + } + + @Override + public void disconnect(String from) { + if (sExecutionService != null) sExecutionService.disconnect(from); + } + + @Override + public void stopConnecting() { + if (sExecutionService != null) sExecutionService.stopConnecting(); + } + + @Override + public void getPumpStatus() { + if (sExecutionService != null) sExecutionService.getPumpStatus(); + } + + @Override + public JSONObject getJSONStatus() { + if (pump.lastConnection + 5 * 60 * 1000L < System.currentTimeMillis()) { + return null; + } + JSONObject pumpjson = new JSONObject(); + JSONObject battery = new JSONObject(); + JSONObject status = new JSONObject(); + JSONObject extended = new JSONObject(); + try { + battery.put("percent", pump.batteryRemaining); + status.put("status", pump.pumpSuspended ? "suspended" : "normal"); + status.put("timestamp", DateUtil.toISOString(pump.lastConnection)); + extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + extended.put("PumpIOB", pump.iob); + if (pump.lastBolusTime.getTime() != 0) { + extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); + extended.put("LastBolusAmount", pump.lastBolusAmount); + } + TemporaryBasal tb = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); + if (tb != null) { + extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); + extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); + extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); + } + ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); + if (eb != null) { + extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); + extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); + extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); + } + extended.put("BaseBasalRate", getBaseBasalRate()); + try { + extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + } catch (Exception e) { + } + + pumpjson.put("battery", battery); + pumpjson.put("status", status); + pumpjson.put("extended", extended); + pumpjson.put("reservoir", (int) pump.reservoirRemainingUnits); + pumpjson.put("clock", DateUtil.toISOString(new Date())); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + return pumpjson; + } + + @Override + public String deviceID() { + return pump.serialNumber; + } + + @Override + public PumpDescription getPumpDescription() { + return pumpDescription; + } + + /** + * DanaR interface + */ + + @Override + public PumpEnactResult loadHistory(byte type) { + return sExecutionService.loadHistory(type); + } + + /** + * Constraint interface + */ + + @Override + public boolean isLoopEnabled() { + return true; + } + + @Override + public boolean isClosedModeEnabled() { + return true; + } + + @Override + public boolean isAutosensModeEnabled() { + return true; + } + + @Override + public boolean isAMAModeEnabled() { + return true; + } + + @Override + public boolean isSMBModeEnabled() { + return true; + } + + @SuppressWarnings("PointlessBooleanExpression") + @Override + public Double applyBasalConstraints(Double absoluteRate) { + double origAbsoluteRate = absoluteRate; + if (pump != null) { + if (absoluteRate > pump.maxBasal) { + absoluteRate = pump.maxBasal; + if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) + log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); + } + } + return absoluteRate; + } + + @SuppressWarnings("PointlessBooleanExpression") + @Override + public Integer applyBasalConstraints(Integer percentRate) { + Integer origPercentRate = percentRate; + if (percentRate < 0) percentRate = 0; + if (percentRate > getPumpDescription().maxTempPercent) + percentRate = getPumpDescription().maxTempPercent; + if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) + log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); + return percentRate; + } + + @SuppressWarnings("PointlessBooleanExpression") + @Override + public Double applyBolusConstraints(Double insulin) { + double origInsulin = insulin; + if (pump != null) { + if (insulin > pump.maxBolus) { + insulin = pump.maxBolus; + if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) + log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); + } + } + return insulin; + } + + @Override + public Integer applyCarbsConstraints(Integer carbs) { + return carbs; + } + + @Override + public Double applyMaxIOBConstraints(Double maxIob) { + return maxIob; + } + + @Nullable + @Override + public ProfileStore getProfile() { + if (pump.lastSettingsRead == 0) + return null; // no info now + return pump.createConvertedProfile(); + } + + @Override + public String getUnits() { + return pump.getUnits(); + } + + @Override + public String getProfileName() { + return pump.createConvertedProfileName(); + } + + // Reply for sms communicator + public String shortStatus(boolean veryShort) { + String ret = ""; + if (pump.lastConnection != 0) { + Long agoMsec = System.currentTimeMillis() - pump.lastConnection; + int agoMin = (int) (agoMsec / 60d / 1000d); + ret += "LastConn: " + agoMin + " minago\n"; + } + if (pump.lastBolusTime.getTime() != 0) { + ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; + } + if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { + ret += "Temp: " + MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; + } + if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { + ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; + } + 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; + } + // TODO: daily total constraint + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/BluetoothDevicePreference.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/BluetoothDevicePreference.java index 63e14f0921..e39fdcfdaa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/BluetoothDevicePreference.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/BluetoothDevicePreference.java @@ -6,6 +6,7 @@ import android.preference.ListPreference; import android.util.AttributeSet; import java.util.Set; +import java.util.Vector; public class BluetoothDevicePreference extends ListPreference { @@ -13,25 +14,22 @@ public class BluetoothDevicePreference extends ListPreference { super(context, attrs); BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter(); - Integer size = 0; - if (bta != null) { - size += bta.getBondedDevices().size(); - } - CharSequence[] entries = new CharSequence[size]; - int i = 0; + Vector entries = new Vector(); if (bta != null) { Set pairedDevices = bta.getBondedDevices(); for (BluetoothDevice dev : pairedDevices) { - entries[i] = dev.getName(); - i++; + String name = dev.getName(); + if(name != null) { + entries.add(name); + } } } - setEntries(entries); - setEntryValues(entries); + setEntries(entries.toArray(new CharSequence[0])); + setEntryValues(entries.toArray(new CharSequence[0])); } public BluetoothDevicePreference(Context context) { this(context, null); } -} \ No newline at end of file +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java index 8ac6bb306b..1ca54a7eb6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRFragment.java @@ -21,6 +21,12 @@ import com.squareup.otto.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Date; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.Unbinder; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventExtendedBolusChange; @@ -49,27 +55,24 @@ public class DanaRFragment extends SubscriberFragment { } }; - TextView lastConnectionView; - TextView btConnectionView; - TextView lastBolusView; - TextView dailyUnitsView; - TextView basaBasalRateView; - TextView tempBasalView; - TextView extendedBolusView; - TextView batteryView; - TextView reservoirView; - TextView iobView; - TextView firmwareView; - TextView basalStepView; - TextView bolusStepView; - TextView serialNumberView; - TextView queueView; - Button viewProfileButton; - Button historyButton; - Button statsButton; + @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; - LinearLayout pumpStatusLayout; - TextView pumpStatusView; + @BindView(R.id.overview_pumpstatuslayout) LinearLayout pumpStatusLayout; + @BindView(R.id.overview_pumpstatus) TextView pumpStatusView; public DanaRFragment() { } @@ -91,61 +94,10 @@ public class DanaRFragment extends SubscriberFragment { Bundle savedInstanceState) { try { View view = inflater.inflate(R.layout.danar_fragment, container, false); - btConnectionView = (TextView) view.findViewById(R.id.danar_btconnection); - lastConnectionView = (TextView) view.findViewById(R.id.danar_lastconnection); - lastBolusView = (TextView) view.findViewById(R.id.danar_lastbolus); - dailyUnitsView = (TextView) view.findViewById(R.id.danar_dailyunits); - basaBasalRateView = (TextView) view.findViewById(R.id.danar_basabasalrate); - tempBasalView = (TextView) view.findViewById(R.id.danar_tempbasal); - extendedBolusView = (TextView) view.findViewById(R.id.danar_extendedbolus); - batteryView = (TextView) view.findViewById(R.id.danar_battery); - reservoirView = (TextView) view.findViewById(R.id.danar_reservoir); - iobView = (TextView) view.findViewById(R.id.danar_iob); - firmwareView = (TextView) view.findViewById(R.id.danar_firmware); - viewProfileButton = (Button) view.findViewById(R.id.danar_viewprofile); - historyButton = (Button) view.findViewById(R.id.danar_history); - statsButton = (Button) view.findViewById(R.id.danar_stats); - basalStepView = (TextView) view.findViewById(R.id.danar_basalstep); - bolusStepView = (TextView) view.findViewById(R.id.danar_bolusstep); - serialNumberView = (TextView) view.findViewById(R.id.danar_serialnumber); - queueView = (TextView) view.findViewById(R.id.danar_queue); + unbinder = ButterKnife.bind(this, view); - pumpStatusView = (TextView) view.findViewById(R.id.overview_pumpstatus); pumpStatusView.setBackgroundColor(MainApp.sResources.getColor(R.color.colorInitializingBorder)); - pumpStatusLayout = (LinearLayout) view.findViewById(R.id.overview_pumpstatuslayout); - viewProfileButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - FragmentManager manager = getFragmentManager(); - ProfileViewDialog profileViewDialog = new ProfileViewDialog(); - profileViewDialog.show(manager, "ProfileViewDialog"); - } - }); - - historyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(getContext(), DanaRHistoryActivity.class)); - } - }); - - statsButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - startActivity(new Intent(getContext(), DanaRStatsActivity.class)); - } - }); - - btConnectionView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - log.debug("Clicked connect to pump"); - ConfigBuilderPlugin.getCommandQueue().readStatus("Clicked connect to pump", null); - } - }); - - updateGUI(); return view; } catch (Exception e) { Crashlytics.logException(e); @@ -154,6 +106,26 @@ public class DanaRFragment extends SubscriberFragment { return null; } + @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(), DanaRStatsActivity.class)); + } + + @OnClick(R.id.danar_btconnection) void onBtConnectionClick() { + log.debug("Clicked connect to pump"); + DanaRPump.getInstance().lastConnection = 0; + ConfigBuilderPlugin.getCommandQueue().readStatus("Clicked connect to pump", null); + } + @Subscribe public void onStatusEvent(final EventPumpStatusChanged c) { Activity activity = getActivity(); @@ -212,8 +184,8 @@ public class DanaRFragment extends SubscriberFragment { @Override public void run() { DanaRPump pump = DanaRPump.getInstance(); - if (pump.lastConnection.getTime() != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection.getTime(); + 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.sResources.getString(R.string.minago), agoMin) + ")"); SetWarnColor.setColor(lastConnectionView, agoMin, 16d, 31d); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java index 4ba660db66..9a0be55897 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java @@ -5,69 +5,30 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.annotation.Nullable; import com.squareup.otto.Subscribe; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; -import java.util.Objects; - -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.PumpEnactResult; -import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; -import info.nightscout.androidaps.interfaces.DanaRInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.data.ProfileStore; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; import info.nightscout.androidaps.plugins.PumpDanaR.services.DanaRExecutionService; -import info.nightscout.utils.DateUtil; -import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { - private static Logger log = LoggerFactory.getLogger(DanaRPlugin.class); - - @Override - public String getFragmentClass() { - return DanaRFragment.class.getName(); - } - - private static boolean fragmentPumpEnabled = false; - private static boolean fragmentProfileEnabled = false; - private static boolean fragmentPumpVisible = true; - - private static DanaRExecutionService sExecutionService; - - - private static DanaRPump pump = DanaRPump.getInstance(); - private static boolean useExtendedBoluses = false; +public class DanaRPlugin extends AbstractDanaRPlugin { private static DanaRPlugin plugin = null; @@ -77,9 +38,8 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C return plugin; } - public static PumpDescription pumpDescription = new PumpDescription(); - public DanaRPlugin() { + log = LoggerFactory.getLogger(DanaRPlugin.class); useExtendedBoluses = SP.getBoolean("danar_useextended", false); Context context = MainApp.instance().getApplicationContext(); @@ -110,6 +70,8 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C pumpDescription.basalMinimumRate = 0.04d; pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = true; } private ServiceConnection mConnection = new ServiceConnection() { @@ -145,83 +107,17 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C } // Plugin base interface - @Override - public int getType() { - return PluginBase.PUMP; - } - @Override public String getName() { return MainApp.instance().getString(R.string.danarpump); } - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.danarpump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - if (type == PluginBase.PROFILE) return fragmentProfileEnabled && fragmentPumpEnabled; - else if (type == PluginBase.PUMP) return fragmentPumpEnabled; - else if (type == PluginBase.CONSTRAINTS) return fragmentPumpEnabled; - return false; - } - - @Override - public boolean isVisibleInTabs(int type) { - if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; - else if (type == PluginBase.PUMP) return fragmentPumpVisible; - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return type == PUMP; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PluginBase.PROFILE) - fragmentProfileEnabled = fragmentEnabled; - else if (type == PluginBase.PUMP) - fragmentPumpEnabled = fragmentEnabled; - // if pump profile was enabled need to switch to another too - if (type == PluginBase.PUMP && !fragmentEnabled && fragmentProfileEnabled) { - setFragmentEnabled(PluginBase.PROFILE, false); - setFragmentVisible(PluginBase.PROFILE, false); - NSProfilePlugin.getPlugin().setFragmentEnabled(PluginBase.PROFILE, true); - NSProfilePlugin.getPlugin().setFragmentVisible(PluginBase.PROFILE, true); - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PluginBase.PUMP) - fragmentPumpVisible = fragmentVisible; - } - @Override public int getPreferencesId() { return R.xml.pref_danar; } + // Pump interface @Override public boolean isFakingTempsByExtendedBoluses() { return useExtendedBoluses; @@ -229,82 +125,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C @Override public boolean isInitialized() { - return pump.lastConnection.getTime() > 0 && pump.isExtendedBolusEnabled && pump.maxBasal > 0; - } - - @Override - public boolean isSuspended() { - return pump.pumpSuspended; - } - - @Override - public boolean isBusy() { - if (sExecutionService == null) return false; - return sExecutionService.isConnected() || sExecutionService.isConnecting(); - } - - // Pump interface - @Override - public PumpEnactResult setNewBasalProfile(Profile profile) { - PumpEnactResult result = new PumpEnactResult(); - - if (sExecutionService == null) { - log.error("setNewBasalProfile sExecutionService is null"); - result.comment = "setNewBasalProfile sExecutionService is null"; - return result; - } - if (!isInitialized()) { - log.error("setNewBasalProfile not initialized"); - Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - } - if (!sExecutionService.updateBasalsInPump(profile)) { - Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - result.success = true; - result.enacted = true; - result.comment = "OK"; - return result; - } - } - - @Override - public boolean isThisProfileSet(Profile profile) { - if (!isInitialized()) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - if (pump.pumpProfiles == null) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - int basalValues = pump.basal48Enable ? 48 : 24; - int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; - for (int h = 0; h < basalValues; h++) { - Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; - Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); - if (profileValue == null) return true; - if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { - log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); - return false; - } - } - return true; - } - - @Override - public Date lastDataTime() { - return pump.lastConnection; - } - - @Override - public double getBaseBasalRate() { - return pump.currentBasal; + return pump.lastConnection > 0 && pump.isExtendedBolusEnabled && pump.maxBasal > 0; } @Override @@ -314,7 +135,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { Treatment t = new Treatment(); boolean connectionOK = false; - if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, t); + if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); PumpEnactResult result = new PumpEnactResult(); result.success = connectionOK; result.bolusDelivered = t.insulin; @@ -337,15 +158,6 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C } } - @Override - public void stopBolusDelivering() { - if (sExecutionService == null) { - log.error("stopBolusDelivering sExecutionService is null"); - return; - } - sExecutionService.bolusStop(); - } - // This is called from APS @Override public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { @@ -498,100 +310,6 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C return result; } - @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { - PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - percent = configBuilderPlugin.applyBasalConstraints(percent); - if (percent < 0) { - result.isTempCancel = false; - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); - log.error("setTempBasalPercent: Invalid input"); - return result; - } - if (percent > getPumpDescription().maxTempPercent) - percent = getPumpDescription().maxTempPercent; - TemporaryBasal runningTB = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); - if (runningTB != null && runningTB.percentRate == percent && !enforceNew) { - result.enacted = false; - result.success = true; - result.isTempCancel = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.duration = pump.tempBasalRemainingMin; - result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); - result.isPercent = true; - if (Config.logPumpActions) - log.debug("setTempBasalPercent: Correct value already set"); - return result; - } - int durationInHours = Math.max(durationInMinutes / 60, 1); - boolean connectionOK = sExecutionService.tempBasal(percent, durationInHours); - if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { - result.enacted = true; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.isTempCancel = false; - result.duration = pump.tempBasalRemainingMin; - result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); - result.isPercent = true; - if (Config.logPumpActions) - log.debug("setTempBasalPercent: OK"); - return result; - } - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.tempbasaldeliveryerror); - log.error("setTempBasalPercent: Failed to set temp basal"); - return result; - } - - @Override - public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - insulin = configBuilderPlugin.applyBolusConstraints(insulin); - // needs to be rounded - int durationInHalfHours = Math.max(durationInMinutes / 30, 1); - insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); - - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = false; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.isPercent = false; - result.isTempCancel = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: Correct extended bolus already set. Current: " + pump.extendedBolusAmount + " Asked: " + insulin); - return result; - } - boolean connectionOK = sExecutionService.extendedBolus(insulin, durationInHalfHours); - if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAmount - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = true; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.isTempCancel = false; - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.bolusDelivered = pump.extendedBolusAmount; - result.isPercent = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: OK"); - return result; - } - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("setExtendedBolus: Failed to extended bolus"); - return result; - } - @Override public PumpEnactResult cancelTempBasal(boolean force) { if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) @@ -632,257 +350,8 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C } } - @Override - public PumpEnactResult cancelExtendedBolus() { - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null) { - sExecutionService.extendedBolusStop(); - result.enacted = true; - result.isTempCancel = true; - } - if (!pump.isExtendedInProgress) { - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - if (Config.logPumpActions) - log.debug("cancelExtendedBolus: OK"); - return result; - } else { - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("cancelExtendedBolus: Failed to cancel extended bolus"); - return result; - } - } - - @Override - public void connect(String from) { - if (sExecutionService != null) { - sExecutionService.connect(from); - pumpDescription.basalStep = pump.basalStep; - pumpDescription.bolusStep = pump.bolusStep; - } - } - - @Override - public boolean isConnected() { - return sExecutionService != null && sExecutionService.isConnected(); - } - - @Override - public boolean isConnecting() { - return sExecutionService != null && sExecutionService.isConnecting(); - } - - @Override - public void disconnect(String from) { - if (sExecutionService != null) sExecutionService.disconnect(from); - } - - @Override - public void stopConnecting() { - if (sExecutionService != null) sExecutionService.stopConnecting(); - } - - @Override - public void getPumpStatus() { - if (sExecutionService != null) sExecutionService.getPumpStatus(); - } - - @Override - public JSONObject getJSONStatus() { - if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { - return null; - } - JSONObject pumpjson = new JSONObject(); - JSONObject battery = new JSONObject(); - JSONObject status = new JSONObject(); - JSONObject extended = new JSONObject(); - try { - battery.put("percent", pump.batteryRemaining); - status.put("status", pump.pumpSuspended ? "suspended" : "normal"); - status.put("timestamp", DateUtil.toISOString(pump.lastConnection)); - extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); - extended.put("PumpIOB", pump.iob); - if (pump.lastBolusTime.getTime() != 0) { - extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); - extended.put("LastBolusAmount", pump.lastBolusAmount); - } - TemporaryBasal tb = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); - if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); - extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); - extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); - } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (eb != null) { - extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); - extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); - extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); - } - extended.put("BaseBasalRate", getBaseBasalRate()); - try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); - } catch (Exception e) { - } - - pumpjson.put("battery", battery); - pumpjson.put("status", status); - pumpjson.put("extended", extended); - pumpjson.put("reservoir", (int) pump.reservoirRemainingUnits); - pumpjson.put("clock", DateUtil.toISOString(new Date())); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return pumpjson; - } - - @Override - public String deviceID() { - return pump.serialNumber; - } - - @Override - public PumpDescription getPumpDescription() { - return pumpDescription; - } - - /** - * DanaR interface - */ - - @Override - public PumpEnactResult loadHistory(byte type) { - return sExecutionService.loadHistory(type); - } - @Override public PumpEnactResult loadEvents() { return null; // no history, not needed } - - /** - * Constraint interface - */ - - @Override - public boolean isLoopEnabled() { - return true; - } - - @Override - public boolean isClosedModeEnabled() { - return true; - } - - @Override - public boolean isAutosensModeEnabled() { - return true; - } - - @Override - public boolean isAMAModeEnabled() { - return true; - } - - @Override - public boolean isSMBModeEnabled() { - return true; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBasalConstraints(Double absoluteRate) { - double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } - } - return absoluteRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - if (percentRate < 0) percentRate = 0; - if (percentRate > getPumpDescription().maxTempPercent) - percentRate = getPumpDescription().maxTempPercent; - if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); - return percentRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBolusConstraints(Double insulin) { - double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } - } - return insulin; - } - - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - return maxIob; - } - - @Nullable - @Override - public ProfileStore getProfile() { - if (pump.lastSettingsRead.getTime() == 0) - return null; // no info now - return pump.createConvertedProfile(); - } - - @Override - public String getUnits() { - return pump.getUnits(); - } - - @Override - public String getProfileName() { - return pump.createConvertedProfileName(); - } - - // Reply for sms communicator - public String shortStatus(boolean veryShort) { - String ret = ""; - if (pump.lastConnection.getTime() != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection.getTime(); - int agoMin = (int) (agoMsec / 60d / 1000d); - ret += "LastConn: " + agoMin + " minago\n"; - } - if (pump.lastBolusTime.getTime() != 0) { - ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - ret += "Temp: " + MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; - } - 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; - } - // TODO: daily total constraint - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java index 9f88011c96..0e1a9ceb75 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPump.java @@ -56,8 +56,8 @@ public class DanaRPump { public static final int CARBS = 14; public static final int PRIMECANNULA = 15; - public Date lastConnection = new Date(0); - public Date lastSettingsRead = new Date(0); + public long lastConnection = 0; + public long lastSettingsRead =0; // Info public String serialNumber = ""; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/SerialIOThread.java index a333faecea..2f377a566a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/SerialIOThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/SerialIOThread.java @@ -13,12 +13,13 @@ import java.io.OutputStream; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageHashTable; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractSerialIOThread; import info.nightscout.utils.CRC; /** * Created by mike on 17.07.2016. */ -public class SerialIOThread extends Thread { +public class SerialIOThread extends AbstractSerialIOThread { private static Logger log = LoggerFactory.getLogger(SerialIOThread.class); private InputStream mInputStream = null; @@ -28,10 +29,10 @@ public class SerialIOThread extends Thread { private boolean mKeepRunning = true; private byte[] mReadBuff = new byte[0]; - MessageBase processedMessage; + private MessageBase processedMessage; public SerialIOThread(BluetoothSocket rfcommSocket) { - super(SerialIOThread.class.toString()); + super(); mRfCommSocket = rfcommSocket; try { @@ -137,6 +138,7 @@ public class SerialIOThread extends Thread { } } + @Override public synchronized void sendMessage(MessageBase message) { if (!mRfCommSocket.isConnected()) { log.error("Socket not connected on sendMessage"); @@ -172,6 +174,7 @@ public class SerialIOThread extends Thread { } } + @Override public void disconnect(String reason) { mKeepRunning = false; try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java index 17f90a1ba7..fe0d280113 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgInitConnStatusTime.java @@ -35,8 +35,8 @@ public class MsgInitConnStatusTime extends MessageBase { MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, true); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, false); - DanaRPump.getInstance().lastConnection = new Date(0); // mark not initialized + DanaRPump.getInstance().lastConnection = 0; // mark not initialized //If profile coming from pump, switch it as well if(MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PROFILE)){ (MainApp.getSpecificPlugin(DanaRPlugin.class)).setFragmentEnabled(PluginBase.PROFILE, false); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingBasal.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingBasal.java index e1a7e45315..a47803c1b0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingBasal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingBasal.java @@ -23,7 +23,7 @@ public class MsgSettingBasal extends MessageBase { pump.pumpProfiles[pump.activeProfile] = new double[24]; for (int index = 0; index < 24; index++) { int basal = intFromBuff(bytes, 2 * index, 2); - if (basal < DanaRPlugin.pumpDescription.basalMinimumRate) basal = 0; + if (basal < DanaRPlugin.getPlugin().pumpDescription.basalMinimumRate) basal = 0; pump.pumpProfiles[pump.activeProfile][index] = basal / 100d; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java index 2d54d4f4a2..5d7b4b65ac 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/comm/MsgSettingMeal.java @@ -6,10 +6,12 @@ import org.slf4j.LoggerFactory; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; +import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; /** * Created by mike on 13.12.2016. @@ -40,6 +42,11 @@ public class MsgSettingMeal extends MessageBase { log.debug("Is Config U/d: " + pump.isConfigUD); } + // DanaRKorean is not possible to set to 0.01 but it works when controlled from AAPS + if (DanaRKoreanPlugin.getPlugin().isEnabled(PluginBase.PUMP)) { + pump.basalStep = 0.01d; + } + if (pump.basalStep != 0.01d) { Notification notification = new Notification(Notification.WRONGBASALSTEP, MainApp.sResources.getString(R.string.danar_setbasalstep001), Notification.URGENT); MainApp.bus().post(new EventNewNotification(notification)); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java new file mode 100644 index 0000000000..d853c9abf1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractDanaRExecutionService.java @@ -0,0 +1,225 @@ +package info.nightscout.androidaps.plugins.PumpDanaR.services; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.SystemClock; + +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Set; +import java.util.UUID; + +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.data.PumpEnactResult; +import info.nightscout.androidaps.db.Treatment; +import info.nightscout.androidaps.events.EventPumpStatusChanged; +import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaR.SerialIOThread; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStop; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryAlarm; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBasalHour; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBolus; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryCarbo; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDailyInsulin; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDone; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryError; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryGlucose; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryRefill; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistorySuspend; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStart; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStop; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; +import info.nightscout.utils.SP; +import info.nightscout.utils.ToastUtils; + +/** + * Created by mike on 28.01.2018. + */ + +public abstract class AbstractDanaRExecutionService extends Service { + protected Logger log; + + protected String mDevName; + + protected BluetoothSocket mRfcommSocket; + protected BluetoothDevice mBTDevice; + + protected DanaRPump mDanaRPump = DanaRPump.getInstance(); + protected Treatment mBolusingTreatment = null; + + protected Boolean mConnectionInProgress = false; + + protected AbstractSerialIOThread mSerialIOThread; + + protected IBinder mBinder; + + protected final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); + + public abstract boolean updateBasalsInPump(final Profile profile); + + public abstract void connect(); + + public abstract void getPumpStatus(); + + public abstract PumpEnactResult loadEvents(); + + public abstract boolean bolus(double amount, int carbs, long carbtime, final Treatment t); + + public abstract boolean highTempBasal(int percent); // Rv2 only + + public abstract boolean tempBasal(int percent, int durationInHours); + + public abstract boolean tempBasalStop(); + + public abstract boolean extendedBolus(double insulin, int durationInHalfHours); + + public abstract boolean extendedBolusStop(); + + + protected BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + String action = intent.getAction(); + if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { + log.debug("Device was disconnected " + device.getName());//Device was disconnected + if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { + if (mSerialIOThread != null) { + mSerialIOThread.disconnect("BT disconnection broadcast"); + } + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); + } + } + } + }; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + public boolean isConnected() { + return mRfcommSocket != null && mRfcommSocket.isConnected(); + } + + public boolean isConnecting() { + return mConnectionInProgress; + } + + public void disconnect(String from) { + if (mSerialIOThread != null) + mSerialIOThread.disconnect(from); + } + + public void stopConnecting() { + if (mSerialIOThread != null) + mSerialIOThread.disconnect("stopConnecting"); + } + + protected void getBTSocketForSelectedPump() { + mDevName = SP.getString(MainApp.sResources.getString(R.string.key_danar_bt_name), ""); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (bluetoothAdapter != null) { + Set bondedDevices = bluetoothAdapter.getBondedDevices(); + + for (BluetoothDevice device : bondedDevices) { + if (mDevName.equals(device.getName())) { + mBTDevice = device; + try { + mRfcommSocket = mBTDevice.createRfcommSocketToServiceRecord(SPP_UUID); + } catch (IOException e) { + log.error("Error creating socket: ", e); + } + break; + } + } + } else { + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.nobtadapter)); + } + if (mBTDevice == null) { + ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.devicenotfound)); + } + } + + public void bolusStop() { + if (Config.logDanaBTComm) + log.debug("bolusStop >>>>> @ " + (mBolusingTreatment == null ? "" : mBolusingTreatment.insulin)); + MsgBolusStop stop = new MsgBolusStop(); + stop.forced = true; + if (isConnected()) { + mSerialIOThread.sendMessage(stop); + while (!stop.stopped) { + mSerialIOThread.sendMessage(stop); + SystemClock.sleep(200); + } + } else { + stop.stopped = true; + } + } + + public PumpEnactResult loadHistory(byte type) { + PumpEnactResult result = new PumpEnactResult(); + if (!isConnected()) return result; + MessageBase msg = null; + switch (type) { + case RecordTypes.RECORD_TYPE_ALARM: + msg = new MsgHistoryAlarm(); + break; + case RecordTypes.RECORD_TYPE_BASALHOUR: + msg = new MsgHistoryBasalHour(); + break; + case RecordTypes.RECORD_TYPE_BOLUS: + msg = new MsgHistoryBolus(); + break; + case RecordTypes.RECORD_TYPE_CARBO: + msg = new MsgHistoryCarbo(); + break; + case RecordTypes.RECORD_TYPE_DAILY: + msg = new MsgHistoryDailyInsulin(); + break; + case RecordTypes.RECORD_TYPE_ERROR: + msg = new MsgHistoryError(); + break; + case RecordTypes.RECORD_TYPE_GLUCOSE: + msg = new MsgHistoryGlucose(); + break; + case RecordTypes.RECORD_TYPE_REFILL: + msg = new MsgHistoryRefill(); + break; + case RecordTypes.RECORD_TYPE_SUSPEND: + msg = new MsgHistorySuspend(); + break; + } + MsgHistoryDone done = new MsgHistoryDone(); + mSerialIOThread.sendMessage(new MsgPCCommStart()); + SystemClock.sleep(400); + mSerialIOThread.sendMessage(msg); + while (!done.received && mRfcommSocket.isConnected()) { + SystemClock.sleep(100); + } + SystemClock.sleep(200); + mSerialIOThread.sendMessage(new MsgPCCommStop()); + result.success = true; + result.comment = "OK"; + return result; + } + + +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractSerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractSerialIOThread.java new file mode 100644 index 0000000000..1cca0b2512 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/AbstractSerialIOThread.java @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.plugins.PumpDanaR.services; + +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; + +/** + * Created by mike on 28.01.2018. + */ + +public abstract class AbstractSerialIOThread extends Thread { + + public abstract void sendMessage(MessageBase message); + public abstract void disconnect(String reason); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java index 0aa4b176c7..63215464a8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/services/DanaRExecutionService.java @@ -1,41 +1,33 @@ package info.nightscout.androidaps.plugins.PumpDanaR.services; -import android.app.Service; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; -import android.os.IBinder; import android.os.SystemClock; import com.squareup.otto.Subscribe; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; -import java.util.Set; -import java.util.UUID; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.SerialIOThread; @@ -45,18 +37,6 @@ import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStartWithSpeed; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStop; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgCheckValue; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryAlarm; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBasalHour; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBolus; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryCarbo; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDailyInsulin; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDone; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryError; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryGlucose; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryRefill; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistorySuspend; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStart; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStop; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetActivateBasalProfile; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetBasalProfile; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetCarbsEntry; @@ -67,9 +47,9 @@ import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetTempBasalStop; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetTime; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingActiveProfile; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingBasal; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingMeal; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingGlucose; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingMaxValues; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingMeal; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingProfileRatios; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingProfileRatiosAll; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingPumpTime; @@ -78,51 +58,18 @@ import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatus; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusBasic; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusBolusExtended; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusTempBasal; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; import info.nightscout.utils.ToastUtils; -public class DanaRExecutionService extends Service { - private static Logger log = LoggerFactory.getLogger(DanaRExecutionService.class); - - private String devName; - - private SerialIOThread mSerialIOThread; - private BluetoothSocket mRfcommSocket; - private BluetoothDevice mBTDevice; - - private IBinder mBinder = new LocalBinder(); - - private DanaRPump danaRPump = DanaRPump.getInstance(); - private Treatment bolusingTreatment = null; - - private static Boolean connectionInProgress = false; - - private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); - - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - String action = intent.getAction(); - if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { - log.debug("Device was disconnected " + device.getName());//Device was disconnected - if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { - if (mSerialIOThread != null) { - mSerialIOThread.disconnect("BT disconnection broadcast"); - } - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); - } - } - } - }; +public class DanaRExecutionService extends AbstractDanaRExecutionService{ public DanaRExecutionService() { + log = LoggerFactory.getLogger(DanaRExecutionService.class); + mBinder = new LocalBinder(); + registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); } @@ -133,17 +80,6 @@ public class DanaRExecutionService extends Service { } } - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - return START_STICKY; - } - private void registerBus() { try { MainApp.bus().unregister(this); @@ -154,49 +90,27 @@ public class DanaRExecutionService extends Service { } @Subscribe - public void onStatusEvent(EventAppExit event) { - if (Config.logFunctionCalls) - log.debug("EventAppExit received"); - + public void onStatusEvent(final EventPreferenceChange pch) { if (mSerialIOThread != null) - mSerialIOThread.disconnect("Application exit"); - - MainApp.instance().getApplicationContext().unregisterReceiver(receiver); - - stopSelf(); - if (Config.logFunctionCalls) - log.debug("EventAppExit finished"); + mSerialIOThread.disconnect("EventPreferenceChange"); } - public boolean isConnected() { - return mRfcommSocket != null && mRfcommSocket.isConnected(); - } - - public boolean isConnecting() { - return connectionInProgress; - } - - public void disconnect(String from) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect(from); - } - - public void connect(String from) { - if (danaRPump.password != -1 && danaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { + public void connect() { + if (mDanaRPump.password != -1 && mDanaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumppassword), R.raw.error); return; } - if (connectionInProgress) + if (mConnectionInProgress) return; new Thread(new Runnable() { @Override public void run() { - connectionInProgress = true; + mConnectionInProgress = true; getBTSocketForSelectedPump(); if (mRfcommSocket == null || mBTDevice == null) { - connectionInProgress = false; + mConnectionInProgress = false; return; // Device not found } @@ -217,48 +131,11 @@ public class DanaRExecutionService extends Service { MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED, 0)); } - connectionInProgress = false; + mConnectionInProgress = false; } }).start(); } - public void stopConnecting() { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("stopConnecting"); - } - - private void getBTSocketForSelectedPump() { - devName = SP.getString(MainApp.sResources.getString(R.string.key_danar_bt_name), ""); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter != null) { - Set bondedDevices = bluetoothAdapter.getBondedDevices(); - - for (BluetoothDevice device : bondedDevices) { - if (devName.equals(device.getName())) { - mBTDevice = device; - try { - mRfcommSocket = mBTDevice.createRfcommSocketToServiceRecord(SPP_UUID); - } catch (IOException e) { - log.error("Error creating socket: ", e); - } - break; - } - } - } else { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.nobtadapter)); - } - if (mBTDevice == null) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.devicenotfound)); - } - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - } - public void getPumpStatus() { try { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpstatus))); @@ -268,7 +145,7 @@ public class DanaRExecutionService extends Service { MsgStatusBolusExtended exStatusMsg = new MsgStatusBolusExtended(); MsgCheckValue checkValue = new MsgCheckValue(); - if (danaRPump.isNewPump) { + if (mDanaRPump.isNewPump) { mSerialIOThread.sendMessage(checkValue); if (!checkValue.received) { return; @@ -283,8 +160,8 @@ public class DanaRExecutionService extends Service { mSerialIOThread.sendMessage(exStatusMsg); MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingbolusstatus))); - Date now = new Date(); - if (danaRPump.lastSettingsRead.getTime() + 60 * 60 * 1000L < now.getTime() || !MainApp.getSpecificPlugin(DanaRPlugin.class).isInitialized()) { + long now = System.currentTimeMillis(); + if (mDanaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !MainApp.getSpecificPlugin(DanaRPlugin.class).isInitialized()) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); @@ -298,26 +175,26 @@ public class DanaRExecutionService extends Service { mSerialIOThread.sendMessage(new MsgSettingProfileRatiosAll()); MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - long timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + long timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); if (Math.abs(timeDiff) > 10) { mSerialIOThread.sendMessage(new MsgSetTime(new Date())); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); } - danaRPump.lastSettingsRead = now; + mDanaRPump.lastSettingsRead = now; } - danaRPump.lastConnection = now; + mDanaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); - if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { - log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); + if (mDanaRPump.dailyTotalUnits > mDanaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { + log.debug("Approaching daily limit: " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits); Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.sResources.getString(R.string.approachingdailylimit), Notification.URGENT); MainApp.bus().post(new EventNewNotification(reportFail)); - NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); + NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits + "U"); } } catch (Exception e) { log.error("Unhandled exception", e); @@ -326,10 +203,10 @@ public class DanaRExecutionService extends Service { public boolean tempBasal(int percent, int durationInHours) { if (!isConnected()) return false; - if (danaRPump.isTempBasalInProgress) { + if (mDanaRPump.isTempBasalInProgress) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); - waitMsec(500); + SystemClock.sleep(500); } MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); @@ -365,11 +242,16 @@ public class DanaRExecutionService extends Service { return true; } - public boolean bolus(double amount, int carbs, final Treatment t) { + @Override + public PumpEnactResult loadEvents() { + return null; + } + + public boolean bolus(double amount, int carbs, long carbtime, final Treatment t) { if (!isConnected()) return false; if (BolusProgressDialog.stopPressed) return false; - bolusingTreatment = t; + mBolusingTreatment = t; int preferencesSpeed = SP.getInt(R.string.key_danars_bolusspeed, 0); MessageBase start; if (preferencesSpeed == 0) @@ -379,7 +261,7 @@ public class DanaRExecutionService extends Service { MsgBolusStop stop = new MsgBolusStop(amount, t); if (carbs > 0) { - mSerialIOThread.sendMessage(new MsgSetCarbsEntry(System.currentTimeMillis(), carbs)); + mSerialIOThread.sendMessage(new MsgSetCarbsEntry(carbtime, carbs)); } MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables @@ -392,20 +274,20 @@ public class DanaRExecutionService extends Service { return false; } while (!stop.stopped && !start.failed) { - waitMsec(100); + SystemClock.sleep(100); if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm stop.stopped = true; stop.forced = true; log.debug("Communication stopped"); } } - waitMsec(300); + SystemClock.sleep(300); EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); bolusingEvent.t = t; bolusingEvent.percent = 99; - bolusingTreatment = null; + mBolusingTreatment = null; int speed = 12; switch (preferencesSpeed) { @@ -437,11 +319,11 @@ public class DanaRExecutionService extends Service { ConfigBuilderPlugin.getCommandQueue().independentConnect("bolusingInterrupted", new Callback() { @Override public void run() { - if (danaRPump.lastBolusTime.getTime() > System.currentTimeMillis() - 60 * 1000L) { // last bolus max 1 min old - t.insulin = danaRPump.lastBolusAmount; - log.debug("Used bolus amount from history: " + danaRPump.lastBolusAmount); + if (mDanaRPump.lastBolusTime.getTime() > System.currentTimeMillis() - 60 * 1000L) { // last bolus max 1 min old + t.insulin = mDanaRPump.lastBolusAmount; + log.debug("Used bolus amount from history: " + mDanaRPump.lastBolusAmount); } else { - log.debug("Bolus amount in history too old: " + danaRPump.lastBolusTime.toLocaleString()); + log.debug("Bolus amount in history too old: " + mDanaRPump.lastBolusTime.toLocaleString()); } synchronized (o) { o.notify(); @@ -460,22 +342,6 @@ public class DanaRExecutionService extends Service { return true; } - public void bolusStop() { - if (Config.logDanaBTComm) - log.debug("bolusStop >>>>> @ " + (bolusingTreatment == null ? "" : bolusingTreatment.insulin)); - MsgBolusStop stop = new MsgBolusStop(); - stop.forced = true; - if (isConnected()) { - mSerialIOThread.sendMessage(stop); - while (!stop.stopped) { - mSerialIOThread.sendMessage(stop); - waitMsec(200); - } - } else { - stop.stopped = true; - } - } - public boolean carbsEntry(int amount) { if (!isConnected()) return false; MsgSetCarbsEntry msg = new MsgSetCarbsEntry(System.currentTimeMillis(), amount); @@ -483,51 +349,9 @@ public class DanaRExecutionService extends Service { return true; } - public PumpEnactResult loadHistory(byte type) { - PumpEnactResult result = new PumpEnactResult(); - if (!isConnected()) return result; - MessageBase msg = null; - switch (type) { - case RecordTypes.RECORD_TYPE_ALARM: - msg = new MsgHistoryAlarm(); - break; - case RecordTypes.RECORD_TYPE_BASALHOUR: - msg = new MsgHistoryBasalHour(); - break; - case RecordTypes.RECORD_TYPE_BOLUS: - msg = new MsgHistoryBolus(); - break; - case RecordTypes.RECORD_TYPE_CARBO: - msg = new MsgHistoryCarbo(); - break; - case RecordTypes.RECORD_TYPE_DAILY: - msg = new MsgHistoryDailyInsulin(); - break; - case RecordTypes.RECORD_TYPE_ERROR: - msg = new MsgHistoryError(); - break; - case RecordTypes.RECORD_TYPE_GLUCOSE: - msg = new MsgHistoryGlucose(); - break; - case RecordTypes.RECORD_TYPE_REFILL: - msg = new MsgHistoryRefill(); - break; - case RecordTypes.RECORD_TYPE_SUSPEND: - msg = new MsgHistorySuspend(); - break; - } - MsgHistoryDone done = new MsgHistoryDone(); - mSerialIOThread.sendMessage(new MsgPCCommStart()); - waitMsec(400); - mSerialIOThread.sendMessage(msg); - while (!done.received && mRfcommSocket.isConnected()) { - waitMsec(100); - } - waitMsec(200); - mSerialIOThread.sendMessage(new MsgPCCommStop()); - result.success = true; - result.comment = "OK"; - return result; + @Override + public boolean highTempBasal(int percent) { + return false; } public boolean updateBasalsInPump(final Profile profile) { @@ -538,13 +362,25 @@ public class DanaRExecutionService extends Service { mSerialIOThread.sendMessage(msgSet); MsgSetActivateBasalProfile msgActivate = new MsgSetActivateBasalProfile((byte) 0); mSerialIOThread.sendMessage(msgActivate); - danaRPump.lastSettingsRead = new Date(0); // force read full settings + mDanaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } - private void waitMsec(long msecs) { - SystemClock.sleep(msecs); + @Subscribe + public void onStatusEvent(EventAppExit event) { + if (Config.logFunctionCalls) + log.debug("EventAppExit received"); + + if (mSerialIOThread != null) + mSerialIOThread.disconnect("Application exit"); + + MainApp.instance().getApplicationContext().unregisterReceiver(receiver); + + stopSelf(); + if (Config.logFunctionCalls) + log.debug("EventAppExit finished"); } + } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java index f269aff112..58392e65f9 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/DanaRKoreanPlugin.java @@ -5,71 +5,31 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.annotation.Nullable; import com.squareup.otto.Subscribe; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; -import java.util.Objects; - -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.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; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; -import info.nightscout.androidaps.interfaces.DanaRInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; -import info.nightscout.androidaps.plugins.PumpDanaR.DanaRFragment; -import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaR.AbstractDanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaRKorean.services.DanaRKoreanExecutionService; -import info.nightscout.utils.DateUtil; -import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { - private static Logger log = LoggerFactory.getLogger(DanaRKoreanPlugin.class); - - @Override - public String getFragmentClass() { - return DanaRFragment.class.getName(); - } - - private static boolean fragmentPumpEnabled = false; - private static boolean fragmentProfileEnabled = false; - private static boolean fragmentPumpVisible = true; - - private static DanaRKoreanExecutionService sExecutionService; - - - private static DanaRPump pump = DanaRPump.getInstance(); - private boolean useExtendedBoluses = false; +public class DanaRKoreanPlugin extends AbstractDanaRPlugin { private static DanaRKoreanPlugin plugin = null; @@ -79,9 +39,8 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf return plugin; } - public static PumpDescription pumpDescription = new PumpDescription(); - public DanaRKoreanPlugin() { + log = LoggerFactory.getLogger(DanaRKoreanPlugin.class); useExtendedBoluses = SP.getBoolean("danar_useextended", false); Context context = MainApp.instance().getApplicationContext(); @@ -112,6 +71,8 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf pumpDescription.basalMinimumRate = 0.1d; pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = true; } private ServiceConnection mConnection = new ServiceConnection() { @@ -147,83 +108,17 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf } // Plugin base interface - @Override - public int getType() { - return PluginBase.PUMP; - } - @Override public String getName() { return MainApp.instance().getString(R.string.danarkoreanpump); } - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.danarpump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - if (type == PluginBase.PROFILE) return fragmentProfileEnabled && fragmentPumpEnabled; - else if (type == PluginBase.PUMP) return fragmentPumpEnabled; - else if (type == PluginBase.CONSTRAINTS) return fragmentPumpEnabled; - return false; - } - - @Override - public boolean isVisibleInTabs(int type) { - if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; - else if (type == PluginBase.PUMP) return fragmentPumpVisible; - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return type == PUMP; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PluginBase.PROFILE) - fragmentProfileEnabled = fragmentEnabled; - else if (type == PluginBase.PUMP) - fragmentPumpEnabled = fragmentEnabled; - // if pump profile was enabled need to switch to another too - if (type == PluginBase.PUMP && !fragmentEnabled && fragmentProfileEnabled) { - setFragmentEnabled(PluginBase.PROFILE, false); - setFragmentVisible(PluginBase.PROFILE, false); - NSProfilePlugin.getPlugin().setFragmentEnabled(PluginBase.PROFILE, true); - NSProfilePlugin.getPlugin().setFragmentVisible(PluginBase.PROFILE, true); - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PluginBase.PUMP) - fragmentPumpVisible = fragmentVisible; - } - @Override public int getPreferencesId() { return R.xml.pref_danarkorean; } + // Pump interface @Override public boolean isFakingTempsByExtendedBoluses() { return useExtendedBoluses; @@ -231,82 +126,7 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf @Override public boolean isInitialized() { - return pump.lastConnection.getTime() > 0 && pump.maxBasal > 0 && !pump.isConfigUD && !pump.isEasyModeEnabled && pump.isExtendedBolusEnabled; - } - - @Override - public boolean isSuspended() { - return pump.pumpSuspended; - } - - @Override - public boolean isBusy() { - if (sExecutionService == null) return false; - return sExecutionService.isConnected() || sExecutionService.isConnecting(); - } - - // Pump interface - @Override - public PumpEnactResult setNewBasalProfile(Profile profile) { - PumpEnactResult result = new PumpEnactResult(); - - if (sExecutionService == null) { - log.error("setNewBasalProfile sExecutionService is null"); - result.comment = "setNewBasalProfile sExecutionService is null"; - return result; - } - if (!isInitialized()) { - log.error("setNewBasalProfile not initialized"); - Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - } - if (!sExecutionService.updateBasalsInPump(profile)) { - Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - result.success = true; - result.enacted = true; - result.comment = "OK"; - return result; - } - } - - @Override - public boolean isThisProfileSet(Profile profile) { - if (!isInitialized()) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - if (pump.pumpProfiles == null) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - int basalValues = pump.basal48Enable ? 48 : 24; - int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; - for (int h = 0; h < basalValues; h++) { - Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; - Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); - if (profileValue == null) return true; - if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { - log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); - return false; - } - } - return true; - } - - @Override - public Date lastDataTime() { - return pump.lastConnection; - } - - @Override - public double getBaseBasalRate() { - return pump.currentBasal; + return pump.lastConnection > 0 && pump.maxBasal > 0 && !pump.isConfigUD && !pump.isEasyModeEnabled && pump.isExtendedBolusEnabled; } @Override @@ -316,7 +136,7 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { Treatment t = new Treatment(); boolean connectionOK = false; - if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, t); + if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) detailedBolusInfo.carbs, detailedBolusInfo.carbTime, t); PumpEnactResult result = new PumpEnactResult(); result.success = connectionOK; result.bolusDelivered = t.insulin; @@ -339,15 +159,6 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf } } - @Override - public void stopBolusDelivering() { - if (sExecutionService == null) { - log.error("stopBolusDelivering sExecutionService is null"); - return; - } - sExecutionService.bolusStop(); - } - // This is called from APS @Override public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean enforceNew) { @@ -500,100 +311,6 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf return result; } - @Override - public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, boolean enforceNew) { - PumpEnactResult result = new PumpEnactResult(); - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - percent = configBuilderPlugin.applyBasalConstraints(percent); - if (percent < 0) { - result.isTempCancel = false; - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_invalidinput); - log.error("setTempBasalPercent: Invalid input"); - return result; - } - if (percent > getPumpDescription().maxTempPercent) - percent = getPumpDescription().maxTempPercent; - TemporaryBasal runningTB = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); - if (runningTB != null && runningTB.percentRate == percent && enforceNew) { - result.enacted = false; - result.success = true; - result.isTempCancel = false; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.duration = pump.tempBasalRemainingMin; - result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); - result.isPercent = true; - if (Config.logPumpActions) - log.debug("setTempBasalPercent: Correct value already set"); - return result; - } - int durationInHours = Math.max(durationInMinutes / 60, 1); - boolean connectionOK = sExecutionService.tempBasal(percent, durationInHours); - if (connectionOK && pump.isTempBasalInProgress && pump.tempBasalPercent == percent) { - result.enacted = true; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.isTempCancel = false; - result.duration = pump.tempBasalRemainingMin; - result.percent = pump.tempBasalPercent; - result.absolute = MainApp.getConfigBuilder().getTempBasalAbsoluteRateHistory(); - result.isPercent = true; - if (Config.logPumpActions) - log.debug("setTempBasalPercent: OK"); - return result; - } - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("setTempBasalPercent: Failed to set temp basal"); - return result; - } - - @Override - public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - insulin = configBuilderPlugin.applyBolusConstraints(insulin); - // needs to be rounded - int durationInHalfHours = Math.max(durationInMinutes / 30, 1); - insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); - - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = false; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.isPercent = false; - result.isTempCancel = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: Correct extended bolus already set. Current: " + pump.extendedBolusAmount + " Asked: " + insulin); - return result; - } - boolean connectionOK = sExecutionService.extendedBolus(insulin, durationInHalfHours); - if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAmount - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = true; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.isTempCancel = false; - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.bolusDelivered = pump.extendedBolusAmount; - result.isPercent = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: OK"); - return result; - } - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("setExtendedBolus: Failed to extended bolus"); - return result; - } - @Override public PumpEnactResult cancelTempBasal(boolean force) { if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) @@ -634,257 +351,8 @@ public class DanaRKoreanPlugin implements PluginBase, PumpInterface, DanaRInterf } } - @Override - public PumpEnactResult cancelExtendedBolus() { - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null) { - sExecutionService.extendedBolusStop(); - result.enacted = true; - result.isTempCancel = true; - } - if (!pump.isExtendedInProgress) { - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - if (Config.logPumpActions) - log.debug("cancelExtendedBolus: OK"); - return result; - } else { - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("cancelExtendedBolus: Failed to cancel extended bolus"); - return result; - } - } - - @Override - public void connect(String from) { - if (sExecutionService != null) { - sExecutionService.connect(from); - pumpDescription.basalStep = pump.basalStep; - pumpDescription.bolusStep = pump.bolusStep; - } - } - - @Override - public boolean isConnected() { - return sExecutionService != null && sExecutionService.isConnected(); - } - - @Override - public boolean isConnecting() { - return sExecutionService != null && sExecutionService.isConnecting(); - } - - @Override - public void disconnect(String from) { - if (sExecutionService != null) sExecutionService.disconnect(from); - } - - @Override - public void stopConnecting() { - if (sExecutionService != null) sExecutionService.stopConnecting(); - } - - @Override - public void getPumpStatus() { - if (sExecutionService != null) sExecutionService.getPumpStatus(); - } - - @Override - public JSONObject getJSONStatus() { - if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { - return null; - } - JSONObject pumpjson = new JSONObject(); - JSONObject battery = new JSONObject(); - JSONObject status = new JSONObject(); - JSONObject extended = new JSONObject(); - try { - battery.put("percent", pump.batteryRemaining); - status.put("status", pump.pumpSuspended ? "suspended" : "normal"); - status.put("timestamp", DateUtil.toISOString(pump.lastConnection)); - extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); - extended.put("PumpIOB", pump.iob); - if (pump.lastBolusTime.getTime() != 0) { - extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); - extended.put("LastBolusAmount", pump.lastBolusAmount); - } - TemporaryBasal tb = MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()); - if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); - extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); - extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); - } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (eb != null) { - extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); - extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); - extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); - } - extended.put("BaseBasalRate", getBaseBasalRate()); - try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); - } catch (Exception e) { - } - - pumpjson.put("battery", battery); - pumpjson.put("status", status); - pumpjson.put("extended", extended); - pumpjson.put("reservoir", (int) pump.reservoirRemainingUnits); - pumpjson.put("clock", DateUtil.toISOString(new Date())); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return pumpjson; - } - - @Override - public String deviceID() { - return pump.serialNumber; - } - - @Override - public PumpDescription getPumpDescription() { - return pumpDescription; - } - - /** - * DanaR interface - */ - - @Override - public PumpEnactResult loadHistory(byte type) { - return sExecutionService.loadHistory(type); - } - @Override public PumpEnactResult loadEvents() { return null; // no history, not needed } - - /** - * Constraint interface - */ - - @Override - public boolean isLoopEnabled() { - return true; - } - - @Override - public boolean isClosedModeEnabled() { - return true; - } - - @Override - public boolean isAutosensModeEnabled() { - return true; - } - - @Override - public boolean isAMAModeEnabled() { - return true; - } - - @Override - public boolean isSMBModeEnabled() { - return true; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBasalConstraints(Double absoluteRate) { - double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } - } - return absoluteRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - if (percentRate < 0) percentRate = 0; - if (percentRate > getPumpDescription().maxTempPercent) - percentRate = getPumpDescription().maxTempPercent; - if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); - return percentRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBolusConstraints(Double insulin) { - double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } - } - return insulin; - } - - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - return maxIob; - } - - @Nullable - @Override - public ProfileStore getProfile() { - if (pump.lastSettingsRead.getTime() == 0) - return null; // no info now - return pump.createConvertedProfile(); - } - - @Override - public String getUnits() { - return pump.getUnits(); - } - - @Override - public String getProfileName() { - return pump.createConvertedProfileName(); - } - - // Reply for sms communicator - public String shortStatus(boolean veryShort) { - String ret = ""; - if (pump.lastConnection.getTime() != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection.getTime(); - int agoMin = (int) (agoMsec / 60d / 1000d); - ret += "LastConn: " + agoMin + " minago\n"; - } - if (pump.lastBolusTime.getTime() != 0) { - ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - ret += "Temp: " + MainApp.getConfigBuilder().getRealTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; - } - 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; - } - // TODO: daily total constraint - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/SerialIOThread.java index 8672afdcb0..9a8f3edb05 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/SerialIOThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/SerialIOThread.java @@ -13,13 +13,14 @@ import java.io.OutputStream; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractSerialIOThread; import info.nightscout.androidaps.plugins.PumpDanaRKorean.comm.MessageHashTable_k; import info.nightscout.utils.CRC; /** * Created by mike on 17.07.2016. */ -public class SerialIOThread extends Thread { +public class SerialIOThread extends AbstractSerialIOThread { private static Logger log = LoggerFactory.getLogger(SerialIOThread.class); private InputStream mInputStream = null; @@ -29,10 +30,10 @@ public class SerialIOThread extends Thread { private boolean mKeepRunning = true; private byte[] mReadBuff = new byte[0]; - MessageBase processedMessage; + private MessageBase processedMessage; public SerialIOThread(BluetoothSocket rfcommSocket) { - super(SerialIOThread.class.toString()); + super(); mRfCommSocket = rfcommSocket; try { @@ -138,6 +139,7 @@ public class SerialIOThread extends Thread { } } + @Override public synchronized void sendMessage(MessageBase message) { if (!mRfCommSocket.isConnected()) { log.error("Socket not connected on sendMessage"); @@ -173,6 +175,7 @@ public class SerialIOThread extends Thread { } } + @Override public void disconnect(String reason) { mKeepRunning = false; try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java index 641dce79f3..3cf47b656f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgInitConnStatusTime_k.java @@ -37,7 +37,7 @@ public class MsgInitConnStatusTime_k extends MessageBase { MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, false); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, true); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, true); - DanaRPump.getInstance().lastConnection = new Date(0); // mark not initialized + DanaRPump.getInstance().lastConnection = 0; // mark not initialized //If profile coming from pump, switch it as well if (MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isEnabled(PluginBase.PROFILE)) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgSettingBasal_k.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgSettingBasal_k.java index 72a8be4e42..5c8b40b959 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgSettingBasal_k.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/comm/MsgSettingBasal_k.java @@ -24,7 +24,7 @@ public class MsgSettingBasal_k extends MessageBase { pump.pumpProfiles[pump.activeProfile] = new double[24]; for (int index = 0; index < 24; index++) { int basal = intFromBuff(bytes, 2 * index, 2); - if (basal < DanaRKoreanPlugin.pumpDescription.basalMinimumRate) basal = 0; + if (basal < DanaRKoreanPlugin.getPlugin().pumpDescription.basalMinimumRate) basal = 0; pump.pumpProfiles[pump.activeProfile][index] = basal / 100d; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java index 4efff4a5ad..6d1ccc132d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRKorean/services/DanaRKoreanExecutionService.java @@ -1,58 +1,36 @@ package info.nightscout.androidaps.plugins.PumpDanaRKorean.services; -import android.app.Service; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; -import android.os.IBinder; import android.os.SystemClock; import com.squareup.otto.Subscribe; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; -import java.util.Set; -import java.util.UUID; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusProgress; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStart; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStop; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryAlarm; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBasalHour; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryBolus; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryCarbo; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDailyInsulin; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryDone; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryError; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryGlucose; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistoryRefill; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgHistorySuspend; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStart; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgPCCommStop; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetCarbsEntry; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetExtendedBolusStart; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetExtendedBolusStop; @@ -68,56 +46,23 @@ import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingPumpTime; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingShippingInfo; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusBolusExtended; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusTempBasal; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.RecordTypes; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractDanaRExecutionService; import info.nightscout.androidaps.plugins.PumpDanaRKorean.DanaRKoreanPlugin; import info.nightscout.androidaps.plugins.PumpDanaRKorean.SerialIOThread; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.PumpDanaRKorean.comm.MsgCheckValue_k; import info.nightscout.androidaps.plugins.PumpDanaRKorean.comm.MsgSettingBasal_k; import info.nightscout.androidaps.plugins.PumpDanaRKorean.comm.MsgStatusBasic_k; -import info.nightscout.androidaps.queue.Callback; import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; import info.nightscout.utils.ToastUtils; -public class DanaRKoreanExecutionService extends Service { - private static Logger log = LoggerFactory.getLogger(DanaRKoreanExecutionService.class); - - private String devName; - - private SerialIOThread mSerialIOThread; - private BluetoothSocket mRfcommSocket; - private BluetoothDevice mBTDevice; - - private IBinder mBinder = new LocalBinder(); - - private DanaRPump danaRPump = DanaRPump.getInstance(); - private Treatment bolusingTreatment = null; - - private static Boolean connectionInProgress = false; - - private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); - - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - String action = intent.getAction(); - if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { - log.debug("Device was disconnected " + device.getName());//Device was disconnected - if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { - if (mSerialIOThread != null) { - mSerialIOThread.disconnect("BT disconnection broadcast"); - } - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); - } - } - } - }; +public class DanaRKoreanExecutionService extends AbstractDanaRExecutionService { public DanaRKoreanExecutionService() { + log = LoggerFactory.getLogger(DanaRKoreanExecutionService.class); + mBinder = new LocalBinder(); + registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); } @@ -128,17 +73,6 @@ public class DanaRKoreanExecutionService extends Service { } } - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - return START_STICKY; - } - private void registerBus() { try { MainApp.bus().unregister(this); @@ -163,35 +97,28 @@ public class DanaRKoreanExecutionService extends Service { log.debug("EventAppExit finished"); } - public boolean isConnected() { - return mRfcommSocket != null && mRfcommSocket.isConnected(); - } - - public boolean isConnecting() { - return connectionInProgress; - } - - public void disconnect(String from) { + @Subscribe + public void onStatusEvent(final EventPreferenceChange pch) { if (mSerialIOThread != null) - mSerialIOThread.disconnect(from); + mSerialIOThread.disconnect("EventPreferenceChange"); } - public void connect(String from) { - if (danaRPump.password != -1 && danaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { + public void connect() { + if (mDanaRPump.password != -1 && mDanaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumppassword), R.raw.error); return; } - if (connectionInProgress) + if (mConnectionInProgress) return; new Thread(new Runnable() { @Override public void run() { - connectionInProgress = true; + mConnectionInProgress = true; getBTSocketForSelectedPump(); if (mRfcommSocket == null || mBTDevice == null) { - connectionInProgress = false; + mConnectionInProgress = false; return; // Device not found } @@ -212,48 +139,11 @@ public class DanaRKoreanExecutionService extends Service { MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED, 0)); } - connectionInProgress = false; + mConnectionInProgress = false; } }).start(); } - public void stopConnecting() { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("stopConnecting"); - } - - private void getBTSocketForSelectedPump() { - devName = SP.getString(MainApp.sResources.getString(R.string.key_danar_bt_name), ""); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter != null) { - Set bondedDevices = bluetoothAdapter.getBondedDevices(); - - for (BluetoothDevice device : bondedDevices) { - if (devName.equals(device.getName())) { - mBTDevice = device; - try { - mRfcommSocket = mBTDevice.createRfcommSocketToServiceRecord(SPP_UUID); - } catch (IOException e) { - log.error("Error creating socket: ", e); - } - break; - } - } - } else { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.nobtadapter)); - } - if (mBTDevice == null) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.devicenotfound)); - } - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - } - public void getPumpStatus() { try { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpstatus))); @@ -263,7 +153,7 @@ public class DanaRKoreanExecutionService extends Service { MsgStatusBolusExtended exStatusMsg = new MsgStatusBolusExtended(); MsgCheckValue_k checkValue = new MsgCheckValue_k(); - if (danaRPump.isNewPump) { + if (mDanaRPump.isNewPump) { mSerialIOThread.sendMessage(checkValue); if (!checkValue.received) { return; @@ -278,8 +168,8 @@ public class DanaRKoreanExecutionService extends Service { mSerialIOThread.sendMessage(exStatusMsg); MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingbolusstatus))); - Date now = new Date(); - if (danaRPump.lastSettingsRead.getTime() + 60 * 60 * 1000L < now.getTime() || !MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isInitialized()) { + long now = System.currentTimeMillis(); + if (mDanaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).isInitialized()) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingMeal()); @@ -290,39 +180,38 @@ public class DanaRKoreanExecutionService extends Service { mSerialIOThread.sendMessage(new MsgSettingProfileRatios()); MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - long timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + long timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); if (Math.abs(timeDiff) > 10) { mSerialIOThread.sendMessage(new MsgSetTime(new Date())); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); } - danaRPump.lastSettingsRead = now; + mDanaRPump.lastSettingsRead = now; } - danaRPump.lastConnection = now; + mDanaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); - if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { - log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); + if (mDanaRPump.dailyTotalUnits > mDanaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { + log.debug("Approaching daily limit: " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits); Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.sResources.getString(R.string.approachingdailylimit), Notification.URGENT); MainApp.bus().post(new EventNewNotification(reportFail)); - NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); + NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits + "U"); } } catch (Exception e) { log.error("Unhandled exception", e); } - return; } public boolean tempBasal(int percent, int durationInHours) { if (!isConnected()) return false; - if (danaRPump.isTempBasalInProgress) { + if (mDanaRPump.isTempBasalInProgress) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); - waitMsec(500); + SystemClock.sleep(500); } MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); @@ -358,16 +247,21 @@ public class DanaRKoreanExecutionService extends Service { return true; } - public boolean bolus(double amount, int carbs, final Treatment t) { + @Override + public PumpEnactResult loadEvents() { + return null; + } + + public boolean bolus(double amount, int carbs, long carbtime, final Treatment t) { if (!isConnected()) return false; if (BolusProgressDialog.stopPressed) return false; - bolusingTreatment = t; + mBolusingTreatment = t; MsgBolusStart start = new MsgBolusStart(amount); MsgBolusStop stop = new MsgBolusStop(amount, t); if (carbs > 0) { - mSerialIOThread.sendMessage(new MsgSetCarbsEntry(System.currentTimeMillis(), carbs)); + mSerialIOThread.sendMessage(new MsgSetCarbsEntry(carbtime, carbs)); } MsgBolusProgress progress = new MsgBolusProgress(amount, t); // initialize static variables @@ -380,37 +274,21 @@ public class DanaRKoreanExecutionService extends Service { return false; } while (!stop.stopped && !start.failed) { - waitMsec(100); + SystemClock.sleep(100); if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm stop.stopped = true; stop.forced = true; log.debug("Communication stopped"); } } - waitMsec(300); + SystemClock.sleep(300); - bolusingTreatment = null; + mBolusingTreatment = null; ConfigBuilderPlugin.getCommandQueue().readStatus("bolusOK", null); return true; } - public void bolusStop() { - if (Config.logDanaBTComm) - log.debug("bolusStop >>>>> @ " + (bolusingTreatment == null ? "" : bolusingTreatment.insulin)); - MsgBolusStop stop = new MsgBolusStop(); - stop.forced = true; - if (isConnected()) { - mSerialIOThread.sendMessage(stop); - while (!stop.stopped) { - mSerialIOThread.sendMessage(stop); - waitMsec(200); - } - } else { - stop.stopped = true; - } - } - public boolean carbsEntry(int amount) { if (!isConnected()) return false; MsgSetCarbsEntry msg = new MsgSetCarbsEntry(System.currentTimeMillis(), amount); @@ -418,51 +296,9 @@ public class DanaRKoreanExecutionService extends Service { return true; } - public PumpEnactResult loadHistory(byte type) { - PumpEnactResult result = new PumpEnactResult(); - if (!isConnected()) return result; - MessageBase msg = null; - switch (type) { - case RecordTypes.RECORD_TYPE_ALARM: - msg = new MsgHistoryAlarm(); - break; - case RecordTypes.RECORD_TYPE_BASALHOUR: - msg = new MsgHistoryBasalHour(); - break; - case RecordTypes.RECORD_TYPE_BOLUS: - msg = new MsgHistoryBolus(); - break; - case RecordTypes.RECORD_TYPE_CARBO: - msg = new MsgHistoryCarbo(); - break; - case RecordTypes.RECORD_TYPE_DAILY: - msg = new MsgHistoryDailyInsulin(); - break; - case RecordTypes.RECORD_TYPE_ERROR: - msg = new MsgHistoryError(); - break; - case RecordTypes.RECORD_TYPE_GLUCOSE: - msg = new MsgHistoryGlucose(); - break; - case RecordTypes.RECORD_TYPE_REFILL: - msg = new MsgHistoryRefill(); - break; - case RecordTypes.RECORD_TYPE_SUSPEND: - msg = new MsgHistorySuspend(); - break; - } - MsgHistoryDone done = new MsgHistoryDone(); - mSerialIOThread.sendMessage(new MsgPCCommStart()); - waitMsec(400); - mSerialIOThread.sendMessage(msg); - while (!done.received && mRfcommSocket.isConnected()) { - waitMsec(100); - } - waitMsec(200); - mSerialIOThread.sendMessage(new MsgPCCommStop()); - result.success = true; - result.comment = "OK"; - return result; + @Override + public boolean highTempBasal(int percent) { + return false; } public boolean updateBasalsInPump(final Profile profile) { @@ -471,13 +307,10 @@ public class DanaRKoreanExecutionService extends Service { double[] basal = DanaRPump.buildDanaRProfileRecord(profile); MsgSetSingleBasalProfile msgSet = new MsgSetSingleBasalProfile(basal); mSerialIOThread.sendMessage(msgSet); - danaRPump.lastSettingsRead = new Date(0); // force read full settings + mDanaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } - private void waitMsec(long msecs) { - SystemClock.sleep(msecs); - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java index ed9f98651f..9978880e08 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/DanaRSPlugin.java @@ -189,6 +189,8 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, pumpDescription.basalMinimumRate = 0.04d; pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = true; } private ServiceConnection mConnection = new ServiceConnection() { @@ -346,7 +348,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Nullable @Override public ProfileStore getProfile() { - if (pump.lastSettingsRead.getTime() == 0) + if (pump.lastSettingsRead == 0) return null; // no info now return pump.createConvertedProfile(); } @@ -365,7 +367,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public boolean isInitialized() { - return pump.lastConnection.getTime() > 0 && pump.maxBasal > 0; + return pump.lastConnection > 0 && pump.maxBasal > 0; } @Override @@ -436,7 +438,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public Date lastDataTime() { - return pump.lastConnection; + return new Date(pump.lastConnection); } @Override @@ -477,7 +479,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, Treatment t = new Treatment(); boolean connectionOK = false; if (detailedBolusInfo.insulin > 0 || carbs > 0) - connectionOK = danaRSService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 1000, t); // +1000 to make the record different + connectionOK = danaRSService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 30000, t); // +30s to make the record different PumpEnactResult result = new PumpEnactResult(); result.success = connectionOK; result.bolusDelivered = t.insulin; @@ -751,7 +753,7 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public JSONObject getJSONStatus() { - if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { + if (pump.lastConnection + 5 * 60 * 1000L < System.currentTimeMillis()) { return null; } JSONObject pumpjson = new JSONObject(); @@ -810,8 +812,8 @@ public class DanaRSPlugin implements PluginBase, PumpInterface, DanaRInterface, @Override public String shortStatus(boolean veryShort) { String ret = ""; - if (pump.lastConnection.getTime() != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection.getTime(); + if (pump.lastConnection != 0) { + Long agoMsec = System.currentTimeMillis() - pump.lastConnection; int agoMin = (int) (agoMsec / 60d / 1000d); ret += "LastConn: " + agoMin + " minago\n"; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Set_Event_History.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Set_Event_History.java index 4e559a77df..84ead83665 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Set_Event_History.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/comm/DanaRS_Packet_APS_Set_Event_History.java @@ -9,6 +9,7 @@ import java.util.Calendar; import java.util.GregorianCalendar; import info.nightscout.androidaps.Config; +import info.nightscout.utils.DateUtil; public class DanaRS_Packet_APS_Set_Event_History extends DanaRS_Packet { private static Logger log = LoggerFactory.getLogger(DanaRS_Packet_APS_Set_Event_History.class); @@ -30,6 +31,8 @@ public class DanaRS_Packet_APS_Set_Event_History extends DanaRS_Packet { this.time = time; this.param1 = param1; this.param2 = param2; + if (Config.logDanaMessageDetail) + log.debug("Set history entry: " + DateUtil.dateAndTimeString(time) + " type: " + type + " param1: " + param1 + " param2: " + param2); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java index f8d18a4d22..2901c4b863 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/BLEComm.java @@ -164,6 +164,8 @@ public class BLEComm { scheduledDisconnection = null; if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { + log.debug("disconnect not possible: (mBluetoothAdapter == null) " + (mBluetoothAdapter == null)); + log.debug("disconnect not possible: (mBluetoothGatt == null) " + (mBluetoothGatt == null)); return; } setCharacteristicNotification(getUARTReadBTGattChar(), false); @@ -257,6 +259,8 @@ public class BLEComm { log.debug("setCharacteristicNotification"); if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { log.debug("BluetoothAdapter not initialized_ERROR"); + isConnecting = false; + isConnected = false; return; } mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); @@ -266,20 +270,25 @@ public class BLEComm { log.debug("readCharacteristic"); if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { log.debug("BluetoothAdapter not initialized_ERROR"); + isConnecting = false; + isConnected = false; return; } mBluetoothGatt.readCharacteristic(characteristic); } public void writeCharacteristic_NO_RESPONSE(final BluetoothGattCharacteristic characteristic, final byte[] data) { - if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { - log.debug("BluetoothAdapter not initialized_ERROR"); - return; - } - new Thread(new Runnable() { public void run() { SystemClock.sleep(WRITE_DELAY_MILLIS); + + if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { + log.debug("BluetoothAdapter not initialized_ERROR"); + isConnecting = false; + isConnected = false; + return; + } + characteristic.setValue(data); characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); log.debug("writeCharacteristic:" + DanaRS_Packet.toHexString(data)); @@ -306,6 +315,8 @@ public class BLEComm { log.debug("getSupportedGattServices"); if ((mBluetoothAdapter == null) || (mBluetoothGatt == null)) { log.debug("BluetoothAdapter not initialized_ERROR"); + isConnecting = false; + isConnected = false; return null; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java index 7790257640..1a1f74b645 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRS/services/DanaRSService.java @@ -131,8 +131,8 @@ public class DanaRSService extends Service { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingtempbasalstatus))); bleComm.sendMessage(new DanaRS_Packet_Basal_Get_Temporary_Basal_State()); - Date now = new Date(); - if (danaRPump.lastSettingsRead.getTime() + 60 * 60 * 1000L < now.getTime() || !MainApp.getSpecificPlugin(DanaRSPlugin.class).isInitialized()) { + long now = System.currentTimeMillis(); + if (danaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !MainApp.getSpecificPlugin(DanaRSPlugin.class).isInitialized()) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpsettings))); bleComm.sendMessage(new DanaRS_Packet_General_Get_Shipping_Information()); // serial no bleComm.sendMessage(new DanaRS_Packet_General_Get_Pump_Check()); // firmware @@ -357,7 +357,7 @@ public class DanaRSService extends Service { bleComm.sendMessage(msgSet); DanaRS_Packet_Basal_Set_Profile_Number msgActivate = new DanaRS_Packet_Basal_Set_Profile_Number(0); bleComm.sendMessage(msgActivate); - danaRPump.lastSettingsRead = new Date(0); // force read full settings + danaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java index b4eb969852..34a5b2381b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/DanaRv2Plugin.java @@ -5,68 +5,31 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; -import android.support.annotation.Nullable; import com.squareup.otto.Subscribe; -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Date; -import java.util.Objects; - -import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; -import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.DetailedBolusInfo; -import info.nightscout.androidaps.data.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; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.interfaces.ConstraintsInterface; -import info.nightscout.androidaps.interfaces.DanaRInterface; -import info.nightscout.androidaps.interfaces.PluginBase; -import info.nightscout.androidaps.interfaces.ProfileInterface; import info.nightscout.androidaps.interfaces.PumpDescription; -import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConfigBuilder.DetailedBolusInfoStorage; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; -import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.ProfileNS.NSProfilePlugin; -import info.nightscout.androidaps.plugins.PumpDanaR.DanaRFragment; -import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; +import info.nightscout.androidaps.plugins.PumpDanaR.AbstractDanaRPlugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.services.DanaRv2ExecutionService; -import info.nightscout.utils.DateUtil; -import info.nightscout.utils.DecimalFormatter; import info.nightscout.utils.Round; import info.nightscout.utils.SP; /** * Created by mike on 05.08.2016. */ -public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, ConstraintsInterface, ProfileInterface { - private static Logger log = LoggerFactory.getLogger(DanaRv2Plugin.class); - - @Override - public String getFragmentClass() { - return DanaRFragment.class.getName(); - } - - private static boolean fragmentPumpEnabled = false; - private static boolean fragmentProfileEnabled = false; - private static boolean fragmentPumpVisible = true; - - private static DanaRv2ExecutionService sExecutionService; - +public class DanaRv2Plugin extends AbstractDanaRPlugin { private static DanaRv2Plugin plugin = null; @@ -76,11 +39,10 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, return plugin; } - private static DanaRPump pump = DanaRPump.getInstance(); - - private static PumpDescription pumpDescription = new PumpDescription(); - private DanaRv2Plugin() { + log = LoggerFactory.getLogger(DanaRv2Plugin.class); + useExtendedBoluses = false; + Context context = MainApp.instance().getApplicationContext(); Intent intent = new Intent(context, DanaRv2ExecutionService.class); context.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); @@ -109,6 +71,8 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, pumpDescription.basalMinimumRate = 0.04d; pumpDescription.isRefillingCapable = true; + + pumpDescription.storesCarbInfo = true; } private ServiceConnection mConnection = new ServiceConnection() { @@ -132,78 +96,11 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, } // Plugin base interface - @Override - public int getType() { - return PluginBase.PUMP; - } - @Override public String getName() { return MainApp.instance().getString(R.string.danarv2pump); } - @Override - public String getNameShort() { - String name = MainApp.sResources.getString(R.string.danarpump_shortname); - if (!name.trim().isEmpty()) { - //only if translation exists - return name; - } - // use long name as fallback - return getName(); - } - - @Override - public boolean isEnabled(int type) { - if (type == PluginBase.PROFILE) return fragmentProfileEnabled && fragmentPumpEnabled; - else if (type == PluginBase.PUMP) return fragmentPumpEnabled; - else if (type == PluginBase.CONSTRAINTS) return fragmentPumpEnabled; - return false; - } - - @Override - public boolean isVisibleInTabs(int type) { - if (type == PluginBase.PROFILE || type == PluginBase.CONSTRAINTS) return false; - else if (type == PluginBase.PUMP) return fragmentPumpVisible; - return false; - } - - @Override - public boolean canBeHidden(int type) { - return true; - } - - @Override - public boolean hasFragment() { - return true; - } - - @Override - public boolean showInList(int type) { - return type == PUMP; - } - - @Override - public void setFragmentEnabled(int type, boolean fragmentEnabled) { - if (type == PluginBase.PROFILE) - fragmentProfileEnabled = fragmentEnabled; - else if (type == PluginBase.PUMP) - fragmentPumpEnabled = fragmentEnabled; - // if pump profile was enabled need to switch to another too - if (type == PluginBase.PUMP && !fragmentEnabled && fragmentProfileEnabled) { - setFragmentEnabled(PluginBase.PROFILE, false); - setFragmentVisible(PluginBase.PROFILE, false); - NSProfilePlugin.getPlugin().setFragmentEnabled(PluginBase.PROFILE, true); - NSProfilePlugin.getPlugin().setFragmentVisible(PluginBase.PROFILE, true); - } - } - - @Override - public void setFragmentVisible(int type, boolean fragmentVisible) { - if (type == PluginBase.PUMP) - fragmentPumpVisible = fragmentVisible; - } - @Override public int getPreferencesId() { return R.xml.pref_danarv2; @@ -216,86 +113,10 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, @Override public boolean isInitialized() { - return pump.lastConnection.getTime() > 0 && pump.maxBasal > 0; - } - - @Override - public boolean isSuspended() { - return pump.pumpSuspended; - } - - @Override - public boolean isBusy() { - if (sExecutionService == null) return false; - return sExecutionService.isConnected() || sExecutionService.isConnecting(); + return pump.lastConnection > 0 && pump.maxBasal > 0; } // Pump interface - @Override - public PumpEnactResult setNewBasalProfile(Profile profile) { - PumpEnactResult result = new PumpEnactResult(); - - if (sExecutionService == null) { - log.error("setNewBasalProfile sExecutionService is null"); - result.comment = "setNewBasalProfile sExecutionService is null"; - return result; - } - if (!isInitialized()) { - log.error("setNewBasalProfile not initialized"); - Notification notification = new Notification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED, MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.pumpNotInitializedProfileNotSet); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - } - if (!sExecutionService.updateBasalsInPump(profile)) { - Notification notification = new Notification(Notification.FAILED_UDPATE_PROFILE, MainApp.sResources.getString(R.string.failedupdatebasalprofile), Notification.URGENT); - MainApp.bus().post(new EventNewNotification(notification)); - result.comment = MainApp.sResources.getString(R.string.failedupdatebasalprofile); - return result; - } else { - MainApp.bus().post(new EventDismissNotification(Notification.PROFILE_NOT_SET_NOT_INITIALIZED)); - MainApp.bus().post(new EventDismissNotification(Notification.FAILED_UDPATE_PROFILE)); - Notification notification = new Notification(Notification.PROFILE_SET_OK, MainApp.sResources.getString(R.string.profile_set_ok), Notification.INFO, 60); - MainApp.bus().post(new EventNewNotification(notification)); - result.success = true; - result.enacted = true; - result.comment = "OK"; - return result; - } - } - - @Override - public boolean isThisProfileSet(Profile profile) { - if (!isInitialized()) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - if (pump.pumpProfiles == null) - return true; // TODO: not sure what's better. so far TRUE to prevent too many SMS - int basalValues = pump.basal48Enable ? 48 : 24; - int basalIncrement = pump.basal48Enable ? 30 * 60 : 60 * 60; - for (int h = 0; h < basalValues; h++) { - Double pumpValue = pump.pumpProfiles[pump.activeProfile][h]; - Double profileValue = profile.getBasal((Integer) (h * basalIncrement)); - if (profileValue == null) return true; - if (Math.abs(pumpValue - profileValue) > getPumpDescription().basalStep) { - log.debug("Diff found. Hour: " + h + " Pump: " + pumpValue + " Profile: " + profileValue); - return false; - } - } - return true; - } - - @Override - public Date lastDataTime() { - return pump.lastConnection; - } - - @Override - public double getBaseBasalRate() { - return pump.currentBasal; - } - @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); @@ -329,7 +150,7 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, Treatment t = new Treatment(); boolean connectionOK = false; if (detailedBolusInfo.insulin > 0 || carbs > 0) - connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 1000, t); // +1000 to make the record different + connectionOK = sExecutionService.bolus(detailedBolusInfo.insulin, (int) carbs, System.currentTimeMillis() + carbTime * 60 * 1000 + 30000, t); // +30s to make the record different PumpEnactResult result = new PumpEnactResult(); result.success = connectionOK; result.bolusDelivered = t.insulin; @@ -511,49 +332,6 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, return result; } - @Override - public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { - ConfigBuilderPlugin configBuilderPlugin = MainApp.getConfigBuilder(); - insulin = configBuilderPlugin.applyBolusConstraints(insulin); - // needs to be rounded - int durationInHalfHours = Math.max(durationInMinutes / 30, 1); - insulin = Round.roundTo(insulin, getPumpDescription().extendedBolusStep); - - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null && Math.abs(runningEB.insulin - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = false; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.isPercent = false; - result.isTempCancel = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: Correct extended bolus already set. Current: " + pump.extendedBolusAmount + " Asked: " + insulin); - return result; - } - boolean connectionOK = sExecutionService.extendedBolus(insulin, durationInHalfHours); - if (connectionOK && pump.isExtendedInProgress && Math.abs(pump.extendedBolusAmount - insulin) < getPumpDescription().extendedBolusStep) { - result.enacted = true; - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - result.isTempCancel = false; - result.duration = pump.extendedBolusRemainingMinutes; - result.absolute = pump.extendedBolusAbsoluteRate; - result.bolusDelivered = pump.extendedBolusAmount; - result.isPercent = false; - if (Config.logPumpActions) - log.debug("setExtendedBolus: OK"); - return result; - } - result.enacted = false; - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("setExtendedBolus: Failed to extended bolus"); - return result; - } - @Override public PumpEnactResult cancelTempBasal(boolean force) { PumpEnactResult result = new PumpEnactResult(); @@ -579,257 +357,8 @@ public class DanaRv2Plugin implements PluginBase, PumpInterface, DanaRInterface, } } - @Override - public PumpEnactResult cancelExtendedBolus() { - PumpEnactResult result = new PumpEnactResult(); - ExtendedBolus runningEB = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (runningEB != null) { - sExecutionService.extendedBolusStop(); - result.enacted = true; - result.isTempCancel = true; - } - if (!pump.isExtendedInProgress) { - result.success = true; - result.comment = MainApp.instance().getString(R.string.virtualpump_resultok); - if (Config.logPumpActions) - log.debug("cancelExtendedBolus: OK"); - return result; - } else { - result.success = false; - result.comment = MainApp.instance().getString(R.string.danar_valuenotsetproperly); - log.error("cancelExtendedBolus: Failed to cancel extended bolus"); - return result; - } - } - - @Override - public void connect(String from) { - if (sExecutionService != null) { - sExecutionService.connect(from); - pumpDescription.basalStep = pump.basalStep; - pumpDescription.bolusStep = pump.bolusStep; - } - } - - @Override - public boolean isConnected() { - return sExecutionService != null && sExecutionService.isConnected(); - } - - @Override - public boolean isConnecting() { - return sExecutionService != null && sExecutionService.isConnecting(); - } - - @Override - public void disconnect(String from) { - if (sExecutionService != null) sExecutionService.disconnect(from); - } - - @Override - public void stopConnecting() { - if (sExecutionService != null) sExecutionService.stopConnecting(); - } - - @Override - public void getPumpStatus() { - if (sExecutionService != null) sExecutionService.getPumpStatus(); - } - - @Override - public JSONObject getJSONStatus() { - if (pump.lastConnection.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { - return null; - } - JSONObject pumpjson = new JSONObject(); - JSONObject battery = new JSONObject(); - JSONObject status = new JSONObject(); - JSONObject extended = new JSONObject(); - try { - battery.put("percent", pump.batteryRemaining); - status.put("status", pump.pumpSuspended ? "suspended" : "normal"); - status.put("timestamp", DateUtil.toISOString(pump.lastConnection)); - extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); - extended.put("PumpIOB", pump.iob); - if (pump.lastBolusTime.getTime() != 0) { - extended.put("LastBolus", pump.lastBolusTime.toLocaleString()); - extended.put("LastBolusAmount", pump.lastBolusAmount); - } - TemporaryBasal tb = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()); - if (tb != null) { - extended.put("TempBasalAbsoluteRate", tb.tempBasalConvertedToAbsolute(System.currentTimeMillis())); - extended.put("TempBasalStart", DateUtil.dateAndTimeString(tb.date)); - extended.put("TempBasalRemaining", tb.getPlannedRemainingMinutes()); - } - ExtendedBolus eb = MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()); - if (eb != null) { - extended.put("ExtendedBolusAbsoluteRate", eb.absoluteRate()); - extended.put("ExtendedBolusStart", DateUtil.dateAndTimeString(eb.date)); - extended.put("ExtendedBolusRemaining", eb.getPlannedRemainingMinutes()); - } - extended.put("BaseBasalRate", getBaseBasalRate()); - try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); - } catch (Exception e) { - } - - pumpjson.put("battery", battery); - pumpjson.put("status", status); - pumpjson.put("extended", extended); - pumpjson.put("reservoir", (int) pump.reservoirRemainingUnits); - pumpjson.put("clock", DateUtil.toISOString(new Date())); - } catch (JSONException e) { - log.error("Unhandled exception", e); - } - return pumpjson; - } - - @Override - public String deviceID() { - return pump.serialNumber; - } - - @Override - public PumpDescription getPumpDescription() { - return pumpDescription; - } - - /** - * DanaR interface - */ - - @Override - public PumpEnactResult loadHistory(byte type) { - return sExecutionService.loadHistory(type); - } - @Override public PumpEnactResult loadEvents() { return sExecutionService.loadEvents(); } - - /** - * Constraint interface - */ - - @Override - public boolean isLoopEnabled() { - return true; - } - - @Override - public boolean isClosedModeEnabled() { - return true; - } - - @Override - public boolean isAutosensModeEnabled() { - return true; - } - - @Override - public boolean isAMAModeEnabled() { - return true; - } - - @Override - public boolean isSMBModeEnabled() { - return true; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBasalConstraints(Double absoluteRate) { - double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } - } - return absoluteRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Integer applyBasalConstraints(Integer percentRate) { - Integer origPercentRate = percentRate; - if (percentRate < 0) percentRate = 0; - if (percentRate > getPumpDescription().maxTempPercent) - percentRate = getPumpDescription().maxTempPercent; - if (!Objects.equals(percentRate, origPercentRate) && Config.logConstraintsChanges && !Objects.equals(origPercentRate, Constants.basalPercentOnlyForCheckLimit)) - log.debug("Limiting percent rate " + origPercentRate + "% to " + percentRate + "%"); - return percentRate; - } - - @SuppressWarnings("PointlessBooleanExpression") - @Override - public Double applyBolusConstraints(Double insulin) { - double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } - } - return insulin; - } - - @Override - public Integer applyCarbsConstraints(Integer carbs) { - return carbs; - } - - @Override - public Double applyMaxIOBConstraints(Double maxIob) { - return maxIob; - } - - @Nullable - @Override - public ProfileStore getProfile() { - if (pump.lastSettingsRead.getTime() == 0) - return null; // no info now - return pump.createConvertedProfile(); - } - - @Override - public String getUnits() { - return pump.getUnits(); - } - - @Override - public String getProfileName() { - return pump.createConvertedProfileName(); - } - - // Reply for sms communicator - public String shortStatus(boolean veryShort) { - String ret = ""; - if (pump.lastConnection.getTime() != 0) { - Long agoMsec = System.currentTimeMillis() - pump.lastConnection.getTime(); - int agoMin = (int) (agoMsec / 60d / 1000d); - ret += "LastConn: " + agoMin + " minago\n"; - } - if (pump.lastBolusTime.getTime() != 0) { - ret += "LastBolus: " + DecimalFormatter.to2Decimal(pump.lastBolusAmount) + "U @" + android.text.format.DateFormat.format("HH:mm", pump.lastBolusTime) + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryRealTempBasalInProgress()) { - ret += "Temp: " + MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis()).toStringFull() + "\n"; - } - if (MainApp.getConfigBuilder().isInHistoryExtendedBoluslInProgress()) { - ret += "Extended: " + MainApp.getConfigBuilder().getExtendedBolusFromHistory(System.currentTimeMillis()).toString() + "\n"; - } - 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; - } - // TODO: daily total constraint - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/SerialIOThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/SerialIOThread.java index 343a176e56..a12e2085aa 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/SerialIOThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/SerialIOThread.java @@ -13,13 +13,14 @@ import java.io.OutputStream; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractSerialIOThread; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MessageHashTable_v2; import info.nightscout.utils.CRC; /** * Created by mike on 17.07.2016. */ -public class SerialIOThread extends Thread { +public class SerialIOThread extends AbstractSerialIOThread { private static Logger log = LoggerFactory.getLogger(SerialIOThread.class); private InputStream mInputStream = null; @@ -29,10 +30,10 @@ public class SerialIOThread extends Thread { private boolean mKeepRunning = true; private byte[] mReadBuff = new byte[0]; - MessageBase processedMessage; + private MessageBase processedMessage; public SerialIOThread(BluetoothSocket rfcommSocket) { - super(SerialIOThread.class.toString()); + super(); mRfCommSocket = rfcommSocket; try { @@ -138,6 +139,7 @@ public class SerialIOThread extends Thread { } } + @Override public synchronized void sendMessage(MessageBase message) { if (!mRfCommSocket.isConnected()) { log.error("Socket not connected on sendMessage"); @@ -173,6 +175,7 @@ public class SerialIOThread extends Thread { } } + @Override public void disconnect(String reason) { mKeepRunning = false; try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java index 9869ef0895..60133d8d25 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/comm/MsgCheckValue_v2.java @@ -40,6 +40,7 @@ public class MsgCheckValue_v2 extends MessageBase { pump.protocol = intFromBuff(bytes, 1, 1); pump.productCode = intFromBuff(bytes, 2, 1); if (pump.model != DanaRPump.EXPORT_MODEL) { + pump.lastConnection = 0; Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); MainApp.bus().post(new EventNewNotification(notification)); MainApp.getSpecificPlugin(DanaRPlugin.class).disconnect("Wrong Model"); @@ -48,7 +49,7 @@ public class MsgCheckValue_v2 extends MessageBase { MainApp.getSpecificPlugin(DanaRKoreanPlugin.class).setFragmentVisible(PluginBase.PUMP, true); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentEnabled(PluginBase.PUMP, false); MainApp.getSpecificPlugin(DanaRPlugin.class).setFragmentVisible(PluginBase.PUMP, false); - DanaRPump.getInstance().lastConnection = new Date(0); // mark not initialized + DanaRPump.getInstance().lastConnection = 0; // mark not initialized //If profile coming from pump, switch it as well if(MainApp.getSpecificPlugin(DanaRPlugin.class).isEnabled(PluginBase.PROFILE)){ @@ -63,6 +64,7 @@ public class MsgCheckValue_v2 extends MessageBase { } if (pump.protocol != 2) { + pump.lastConnection = 0; Notification notification = new Notification(Notification.WRONG_DRIVER, MainApp.sResources.getString(R.string.pumpdrivercorrected), Notification.NORMAL); MainApp.bus().post(new EventNewNotification(notification)); DanaRKoreanPlugin.getPlugin().disconnect("Wrong Model"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java index 71a2f9ac79..81b5837511 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaRv2/services/DanaRv2ExecutionService.java @@ -1,52 +1,66 @@ package info.nightscout.androidaps.plugins.PumpDanaRv2.services; -import android.app.Service; -import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothSocket; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; -import android.os.IBinder; import android.os.SystemClock; import com.squareup.otto.Subscribe; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; -import java.util.Set; -import java.util.UUID; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Constants; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; +import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventPreferenceChange; import info.nightscout.androidaps.events.EventPumpStatusChanged; -import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; -import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.plugins.PumpDanaR.DanaRPump; -import info.nightscout.androidaps.plugins.PumpDanaR.comm.*; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MessageBase; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusProgress; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStart; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStartWithSpeed; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgBolusStop; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetActivateBasalProfile; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetBasalProfile; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetCarbsEntry; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetExtendedBolusStart; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetExtendedBolusStop; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetTempBasalStart; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetTempBasalStop; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSetTime; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingActiveProfile; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingBasal; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingGlucose; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingMaxValues; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingMeal; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingProfileRatios; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingProfileRatiosAll; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingPumpTime; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgSettingShippingInfo; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatus; +import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgStatusBasic; import info.nightscout.androidaps.plugins.PumpDanaR.events.EventDanaRNewStatus; +import info.nightscout.androidaps.plugins.PumpDanaR.services.AbstractDanaRExecutionService; import info.nightscout.androidaps.plugins.PumpDanaRv2.DanaRv2Plugin; import info.nightscout.androidaps.plugins.PumpDanaRv2.SerialIOThread; +import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgCheckValue_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgHistoryEvents_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgSetAPSTempBasalStart_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgSetHistoryEntry_v2; -import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgCheckValue_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusBolusExtended_v2; import info.nightscout.androidaps.plugins.PumpDanaRv2.comm.MsgStatusTempBasal_v2; import info.nightscout.androidaps.queue.Callback; @@ -54,47 +68,16 @@ import info.nightscout.utils.NSUpload; import info.nightscout.utils.SP; import info.nightscout.utils.ToastUtils; -public class DanaRv2ExecutionService extends Service { - private static Logger log = LoggerFactory.getLogger(DanaRv2ExecutionService.class); - - private String devName; - - private SerialIOThread mSerialIOThread; - private BluetoothSocket mRfcommSocket; - private BluetoothDevice mBTDevice; - - private IBinder mBinder = new LocalBinder(); - - private DanaRPump danaRPump; - private Treatment bolusingTreatment = null; - - private static Boolean connectionInProgress = false; - - private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); +public class DanaRv2ExecutionService extends AbstractDanaRExecutionService { private long lastHistoryFetched = 0; - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - String action = intent.getAction(); - if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) { - log.debug("Device was disconnected " + device.getName());//Device was disconnected - if (mBTDevice != null && mBTDevice.getName() != null && mBTDevice.getName().equals(device.getName())) { - if (mSerialIOThread != null) { - mSerialIOThread.disconnect("BT disconnection broadcast"); - } - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); - } - } - } - }; - public DanaRv2ExecutionService() { + log = LoggerFactory.getLogger(DanaRv2ExecutionService.class); + mBinder = new LocalBinder(); + registerBus(); MainApp.instance().getApplicationContext().registerReceiver(receiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED)); - danaRPump = DanaRPump.getInstance(); } public class LocalBinder extends Binder { @@ -103,17 +86,6 @@ public class DanaRv2ExecutionService extends Service { } } - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - - return START_STICKY; - } - private void registerBus() { try { MainApp.bus().unregister(this); @@ -138,35 +110,28 @@ public class DanaRv2ExecutionService extends Service { log.debug("EventAppExit finished"); } - public boolean isConnected() { - return mRfcommSocket != null && mRfcommSocket.isConnected(); - } - - public boolean isConnecting() { - return connectionInProgress; - } - - public void disconnect(String from) { + @Subscribe + public void onStatusEvent(final EventPreferenceChange pch) { if (mSerialIOThread != null) - mSerialIOThread.disconnect(from); + mSerialIOThread.disconnect("EventPreferenceChange"); } - public void connect(String from) { - if (danaRPump.password != -1 && danaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { + public void connect() { + if (mDanaRPump.password != -1 && mDanaRPump.password != SP.getInt(R.string.key_danar_password, -1)) { ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.wrongpumppassword), R.raw.error); return; } - if (connectionInProgress) + if (mConnectionInProgress) return; new Thread(new Runnable() { @Override public void run() { - connectionInProgress = true; + mConnectionInProgress = true; getBTSocketForSelectedPump(); if (mRfcommSocket == null || mBTDevice == null) { - connectionInProgress = false; + mConnectionInProgress = false; return; // Device not found } @@ -187,48 +152,11 @@ public class DanaRv2ExecutionService extends Service { MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTED, 0)); } - connectionInProgress = false; + mConnectionInProgress = false; } }).start(); } - public void stopConnecting() { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("stopConnecting"); - } - - private void getBTSocketForSelectedPump() { - devName = SP.getString(MainApp.sResources.getString(R.string.key_danar_bt_name), ""); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter != null) { - Set bondedDevices = bluetoothAdapter.getBondedDevices(); - - for (BluetoothDevice device : bondedDevices) { - if (devName.equals(device.getName())) { - mBTDevice = device; - try { - mRfcommSocket = mBTDevice.createRfcommSocketToServiceRecord(SPP_UUID); - } catch (IOException e) { - log.error("Error creating socket: ", e); - } - break; - } - } - } else { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.nobtadapter)); - } - if (mBTDevice == null) { - ToastUtils.showToastInUiThread(MainApp.instance().getApplicationContext(), MainApp.sResources.getString(R.string.devicenotfound)); - } - } - - @Subscribe - public void onStatusEvent(final EventPreferenceChange pch) { - if (mSerialIOThread != null) - mSerialIOThread.disconnect("EventPreferenceChange"); - } - public void getPumpStatus() { try { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpstatus))); @@ -238,7 +166,7 @@ public class DanaRv2ExecutionService extends Service { MsgStatusBolusExtended_v2 exStatusMsg = new MsgStatusBolusExtended_v2(); MsgCheckValue_v2 checkValue = new MsgCheckValue_v2(); - if (danaRPump.isNewPump) { + if (mDanaRPump.isNewPump) { mSerialIOThread.sendMessage(checkValue); if (!checkValue.received) { return; @@ -253,8 +181,8 @@ public class DanaRv2ExecutionService extends Service { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingextendedbolusstatus))); mSerialIOThread.sendMessage(exStatusMsg); - Date now = new Date(); - if (danaRPump.lastSettingsRead.getTime() + 60 * 60 * 1000L < now.getTime() || !MainApp.getSpecificPlugin(DanaRv2Plugin.class).isInitialized()) { + long now = System.currentTimeMillis(); + if (mDanaRPump.lastSettingsRead + 60 * 60 * 1000L < now || !MainApp.getSpecificPlugin(DanaRv2Plugin.class).isInitialized()) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumpsettings))); mSerialIOThread.sendMessage(new MsgSettingShippingInfo()); mSerialIOThread.sendMessage(new MsgSettingActiveProfile()); @@ -268,28 +196,28 @@ public class DanaRv2ExecutionService extends Service { mSerialIOThread.sendMessage(new MsgSettingProfileRatiosAll()); MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.gettingpumptime))); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - long timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + long timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); if (Math.abs(timeDiff) > 10) { mSerialIOThread.sendMessage(new MsgSetTime(new Date())); mSerialIOThread.sendMessage(new MsgSettingPumpTime()); - timeDiff = (danaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; + timeDiff = (mDanaRPump.pumpTime.getTime() - System.currentTimeMillis()) / 1000L; log.debug("Pump time difference: " + timeDiff + " seconds"); } - danaRPump.lastSettingsRead = now; + mDanaRPump.lastSettingsRead = now; } loadEvents(); - danaRPump.lastConnection = now; + mDanaRPump.lastConnection = now; MainApp.bus().post(new EventDanaRNewStatus()); MainApp.bus().post(new EventInitializationChanged()); NSUpload.uploadDeviceStatus(); - if (danaRPump.dailyTotalUnits > danaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { - log.debug("Approaching daily limit: " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits); + if (mDanaRPump.dailyTotalUnits > mDanaRPump.maxDailyTotalUnits * Constants.dailyLimitWarning) { + log.debug("Approaching daily limit: " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits); Notification reportFail = new Notification(Notification.APPROACHING_DAILY_LIMIT, MainApp.sResources.getString(R.string.approachingdailylimit), Notification.URGENT); MainApp.bus().post(new EventNewNotification(reportFail)); - NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + danaRPump.dailyTotalUnits + "/" + danaRPump.maxDailyTotalUnits + "U"); + NSUpload.uploadError(MainApp.sResources.getString(R.string.approachingdailylimit) + ": " + mDanaRPump.dailyTotalUnits + "/" + mDanaRPump.maxDailyTotalUnits + "U"); } } catch (Exception e) { log.error("Unhandled exception", e); @@ -299,10 +227,10 @@ public class DanaRv2ExecutionService extends Service { public boolean tempBasal(int percent, int durationInHours) { if (!isConnected()) return false; - if (danaRPump.isTempBasalInProgress) { + if (mDanaRPump.isTempBasalInProgress) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); - waitMsec(500); + SystemClock.sleep(500); } MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStart(percent, durationInHours)); @@ -314,10 +242,10 @@ public class DanaRv2ExecutionService extends Service { public boolean highTempBasal(int percent) { if (!isConnected()) return false; - if (danaRPump.isTempBasalInProgress) { + if (mDanaRPump.isTempBasalInProgress) { MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal))); mSerialIOThread.sendMessage(new MsgSetTempBasalStop()); - waitMsec(500); + SystemClock.sleep(500); } MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal))); mSerialIOThread.sendMessage(new MsgSetAPSTempBasalStart_v2(percent)); @@ -362,7 +290,7 @@ public class DanaRv2ExecutionService extends Service { if (BolusProgressDialog.stopPressed) return false; MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.startingbolus))); - bolusingTreatment = t; + mBolusingTreatment = t; final int preferencesSpeed = SP.getInt(R.string.key_danars_bolusspeed, 0); MessageBase start; if (preferencesSpeed == 0) @@ -390,7 +318,7 @@ public class DanaRv2ExecutionService extends Service { return false; } while (!stop.stopped && !start.failed) { - waitMsec(100); + SystemClock.sleep(100); if ((System.currentTimeMillis() - progress.lastReceive) > 15 * 1000L) { // if i didn't receive status for more than 15 sec expecting broken comm stop.stopped = true; stop.forced = true; @@ -403,7 +331,7 @@ public class DanaRv2ExecutionService extends Service { bolusingEvent.t = t; bolusingEvent.percent = 99; - bolusingTreatment = null; + mBolusingTreatment = null; int speed = 12; switch (preferencesSpeed) { case 0: @@ -440,14 +368,14 @@ public class DanaRv2ExecutionService extends Service { public void bolusStop() { if (Config.logDanaBTComm) - log.debug("bolusStop >>>>> @ " + (bolusingTreatment == null ? "" : bolusingTreatment.insulin)); + log.debug("bolusStop >>>>> @ " + (mBolusingTreatment == null ? "" : mBolusingTreatment.insulin)); MsgBolusStop stop = new MsgBolusStop(); stop.forced = true; if (isConnected()) { mSerialIOThread.sendMessage(stop); while (!stop.stopped) { mSerialIOThread.sendMessage(stop); - waitMsec(200); + SystemClock.sleep(200); } } else { stop.stopped = true; @@ -464,57 +392,10 @@ public class DanaRv2ExecutionService extends Service { return true; } - public PumpEnactResult loadHistory(byte type) { - PumpEnactResult result = new PumpEnactResult(); - if (!isConnected()) return result; - MessageBase msg = null; - switch (type) { - case RecordTypes.RECORD_TYPE_ALARM: - msg = new MsgHistoryAlarm(); - break; - case RecordTypes.RECORD_TYPE_BASALHOUR: - msg = new MsgHistoryBasalHour(); - break; - case RecordTypes.RECORD_TYPE_BOLUS: - msg = new MsgHistoryBolus(); - break; - case RecordTypes.RECORD_TYPE_CARBO: - msg = new MsgHistoryCarbo(); - break; - case RecordTypes.RECORD_TYPE_DAILY: - msg = new MsgHistoryDailyInsulin(); - break; - case RecordTypes.RECORD_TYPE_ERROR: - msg = new MsgHistoryError(); - break; - case RecordTypes.RECORD_TYPE_GLUCOSE: - msg = new MsgHistoryGlucose(); - break; - case RecordTypes.RECORD_TYPE_REFILL: - msg = new MsgHistoryRefill(); - break; - case RecordTypes.RECORD_TYPE_SUSPEND: - msg = new MsgHistorySuspend(); - break; - } - MsgHistoryDone done = new MsgHistoryDone(); - mSerialIOThread.sendMessage(new MsgPCCommStart()); - waitMsec(400); - mSerialIOThread.sendMessage(msg); - while (!done.received && mRfcommSocket.isConnected()) { - waitMsec(100); - } - waitMsec(200); - mSerialIOThread.sendMessage(new MsgPCCommStop()); - result.success = true; - result.comment = "OK"; - return result; - } - public PumpEnactResult loadEvents() { if (!isConnected()) return new PumpEnactResult().success(false); - waitMsec(300); + SystemClock.sleep(300); MsgHistoryEvents_v2 msg; if (lastHistoryFetched == 0) { msg = new MsgHistoryEvents_v2(); @@ -525,9 +406,9 @@ public class DanaRv2ExecutionService extends Service { } mSerialIOThread.sendMessage(msg); while (!msg.done && mRfcommSocket.isConnected()) { - waitMsec(100); + SystemClock.sleep(100); } - waitMsec(200); + SystemClock.sleep(200); if (MsgHistoryEvents_v2.lastEventTimeLoaded != 0) lastHistoryFetched = MsgHistoryEvents_v2.lastEventTimeLoaded - 45 * 60 * 1000L; //always load last 45 min; else @@ -543,13 +424,10 @@ public class DanaRv2ExecutionService extends Service { mSerialIOThread.sendMessage(msgSet); MsgSetActivateBasalProfile msgActivate = new MsgSetActivateBasalProfile((byte) 0); mSerialIOThread.sendMessage(msgActivate); - danaRPump.lastSettingsRead = new Date(0); // force read full settings + mDanaRPump.lastSettingsRead = 0; // force read full settings getPumpStatus(); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); return true; } - private void waitMsec(long msecs) { - SystemClock.sleep(msecs); - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java index f02cdd9371..f45d6cec40 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityAAPS/SensitivityAAPSPlugin.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; @@ -191,8 +192,10 @@ public class SensitivityAAPSPlugin implements PluginBase, SensitivityInterface{ log.debug(ratioLimit); } - log.debug("Sensitivity to: " + new Date(toTime).toLocaleString() + " percentile: " + percentile + " ratio: " + ratio); - log.debug("Sensitivity to: deviations " + Arrays.toString(deviations)); + if (Config.logAutosensData) { + log.debug("Sensitivity to: " + new Date(toTime).toLocaleString() + " percentile: " + percentile + " ratio: " + ratio + " mealCOB: " + current.cob); + log.debug("Sensitivity to: deviations " + Arrays.toString(deviations)); + } AutosensResult output = new AutosensResult(); output.ratio = Round.roundTo(ratio, 0.01); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java index 9aedf22d44..bf7d6ce8f5 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityOref0/SensitivityOref0Plugin.java @@ -7,8 +7,10 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.List; +import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.R; import info.nightscout.androidaps.data.Profile; @@ -118,7 +120,7 @@ public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface return new AutosensResult(); } - AutosensData current = IobCobCalculatorPlugin.getLastAutosensData("SensitivityOref0"); // this is running inside lock already + AutosensData current = IobCobCalculatorPlugin.getAutosensData(toTime); // this is running inside lock already if (current == null) { log.debug("No current autosens data available"); return new AutosensResult(); @@ -199,10 +201,8 @@ public class SensitivityOref0Plugin implements PluginBase, SensitivityInterface log.debug(ratioLimit); } - double newisf = Math.round(Profile.toMgdl(sens, profile.getUnits()) / ratio); - if (ratio != 1) { - log.debug("ISF adjusted from " + Profile.toMgdl(sens, profile.getUnits()) + " to " + newisf); - } + if (Config.logAutosensData) + log.debug("Sensitivity to: " + new Date(toTime).toLocaleString() + " ratio: " + ratio + " mealCOB: " + current.cob); AutosensResult output = new AutosensResult(); output.ratio = Round.roundTo(ratio, 0.01); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java index 046cd056fa..5f9e996a8a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SensitivityWeightedAverage/SensitivityWeightedAveragePlugin.java @@ -217,7 +217,7 @@ public class SensitivityWeightedAveragePlugin implements PluginBase, Sensitivity } if (Config.logAutosensData) - log.debug("Sensitivity to: " + new Date(toTime).toLocaleString() + " weightedaverage: " + average + " ratio: " + ratio); + log.debug("Sensitivity to: " + new Date(toTime).toLocaleString() + " weightedaverage: " + average + " ratio: " + ratio + " mealCOB: " + current.cob); AutosensResult output = new AutosensResult(); output.ratio = Round.roundTo(ratio, 0.01); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java index 04f5700dae..193801ac28 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SourceDexcomG5/SourceDexcomG5Plugin.java @@ -82,4 +82,9 @@ public class SourceDexcomG5Plugin implements PluginBase, BgSourceInterface { public int getPreferencesId() { return R.xml.pref_dexcomg5; } + + @Override + public boolean advancedFilteringSupported() { + return true; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java index 51fd755b07..eb9a6b1c6b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SourceGlimp/SourceGlimpPlugin.java @@ -83,4 +83,8 @@ public class SourceGlimpPlugin implements PluginBase, BgSourceInterface { } + @Override + public boolean advancedFilteringSupported() { + return false; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java index e530dd563b..1111c8c9eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SourceMM640g/SourceMM640gPlugin.java @@ -83,4 +83,8 @@ public class SourceMM640gPlugin implements PluginBase, BgSourceInterface { } + @Override + public boolean advancedFilteringSupported() { + return false; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java index 1928448f0e..f43c44047e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SourceNSClient/SourceNSClientPlugin.java @@ -85,4 +85,8 @@ public class SourceNSClientPlugin implements PluginBase, BgSourceInterface { } + @Override + public boolean advancedFilteringSupported() { + return true; + } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java index 7f73d457e7..532492d40e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/SourceXdrip/SourceXdripPlugin.java @@ -84,4 +84,8 @@ public class SourceXdripPlugin implements PluginBase, BgSourceInterface { } + @Override + public boolean advancedFilteringSupported() { + return false; + } } 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 6fcbcfe951..b10ad96697 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 @@ -20,6 +20,7 @@ import info.nightscout.androidaps.events.EventExtendedBolusChange; import info.nightscout.androidaps.plugins.Common.SubscriberFragment; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsBolusFragment; +import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsCareportalFragment; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsExtendedBolusesFragment; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsProfileSwitchFragment; import info.nightscout.androidaps.plugins.Treatments.fragments.TreatmentsTempTargetFragment; @@ -33,6 +34,7 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli TextView tempBasalsTab; TextView tempTargetTab; TextView profileSwitchTab; + TextView careportalTab; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -45,11 +47,13 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli 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); @@ -87,6 +91,10 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli setFragment(new TreatmentsProfileSwitchFragment()); setBackgroundColorOnSelected(profileSwitchTab); break; + case R.id.treatments_careportal: + setFragment(new TreatmentsCareportalFragment()); + setBackgroundColorOnSelected(careportalTab); + break; } } @@ -104,6 +112,7 @@ public class TreatmentsFragment extends SubscriberFragment implements View.OnCli tempBasalsTab.setBackgroundColor(MainApp.sResources.getColor(R.color.defaultbackground)); tempTargetTab.setBackgroundColor(MainApp.sResources.getColor(R.color.defaultbackground)); profileSwitchTab.setBackgroundColor(MainApp.sResources.getColor(R.color.defaultbackground)); + careportalTab.setBackgroundColor(MainApp.sResources.getColor(R.color.defaultbackground)); selected.setBackgroundColor(MainApp.sResources.getColor(R.color.tabBgColorSelected)); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java index 900eba7a40..dad53a0560 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsPlugin.java @@ -8,6 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Date; import java.util.List; import info.nightscout.androidaps.Config; @@ -197,6 +198,8 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { Iob tIOB = t.iobCalc(time, dia); total.iob += tIOB.iobContrib; total.activity += tIOB.activityContrib; + if (t.date > total.lastBolusTime) + total.lastBolusTime = t.date; if (!t.isSMB) { // instead of dividing the DIA that only worked on the bilinear curves, // multiply the time the treatment is seen active. @@ -204,9 +207,6 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { long snoozeTime = t.date + (long) (timeSinceTreatment * SP.getDouble("openapsama_bolussnooze_dia_divisor", 2.0)); Iob bIOB = t.iobCalc(snoozeTime, dia); total.bolussnooze += bIOB.iobContrib; - } else { - total.basaliob += t.insulin; - total.microBolusIOB += tIOB.iobContrib; } } @@ -244,6 +244,7 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (t > dia_ago && t <= now) { if (treatment.carbs >= 1) { result.carbs += treatment.carbs; + result.lastCarbTime = t; } if (treatment.insulin > 0 && treatment.mealBolus) { result.boluses += treatment.insulin; @@ -254,7 +255,10 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { AutosensData autosensData = IobCobCalculatorPlugin.getLastAutosensDataSynchronized("getMealData()"); if (autosensData != null) { result.mealCOB = autosensData.cob; + result.slopeFromMinDeviation = autosensData.slopeFromMinDeviation; + result.slopeFromMaxDeviation = autosensData.slopeFromMaxDeviation; } + result.lastBolusTime = getLastBolusTime(); return result; } @@ -276,6 +280,18 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { return in5minback; } + @Override + public long getLastBolusTime() { + long now = System.currentTimeMillis(); + long last = 0; + for (Treatment t : treatments) { + if (t.date > last && t.insulin > 0 && t.isValid && t.date <= now) + last = t.date; + } + log.debug("Last bolus time: " + new Date(last).toLocaleString()); + return last; + } + @Override public boolean isInHistoryRealTempBasalInProgress() { return getRealTempBasalFromHistory(System.currentTimeMillis()) != null; @@ -327,6 +343,12 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { IobTotal calc = t.iobCalc(time); //log.debug("BasalIOB " + new Date(time) + " >>> " + calc.basaliob); total.plus(calc); + if (!t.isEndingEvent()) { + total.lastTempDate = t.date; + total.lastTempDuration = t.durationInMinutes; + total.lastTempRate = t.tempBasalConvertedToAbsolute(t.date); + } + } } if (ConfigBuilderPlugin.getActivePump().isFakingTempsByExtendedBoluses()) { @@ -337,6 +359,12 @@ public class TreatmentsPlugin implements PluginBase, TreatmentsInterface { if (e.date > time) continue; IobTotal calc = e.iobCalc(time); totalExt.plus(calc); + TemporaryBasal t = new TemporaryBasal(e); + if (!t.isEndingEvent() && t.date > total.lastTempDate) { + total.lastTempDate = t.date; + total.lastTempDuration = t.durationInMinutes; + total.lastTempRate = t.tempBasalConvertedToAbsolute(t.date); + } } } // Convert to basal iob diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsBolusFragment.java index d096fd8549..99c89f0706 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 @@ -81,7 +81,7 @@ public class TreatmentsBolusFragment extends SubscriberFragment implements View. Iob iob = t.iobCalc(System.currentTimeMillis(), profile.getDia()); holder.iob.setText(DecimalFormatter.to2Decimal(iob.iobContrib) + " U"); holder.activity.setText(DecimalFormatter.to3Decimal(iob.activityContrib) + " U"); - holder.mealOrCorrection.setText(t.mealBolus ? MainApp.sResources.getString(R.string.mealbolus) : MainApp.sResources.getString(R.string.correctionbous)); + holder.mealOrCorrection.setText(t.isSMB ? "SMB" : t.mealBolus ? MainApp.sResources.getString(R.string.mealbolus) : MainApp.sResources.getString(R.string.correctionbous)); holder.ph.setVisibility(t.source == Source.PUMP ? View.VISIBLE : View.GONE); holder.ns.setVisibility(NSUpload.isIdValid(t._id) ? View.VISIBLE : View.GONE); holder.invalid.setVisibility(t.isValid ? View.GONE : View.VISIBLE); 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 new file mode 100644 index 0000000000..7218198b43 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/fragments/TreatmentsCareportalFragment.java @@ -0,0 +1,192 @@ +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 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.NSClientInternal.UploadQueue; +import info.nightscout.utils.DateUtil; +import info.nightscout.utils.NSUpload; +import info.nightscout.utils.SP; +import info.nightscout.utils.Translator; + +/** + * Created by mike on 13/01/17. + */ + +public class TreatmentsCareportalFragment extends SubscriberFragment implements View.OnClickListener { + + RecyclerView recyclerView; + LinearLayoutManager llm; + Button refreshFromNS; + + Context context; + + public class RecyclerViewAdapter extends RecyclerView.Adapter { + + List careportalEventList; + + RecyclerViewAdapter(List careportalEventList) { + this.careportalEventList = careportalEventList; + } + + @Override + public CareportalEventsViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.treatments_careportal_item, viewGroup, false); + CareportalEventsViewHolder CareportalEventsViewHolder = new CareportalEventsViewHolder(v); + return CareportalEventsViewHolder; + } + + @Override + public void onBindViewHolder(CareportalEventsViewHolder holder, int position) { + CareportalEvent careportalEvent = careportalEventList.get(position); + holder.ns.setVisibility(NSUpload.isIdValid(careportalEvent._id) ? View.VISIBLE : View.GONE); + holder.date.setText(DateUtil.dateAndTimeString(careportalEvent.date)); + holder.note.setText(careportalEvent.getNotes()); + holder.type.setText(Translator.translate(careportalEvent.eventType)); + holder.remove.setTag(careportalEvent); + } + + @Override + public int getItemCount() { + return careportalEventList.size(); + } + + @Override + public void onAttachedToRecyclerView(RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + } + + public class CareportalEventsViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + CardView cv; + TextView date; + TextView type; + TextView note; + TextView remove; + TextView ns; + + CareportalEventsViewHolder(View itemView) { + super(itemView); + cv = (CardView) itemView.findViewById(R.id.careportal_cardview); + date = (TextView) itemView.findViewById(R.id.careportal_date); + type = (TextView) itemView.findViewById(R.id.careportal_type); + note = (TextView) itemView.findViewById(R.id.careportal_note); + ns = (TextView) itemView.findViewById(R.id.ns_sign); + remove = (TextView) itemView.findViewById(R.id.careportal_remove); + remove.setOnClickListener(this); + 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.sResources.getString(R.string.confirmation)); + builder.setMessage(MainApp.sResources.getString(R.string.removerecord) + "\n" + DateUtil.dateAndTimeString(careportalEvent.date)); + builder.setPositiveButton(MainApp.sResources.getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int 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.sResources.getString(R.string.cancel), null); + builder.show(); + break; + } + } + } + } + + @Override + 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.setHasFixedSize(true); + llm = new LinearLayoutManager(view.getContext()); + recyclerView.setLayoutManager(llm); + + RecyclerViewAdapter adapter = new RecyclerViewAdapter(MainApp.getDbHelper().getCareportalEventsFromTime(false)); + recyclerView.setAdapter(adapter); + + refreshFromNS = (Button) view.findViewById(R.id.careportal_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.careportal_refreshfromnightscout: + AlertDialog.Builder builder = new AlertDialog.Builder(this.getContext()); + builder.setTitle(this.getContext().getString(R.string.confirmation)); + builder.setMessage(this.getContext().getString(R.string.refresheventsfromnightscout) + " ?"); + builder.setPositiveButton(this.getContext().getString(R.string.ok), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + MainApp.getDbHelper().resetCareportalEvents(); + Intent restartNSClient = new Intent(Intents.ACTION_RESTART); + MainApp.instance().getApplicationContext().sendBroadcast(restartNSClient); + } + }); + builder.setNegativeButton(this.getContext().getString(R.string.cancel), null); + builder.show(); + break; + } + + } + + @Subscribe + public void onStatusEvent(final EventCareportalEventChange 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().getCareportalEventsFromTime(false)), false); + } + }); + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java index da276af3c2..fbec0a2308 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java @@ -503,7 +503,7 @@ public class ActionStringHandler { return "Last result not available!"; } - if (!result.changeRequested) { + if (!result.isChangeRequested()) { ret += MainApp.sResources.getString(R.string.nochangerequested) + "\n"; } else if (result.rate == 0 && result.duration == 0) { ret += MainApp.sResources.getString(R.string.canceltemp) + "\n"; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java index 65b5a2557d..4adec23560 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/wearintegration/WatchUpdaterService.java @@ -523,27 +523,31 @@ public class WatchUpdaterService extends WearableListenerService implements private void sendStatus() { if (googleApiClient.isConnected()) { - - TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); - treatmentsInterface.updateTotalIOBTreatments(); - IobTotal bolusIob = treatmentsInterface.getLastCalculationTreatments().round(); - treatmentsInterface.updateTotalIOBTempBasals(); - IobTotal basalIob = treatmentsInterface.getLastCalculationTempBasals().round(); - - String iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob); - String iobDetail = "(" + DecimalFormatter.to2Decimal(bolusIob.iob) + "|" + DecimalFormatter.to2Decimal(basalIob.basaliob) + ")"; - String cobString = generateCOBString(); - String currentBasal = generateBasalString(treatmentsInterface); - - //bgi - String bgiString = ""; Profile profile = MainApp.getConfigBuilder().getProfile(); + String status = MainApp.instance().getString(R.string.noprofile); + String iobSum, iobDetail, cobString, currentBasal, bgiString; + iobSum = iobDetail = cobString = currentBasal = bgiString = ""; if(profile!=null) { + TreatmentsInterface treatmentsInterface = MainApp.getConfigBuilder(); + treatmentsInterface.updateTotalIOBTreatments(); + IobTotal bolusIob = treatmentsInterface.getLastCalculationTreatments().round(); + treatmentsInterface.updateTotalIOBTempBasals(); + IobTotal basalIob = treatmentsInterface.getLastCalculationTempBasals().round(); + + iobSum = DecimalFormatter.to2Decimal(bolusIob.iob + basalIob.basaliob); + iobDetail = "(" + DecimalFormatter.to2Decimal(bolusIob.iob) + "|" + DecimalFormatter.to2Decimal(basalIob.basaliob) + ")"; + cobString = generateCOBString(); + currentBasal = generateBasalString(treatmentsInterface); + + //bgi + + double bgi = -(bolusIob.activity + basalIob.activity) * 5 * profile.getIsf(); bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.to1Decimal(bgi); + + status = generateStatusString(profile, currentBasal,iobSum, iobDetail, bgiString); } - String status = generateStatusString(profile, currentBasal,iobSum, iobDetail, bgiString); //batteries int phoneBattery = getBatteryLevel(getApplicationContext()); @@ -637,6 +641,11 @@ public class WatchUpdaterService extends WearableListenerService implements private String generateBasalString(TreatmentsInterface treatmentsInterface) { String basalStringResult; + + Profile profile = MainApp.getConfigBuilder().getProfile(); + if (profile == null) + return ""; + TemporaryBasal activeTemp = treatmentsInterface.getTempBasalFromHistory(System.currentTimeMillis()); if (activeTemp != null) { basalStringResult = activeTemp.toStringShort(); @@ -644,7 +653,7 @@ public class WatchUpdaterService extends WearableListenerService implements if (SP.getBoolean(R.string.key_danar_visualizeextendedaspercentage, false)) { basalStringResult = "100%"; } else { - basalStringResult = DecimalFormatter.to2Decimal(MainApp.getConfigBuilder().getProfile().getBasal()) + "U/h"; + basalStringResult = DecimalFormatter.to2Decimal(profile.getBasal()) + "U/h"; } } return basalStringResult; diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java index 04da1cd9a9..b41a1727b3 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.java @@ -32,6 +32,7 @@ import info.nightscout.androidaps.queue.commands.CommandExtendedBolus; import info.nightscout.androidaps.queue.commands.CommandLoadEvents; import info.nightscout.androidaps.queue.commands.CommandLoadHistory; import info.nightscout.androidaps.queue.commands.CommandReadStatus; +import info.nightscout.androidaps.queue.commands.CommandSMBBolus; import info.nightscout.androidaps.queue.commands.CommandSetProfile; import info.nightscout.androidaps.queue.commands.CommandTempBasalAbsolute; import info.nightscout.androidaps.queue.commands.CommandTempBasalPercent; @@ -151,30 +152,35 @@ public class CommandQueue { // returns true if command is queued public boolean bolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { - if (isRunning(Command.CommandType.BOLUS)) { + Command.CommandType type = detailedBolusInfo.isSMB ? Command.CommandType.SMB_BOLUS : Command.CommandType.BOLUS; + + if (isRunning(type)) { if (callback != null) callback.result(executingNowError()).run(); return false; } // remove all unfinished boluses - removeAll(Command.CommandType.BOLUS); + removeAll(type); // apply constraints detailedBolusInfo.insulin = MainApp.getConfigBuilder().applyBolusConstraints(detailedBolusInfo.insulin); detailedBolusInfo.carbs = MainApp.getConfigBuilder().applyCarbsConstraints((int) detailedBolusInfo.carbs); // add new command to queue - add(new CommandBolus(detailedBolusInfo, callback)); + if (detailedBolusInfo.isSMB) { + add(new CommandSMBBolus(detailedBolusInfo, callback)); + } else { + add(new CommandBolus(detailedBolusInfo, callback)); + // Bring up bolus progress dialog (start here, so the dialog is shown when the bolus is requested, + // not when the Bolus command is starting. The command closes the dialog upon completion). + showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context); + // Notify Wear about upcoming bolus + MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin)); + } notifyAboutNewCommand(); - // Notify Wear about upcoming bolus - MainApp.bus().post(new EventBolusRequested(detailedBolusInfo.insulin)); - - // Bring up bolus progress dialog - showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context); - return true; } 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 d257bd24d0..c5fb9821bc 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/QueueThread.java @@ -16,6 +16,8 @@ import info.nightscout.androidaps.events.EventPumpStatusChanged; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification; +import info.nightscout.androidaps.plugins.Overview.notifications.Notification; import info.nightscout.androidaps.queue.events.EventQueueChanged; import info.nightscout.utils.SP; @@ -52,12 +54,6 @@ public class QueueThread extends Thread { while (true) { PumpInterface pump = ConfigBuilderPlugin.getActivePump(); long secondsElapsed = (System.currentTimeMillis() - connectionStartTime) / 1000; - if (pump.isConnecting()) { - log.debug("QUEUE: connecting " + secondsElapsed); - MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); - SystemClock.sleep(1000); - continue; - } if (!pump.isConnected() && secondsElapsed > Constants.PUMP_MAX_CONNECTION_TIME_IN_SECONDS) { MainApp.bus().post(new EventDismissBolusprogressIfRunning(null)); @@ -74,19 +70,37 @@ public class QueueThread extends Thread { //write time SP.putLong(R.string.key_btwatchdog_lastbark, System.currentTimeMillis()); //toggle BT + pump.stopConnecting(); + pump.disconnect("watchdog"); + SystemClock.sleep(1000); BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBluetoothAdapter.disable(); SystemClock.sleep(1000); mBluetoothAdapter.enable(); 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)); connectionStartTime = lastCommandTime = System.currentTimeMillis(); + pump.connect("watchdog"); } else { queue.clear(); + log.debug("QUEUE: no connection possible"); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTING)); + pump.disconnect("Queue empty"); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.DISCONNECTED)); return; } } + if (pump.isConnecting()) { + log.debug("QUEUE: connecting " + secondsElapsed); + MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); + SystemClock.sleep(1000); + continue; + } + + if (!pump.isConnected()) { log.debug("QUEUE: connect"); MainApp.bus().post(new EventPumpStatusChanged(EventPumpStatusChanged.CONNECTING, (int) secondsElapsed)); diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java index 5129c7983f..8875ff84a8 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/Command.java @@ -11,6 +11,7 @@ import info.nightscout.androidaps.queue.Callback; public abstract class Command { public enum CommandType { BOLUS, + SMB_BOLUS, TEMPBASAL, EXTENDEDBOLUS, BASALPROFILE, diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java new file mode 100644 index 0000000000..e1b268106a --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandSMBBolus.java @@ -0,0 +1,46 @@ +package info.nightscout.androidaps.queue.commands; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.data.DetailedBolusInfo; +import info.nightscout.androidaps.data.PumpEnactResult; +import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; +import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog; +import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogressIfRunning; +import info.nightscout.androidaps.queue.Callback; +import info.nightscout.utils.DecimalFormatter; + +/** + * Created by mike on 09.11.2017. + */ + +public class CommandSMBBolus extends Command { + private static Logger log = LoggerFactory.getLogger(CommandSMBBolus.class); + DetailedBolusInfo detailedBolusInfo; + + public CommandSMBBolus(DetailedBolusInfo detailedBolusInfo, Callback callback) { + commandType = CommandType.SMB_BOLUS; + this.detailedBolusInfo = detailedBolusInfo; + this.callback = callback; + } + + @Override + public void execute() { + PumpEnactResult r; + if (detailedBolusInfo.deliverAt != 0 && detailedBolusInfo.deliverAt + 60 * 1000L > System.currentTimeMillis()) + r = ConfigBuilderPlugin.getActivePump().deliverTreatment(detailedBolusInfo); + else { + r = new PumpEnactResult().enacted(false).success(false).comment("SMB request too old"); + log.debug("SMB bolus canceled. delivetAt=" + detailedBolusInfo.deliverAt + " now=" + System.currentTimeMillis()); + } + + if (callback != null) + callback.result(r).run(); + } + + public String status() { + return "SMBBOLUS " + DecimalFormatter.to1Decimal(detailedBolusInfo.insulin) + "U"; + } +} diff --git a/app/src/main/java/info/nightscout/utils/NSUpload.java b/app/src/main/java/info/nightscout/utils/NSUpload.java index 2ec3a57dd6..2f8893aa7b 100644 --- a/app/src/main/java/info/nightscout/utils/NSUpload.java +++ b/app/src/main/java/info/nightscout/utils/NSUpload.java @@ -199,17 +199,8 @@ public class NSUpload { apsResult.json().put("timestamp", DateUtil.toISOString(lastRun.lastAPSRun)); deviceStatus.suggested = apsResult.json(); - if (lastRun.request instanceof DetermineBasalResultMA) { - DetermineBasalResultMA result = (DetermineBasalResultMA) lastRun.request; - deviceStatus.iob = result.iob.json(); - deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); - } - - if (lastRun.request instanceof DetermineBasalResultAMA) { - DetermineBasalResultAMA result = (DetermineBasalResultAMA) lastRun.request; - deviceStatus.iob = result.iob.json(); - deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); - } + deviceStatus.iob = lastRun.request.iob.json(); + deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun)); if (lastRun.setByPump != null && lastRun.setByPump.enacted) { // enacted deviceStatus.enacted = lastRun.request.json(); diff --git a/app/src/main/res/layout/overview_fragment.xml b/app/src/main/res/layout/overview_fragment.xml index 158fd6b9b5..10c89254e9 100644 --- a/app/src/main/res/layout/overview_fragment.xml +++ b/app/src/main/res/layout/overview_fragment.xml @@ -270,6 +270,7 @@ app:buttonTint="@color/basal" /> - + android:buttonTint="@color/prediction" /> + android:buttonTint="@color/basal" /> + android:buttonTint="@color/iob" /> + android:buttonTint="@color/cob" /> + android:buttonTint="@color/deviations" /> + android:buttonTint="@color/prediction" /> + android:buttonTint="@color/basal" /> + android:buttonTint="@color/iob" /> + android:buttonTint="@color/cob" /> + android:buttonTint="@color/deviations" /> + android:buttonTint="@color/prediction" /> + android:buttonTint="@color/basal" /> + android:buttonTint="@color/iob" /> + android:buttonTint="@color/cob" /> + android:buttonTint="@color/deviations" /> + + + +