From 8a19267a39d0e869e68601b39a071d2a00d50736 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 16 Feb 2022 00:03:21 +0100 Subject: [PATCH] AutoISF -> separate plugin --- .../main/assets/OpenAPSSMB/determine-basal.js | 137 +- .../OpenAPSSMBAutoISF/determine-basal.js | 1224 +++++++++++++++++ .../nightscout/androidaps/di/APSModule.kt | 2 + .../nightscout/androidaps/di/PluginsModule.kt | 7 + .../openAPSAMA/DetermineBasalAdapterAMAJS.kt | 55 +- .../aps/openAPSAMA/OpenAPSAMAFragment.kt | 2 +- .../aps/openAPSAMA/OpenAPSAMAPlugin.kt | 10 +- .../openAPSSMB/DetermineBasalAdapterSMBJS.kt | 83 +- .../aps/openAPSSMB/OpenAPSSMBFragment.kt | 15 +- .../aps/openAPSSMB/OpenAPSSMBPlugin.kt | 75 +- .../DetermineBasalAdapterSMBAutoISFJS.kt | 298 ++++ .../OpenAPSSMBAutoISFPlugin.kt | 74 + .../androidaps/utils/stats/TddCalculator.kt | 1 - app/src/main/res/values/strings.xml | 2 + .../nightscout/androidaps/interfaces/APS.kt | 4 + .../DetermineBasalAdapterInterface.kt | 36 + .../interfaces/PluginDescription.kt | 2 +- 17 files changed, 1809 insertions(+), 218 deletions(-) create mode 100644 app/src/main/assets/OpenAPSSMBAutoISF/determine-basal.js create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/DetermineBasalAdapterSMBAutoISFJS.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/OpenAPSSMBAutoISFPlugin.kt create mode 100644 core/src/main/java/info/nightscout/androidaps/interfaces/DetermineBasalAdapterInterface.kt diff --git a/app/src/main/assets/OpenAPSSMB/determine-basal.js b/app/src/main/assets/OpenAPSSMB/determine-basal.js index d33dc5988d..9b74c5228a 100644 --- a/app/src/main/assets/OpenAPSSMB/determine-basal.js +++ b/app/src/main/assets/OpenAPSSMB/determine-basal.js @@ -277,56 +277,16 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var profile_sens = round(profile.sens,1) var sens = profile.sens; - - var now = new Date().getHours(); - if (now < 1){ - now = 1;} - else { - console.error("Time now is "+now+"; "); - } - if (meal_data.TDDAIMI7){ - var tdd7 = meal_data.TDDAIMI7; - } - else{ - var tdd7 = ((basal * 12)*100)/21; - } - var tdd_pump_now = meal_data.TDDPUMP; - var tdd_pump = ( tdd_pump_now / (now / 24)); - var TDD = (tdd7 * 0.4) + (tdd_pump * 0.6); - console.error("Pump extrapolated TDD = "+tdd_pump+"; "); - //if (tdd7 > 0){ - if ( tdd_pump > tdd7 && now < 5 || now < 7 && TDD < ( 0.8 * tdd7 ) ){ - TDD = ( 0.8 * tdd7 ); - console.log("Excess or too low insulin from pump so TDD set to "+TDD+" based on 75% of TDD7; "); - rT.reason += "TDD: " +TDD+ " due to low or high tdd from pump; "; - } - - else if (tdd_pump < (0.33 * tdd7)){ - TDD = (tdd7 * 0.25) + (tdd_pump * 0.75); - console.error("TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "); - rT.reason += "TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "; - } - - else { - console.log("TDD 7 ="+tdd7+", TDD Pump ="+tdd_pump+" and TDD = "+TDD+";"); - rT.reason += "TDD: " +TDD+ " based on standard pump 60/tdd7 40 split; "; - } - - - var variable_sens = (277700 / (TDD * bg)); - variable_sens = round(variable_sens,1); - console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); - - sens = variable_sens; - if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { - sens = sens / sensitivityRatio ; - sens = round(sens, 1); - console.log("ISF from "+variable_sens+" to "+sens+ "due to temp target; "); - } else { - sens = sens; + if (typeof autosens_data !== 'undefined' && autosens_data) { + sens = profile.sens / sensitivityRatio; sens = round(sens, 1); + if (sens !== profile_sens) { + console.log("ISF from "+profile_sens+" to "+sens); + } else { + console.log("ISF unchanged: "+sens); + } + //console.log(" (autosens ratio "+sensitivityRatio+")"); } - console.error("; CR:",profile.carb_ratio); // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match @@ -740,20 +700,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); - console.log("EventualBG is" +eventualBG+" ;"); - - if( glucose_status.delta >= 0 || bg > 60 && glucose_status.delta < 2 && glucose_status.delta > -2 && glucose_status.short_avgdelta > -2 && glucose_status.short_avgdelta < 2 || eventualBG > target_bg && glucose_status.delta < 0 ) { - var future_sens = ( 277700 / (TDD * bg) ); - console.log("Future state sensitivity is " +future_sens+" using current bg due to no COB & small delta or variation"); - rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; - } - else { - var future_sens = ( 277700 / (TDD * eventualBG)); - console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); - rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; - } - var future_sens = round(future_sens,1); - minIOBPredBG = Math.max(39,minIOBPredBG); minCOBPredBG = Math.max(39,minCOBPredBG); @@ -974,33 +920,30 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ 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 " + round(basal, 2) + "U/hr. "; + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; } else { - rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. "; + 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 - // multiply by 2 to low-temp faster for increased hypo safety - - var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / future_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 - 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); + // calculate 30m low-temp required to get projected BG up to target + // multiply by 2 to low-temp faster for increased hypo safety + 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 + 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; @@ -1049,10 +992,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ 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 " + round(basal, 2) + "U/hr. "; + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; } else { - rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. "; + rT.reason += "; setting current basal of " + basal + " as temp. "; return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); } } @@ -1063,10 +1006,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ 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 " + round(basal, 2) + "U/hr. "; + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; } else { - rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. "; + rT.reason += "; setting current basal of " + basal + " as temp. "; return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); } } @@ -1080,22 +1023,22 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ 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 " + round(basal, 2) + "U/hr. "; + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr. "; return rT; } else { - rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. "; + 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); - insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / future_sens, 2); - // 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; - } + // insulinReq is the additional insulin required to get minPredBG down to target_bg + //console.error(minPredBG,eventualBG); + insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); + // 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: rate = basal + (2 * insulinReq); diff --git a/app/src/main/assets/OpenAPSSMBAutoISF/determine-basal.js b/app/src/main/assets/OpenAPSSMBAutoISF/determine-basal.js new file mode 100644 index 0000000000..d33dc5988d --- /dev/null +++ b/app/src/main/assets/OpenAPSSMBAutoISF/determine-basal.js @@ -0,0 +1,1224 @@ +/* + 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; + return /* expectedDelta */ round(bgi + (target_delta / five_min_blocks), 1); +} + + +function convert_bg(value, profile) +{ + if (profile.out_units === "mmol/L") + { + return round(value / 18, 1).toFixed(1); + } + else + { + return Math.round(value); + } +} + +function enable_smb( + profile, + microBolusAllowed, + meal_data, + target_bg +) { + // disable SMB when a high temptarget is set + if (! microBolusAllowed) { + console.error("SMB disabled (!microBolusAllowed)"); + return false; + } else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) { + console.error("SMB disabled due to high temptarget of",target_bg); + return false; + } else if (meal_data.bwFound === true && profile.A52_risk_enable === false) { + console.error("SMB disabled due to Bolus Wizard activity in the last 6 hours."); + return false; + } + + // enable SMB/UAM if always-on (unless previously disabled for high temptarget) + if (profile.enableSMB_always === true) { + if (meal_data.bwFound) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"); + } else { + console.error("SMB enabled due to enableSMB_always"); + } + return true; + } + + // enable SMB/UAM (if enabled in preferences) while we have COB + if (profile.enableSMB_with_COB === true && meal_data.mealCOB) { + if (meal_data.bwCarbs) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"); + } else { + console.error("SMB enabled for COB of",meal_data.mealCOB); + } + return 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) + if (profile.enableSMB_after_carbs === true && meal_data.carbs ) { + if (meal_data.bwCarbs) { + console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard"); + } else { + console.error("SMB enabled for 6h after carb entry"); + } + return true; + } + + // enable SMB/UAM (if enabled in preferences) if a low temptarget is set + if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) { + if (meal_data.bwFound) { + console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard"); + } else { + console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile)); + } + return true; + } + + console.error("SMB disabled (no enableSMB preferences active or no condition satisfied)"); + return false; +} + +var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data, currentTime, isSaveCgmSource) { + var rT = {}; //short for requestedTemp + + var deliverAt = new Date(); + if (currentTime) { + deliverAt = new Date(currentTime); + } + + 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(); + if (currentTime) { + systemTime = currentTime; + } + var bgTime = new Date(glucose_status.date); + var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1); + + var bg = glucose_status.glucose; + var noise = glucose_status.noise; + // 38 is an xDrip error state that usually indicates sensor failure + // all other BG values between 11 and 37 mg/dL reflect non-error-code BG values, so we should zero temp for those + if (bg <= 10 || bg === 38 || noise >= 3) { //Dexcom is in ??? mode or calibrating, or xDrip reports high noise + rT.reason = "CGM is calibrating, in ??? state, or noise is high"; + } + 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 is too old/noisy, or is changing less than 1 mg/dL/5m for 45m, cancel any high temps and shorten any long zero temps + //cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc + } else if ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 && !isSaveCgmSource) { + if ( glucose_status.last_cal && glucose_status.last_cal < 3 ) { + rT.reason = "CGM was just calibrated"; + } else { + rT.reason = "Error: CGM data is unchanged for the past ~45m"; + } + } + //cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc + if (bg <= 10 || bg === 38 || noise >= 3 || minAgo > 12 || minAgo < -5 || ( bg > 60 && glucose_status.delta == 0 && glucose_status.short_avgdelta > -1 && glucose_status.short_avgdelta < 1 && glucose_status.long_avgdelta > -1 && glucose_status.long_avgdelta < 1 ) && !isSaveCgmSource ) { + if (currenttemp.rate > basal) { // high temp is running + rT.reason += ". Replacing high temp basal of "+currenttemp.rate+" with neutral temp of "+basal; + rT.deliverAt = deliverAt; + rT.temp = 'absolute'; + rT.duration = 30; + rT.rate = basal; + 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 " + round(basal, 2) + "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 target (which might change) + if ( profile.half_basal_exercise_target ) { + var halfBasalTarget = profile.half_basal_exercise_target; + } else { + 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 + || 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.log("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); + } else if (typeof autosens_data !== 'undefined' && autosens_data) { + sensitivityRatio = autosens_data.ratio; + console.log("Autosens ratio: "+sensitivityRatio+"; "); + } + if (sensitivityRatio) { + basal = profile.current_basal * sensitivityRatio; + basal = round_basal(basal, profile); + if (basal !== profile_current_basal) { + console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); + } else { + console.log("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.log("Temp Target set, not adjusting with autosens; "); + } else if (typeof autosens_data !== 'undefined' && autosens_data) { + 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; + var 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.log("target_bg unchanged: "+new_target_bg+"; "); + } else { + console.log("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; + + var now = new Date().getHours(); + if (now < 1){ + now = 1;} + else { + console.error("Time now is "+now+"; "); + } + if (meal_data.TDDAIMI7){ + var tdd7 = meal_data.TDDAIMI7; + } + else{ + var tdd7 = ((basal * 12)*100)/21; + } + var tdd_pump_now = meal_data.TDDPUMP; + var tdd_pump = ( tdd_pump_now / (now / 24)); + var TDD = (tdd7 * 0.4) + (tdd_pump * 0.6); + console.error("Pump extrapolated TDD = "+tdd_pump+"; "); + //if (tdd7 > 0){ + if ( tdd_pump > tdd7 && now < 5 || now < 7 && TDD < ( 0.8 * tdd7 ) ){ + TDD = ( 0.8 * tdd7 ); + console.log("Excess or too low insulin from pump so TDD set to "+TDD+" based on 75% of TDD7; "); + rT.reason += "TDD: " +TDD+ " due to low or high tdd from pump; "; + } + + else if (tdd_pump < (0.33 * tdd7)){ + TDD = (tdd7 * 0.25) + (tdd_pump * 0.75); + console.error("TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "); + rT.reason += "TDD weighted to pump due to low insulin usage. TDD = "+TDD+"; "; + } + + else { + console.log("TDD 7 ="+tdd7+", TDD Pump ="+tdd_pump+" and TDD = "+TDD+";"); + rT.reason += "TDD: " +TDD+ " based on standard pump 60/tdd7 40 split; "; + } + + + var variable_sens = (277700 / (TDD * bg)); + variable_sens = round(variable_sens,1); + console.log("Current sensitivity for predictions is " +variable_sens+" based on current bg"); + + sens = variable_sens; + if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget || profile.low_temptarget_lowers_sensitivity && profile.temptargetSet && target_bg < normalTarget ) { + sens = sens / sensitivityRatio ; + sens = round(sens, 1); + console.log("ISF from "+variable_sens+" to "+sens+ "due to temp target; "); + } else { + sens = sens; + sens = round(sens, 1); + } + + 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(systemTime).getTime() - iob_data.lastTemp.date ) / 60000); // in minutes + } else { + lastTempAge = 0; + } + //console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m"); + var 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 && lastTempAge > 10 && currenttemp.duration ) { + rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; canceling temp"; + return tempBasalFunctions.setTempBasal(0, 0, 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 && lastTempAge > 10 ) { + rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; canceling temp"; + //console.error(currenttemp, round(iob_data.lastTemp,1), round(lastTempAge,1)); + return tempBasalFunctions.setTempBasal(0, 0, 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 + naive_eventualBG = round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); + } + // and adjust it for the deviation above + var eventualBG = naive_eventualBG + deviation; + + // raise target for noisy / raw CGM data + if (glucose_status.noise >= 2) { + // increase target at least 10% (default 30%) for raw / noisy data + var noisyCGMTargetMultiplier = Math.max( 1.1, profile.noisyCGMTargetMultiplier ); + // don't allow maxRaw above 250 + var maxRaw = Math.min( 250, profile.maxRaw ); + var adjustedMinBG = round(Math.min(200, min_bg * noisyCGMTargetMultiplier )); + var adjustedTargetBG = round(Math.min(200, target_bg * noisyCGMTargetMultiplier )); + var adjustedMaxBG = round(Math.min(200, max_bg * noisyCGMTargetMultiplier )); + console.log("Raising target_bg for noisy / raw CGM data, from "+target_bg+" to "+adjustedTargetBG+"; "); + min_bg = adjustedMinBG; + target_bg = adjustedTargetBG; + max_bg = adjustedMaxBG; + // adjust target BG range if configured to bring down high BG faster + } else 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 + adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0); + adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0); + 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.log("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; "); + min_bg = adjustedMinBG; + } else { + console.log("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.log("target_bg from "+target_bg+" to "+adjustedTargetBG+"; "); + target_bg = adjustedTargetBG; + } else { + console.log("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 + , 'targetBG': target_bg + , '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); + + var enableSMB = enable_smb( + profile, + microBolusAllowed, + meal_data, + target_bg + ); + + // enable UAM (if enabled in preferences) + var enableUAM=(profile.enableUAM); + + + //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); + var uci = round((minDelta - bgi),1); + // ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g) + + // TODO: remove commented-out code for old behavior + //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; + //} + // 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 TT or autosens + // this avoids overdosing insulin for large meals when low temp targets are active + csf = sens / profile.carb_ratio; + console.error("profile.sens:",profile.sens,"sens:",sens,"CSF:",csf); + + 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 + var 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; + } + var remainingCATimeMin = 3; // h; duration of expected not-yet-observed carb absorption + // adjust remainingCATime (instead of CR) for autosens if sensitivityRatio defined + if (sensitivityRatio){ + 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; + 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(systemTime).getTime() - meal_data.lastCarbTime ) / 60000); + //console.error(meal_data.lastCarbTime, lastCarbAge); + + var 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); + + // 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); + + var 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 )); + } + var 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); + var predBGI = round(( -iobTick.activity * sens * 5 ), 2); + var 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) + var 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 + var 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) + var predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); + var 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.log(round(predCI,1)+"+"+round(remainingCI,1)+" "); + COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI; + var 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 + var 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) + var 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 + var 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 insulin 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"); + } + if (meal_data.mealCOB) { + console.error("predCIs (mg/dL/5m):",predCIs.join(" ")); + console.error("remainingCIs: ",remainingCIs.join(" ")); + } + 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 (i=ZTpredBGs.length-1; i > 6; i--) { + // 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 (i=aCOBpredBGs.length-1; i > 12; i--) { + if (aCOBpredBGs[i-1] !== aCOBpredBGs[i]) { break; } + else { aCOBpredBGs.pop(); } + } + } + 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 (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 (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 based on COB or UAM predBGs + rT.eventualBG = eventualBG; + } + + console.error("UAM Impact:",uci,"mg/dL per 5m; UAM Duration:",UAMduration,"hours"); + + console.log("EventualBG is" +eventualBG+" ;"); + + if( glucose_status.delta >= 0 || bg > 60 && glucose_status.delta < 2 && glucose_status.delta > -2 && glucose_status.short_avgdelta > -2 && glucose_status.short_avgdelta < 2 || eventualBG > target_bg && glucose_status.delta < 0 ) { + var future_sens = ( 277700 / (TDD * bg) ); + console.log("Future state sensitivity is " +future_sens+" using current bg due to no COB & small delta or variation"); + rT.reason += "Dosing sensitivity: " +future_sens+" using current BG;"; + } + else { + var future_sens = ( 277700 / (TDD * eventualBG)); + console.log("Future state sensitivity is " +future_sens+" based on eventual bg due to -ve delta"); + rT.reason += "Dosing sensitivity: " +future_sens+" using eventual BG;"; + } + var future_sens = round(future_sens,1); + + + 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) { + + // 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 + var 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 if ( enableUAM ) { + minPredBG = minZTUAMPredBG; + } else { + minPredBG = minGuardBG; + } + // in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG + } else if ( enableUAM ) { + minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); + } + + // make sure minPredBG isn't higher than avgPredBG + minPredBG = Math.min( minPredBG, avgPredBG ); + + console.log("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); + if (minCOBPredBG < 999) { + console.log(" minCOBPredBG: "+minCOBPredBG); + } + if (minUAMPredBG < 999) { + console.log(" 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: " + round(meal_data.mealCOB, 1) + ", 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 += "; "; + // use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39 + 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 (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 minutesAboveThreshold 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 = 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.carbsReqWithin = minutesAboveThreshold; + 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); + 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 not in LGS mode, cancel temps before the top of the hour to reduce beeping/vibration + // console.error(profile.skip_neutral_temps, rT.deliverAt.getMinutes()); + if ( profile.skip_neutral_temps && rT.deliverAt.getMinutes() >= 55 ) { + rT.reason += "; Canceling temp at " + rT.deliverAt.getMinutes() + "m past the hour. "; + return tempBasalFunctions.setTempBasal(0, 0, 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 " + round(basal, 2) + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + round(basal, 2) + " as temp. "; + return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + } + } + + + + // calculate 30m low-temp required to get projected BG up to target + // multiply by 2 to low-temp faster for increased hypo safety + + var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / future_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 + 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 ) { + bgUndershoot = target_bg - naive_eventualBG; + worstCaseInsulinReq = bgUndershoot / sens; + 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); + 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 " + round(basal, 2) + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + round(basal, 2) + " 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 " + round(basal, 2) + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + round(basal, 2) + " 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 ( eventualBG >= max_bg ) { + 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 " + round(basal, 2) + "U/hr. "; + return rT; + } else { + rT.reason += "; setting current basal of " + round(basal, 2) + " 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); + insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / future_sens, 2); + // 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: + 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(systemTime).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 + var mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); + if (typeof profile.maxSMBBasalMinutes === 'undefined' ) { + var 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); + if (profile.maxUAMSMBBasalMinutes) { + console.error("profile.maxUAMSMBBasalMinutes:",profile.maxUAMSMBBasalMinutes,"profile.current_basal:",profile.current_basal); + maxBolus = round( profile.current_basal * profile.maxUAMSMBBasalMinutes / 60 ,1); + } else { + console.error("profile.maxUAMSMBBasalMinutes undefined: defaulting to 30m"); + 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 bolus increment + var roundSMBTo = 1 / profile.bolus_increment; + var microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*roundSMBTo)/roundSMBTo; + // calculate a long enough zero temp to eventually correct back up to target + var smbTarget = target_bg; + worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; + 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 < profile.bolus_increment) { + durationReq = 0; + } + + var smbLowTempReq = 0; + if (durationReq <= 0) { + durationReq = 0; + // don't set an SMB zero temp longer than 60 minutes + } else if (durationReq >= 30) { + durationReq = round(durationReq/30)*30; + durationReq = Math.min(60,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 by default + var SMBInterval = 3; + if (profile.SMBInterval) { + // allow SMBIntervals between 1 and 10 minutes + SMBInterval = Math.min(10,Math.max(1,profile.SMBInterval)); + } + var nextBolusMins = round(SMBInterval-lastBolusAge,0); + var nextBolusSeconds = round((SMBInterval - lastBolusAge) * 60, 0) % 60; + //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 > SMBInterval) { + if (microBolus > 0) { + rT.units = microBolus; + rT.reason += "Microbolusing " + microBolus + "U. "; + } + } else { + rT.reason += "Waiting " + nextBolusMins + "m " + nextBolusSeconds + "s 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; + } + + } + + var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); + + if (rate > maxSafeBasal) { + rT.reason += "adj. req. rate: "+round(rate, 2)+" to maxSafeBasal: "+maxSafeBasal+", "; + rate = round_basal(maxSafeBasal, profile); + } + + 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/di/APSModule.kt b/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt index 235ac4065d..b9d75200e6 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/APSModule.kt @@ -7,6 +7,7 @@ import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalAdapterAM import info.nightscout.androidaps.plugins.aps.openAPSAMA.DetermineBasalResultAMA import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB +import info.nightscout.androidaps.plugins.aps.openAPSSMBAutoISF.DetermineBasalAdapterSMBAutoISFJS import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobOref1Thread import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread @@ -19,6 +20,7 @@ abstract class APSModule { @ContributesAndroidInjector abstract fun determineBasalResultAMAInjector(): DetermineBasalResultAMA @ContributesAndroidInjector abstract fun determineBasalAdapterAMAJSInjector(): DetermineBasalAdapterAMAJS @ContributesAndroidInjector abstract fun determineBasalAdapterSMBJSInjector(): DetermineBasalAdapterSMBJS + @ContributesAndroidInjector abstract fun determineBasalAdapterSMBAutoISFJSInjector(): DetermineBasalAdapterSMBAutoISFJS @ContributesAndroidInjector abstract fun iobCobThreadInjector(): IobCobThread @ContributesAndroidInjector abstract fun iobCobOref1ThreadInjector(): IobCobOref1Thread } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt index 1942773a03..2f6d629bcf 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/PluginsModule.kt @@ -14,6 +14,7 @@ import info.nightscout.androidaps.plugin.general.openhumans.OpenHumansUploader import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMBAutoISF.OpenAPSSMBAutoISFPlugin import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.constraints.bgQualityCheck.BgQualityCheckPlugin import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin @@ -212,6 +213,12 @@ abstract class PluginsModule { @IntKey(220) abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase + @Binds + @APS + @IntoMap + @IntKey(222) + abstract fun bindOpenAPSSMBAutoISFPlugin(plugin: OpenAPSSMBAutoISFPlugin): PluginBase + @Binds @AllConfigs @IntoMap diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt index 72196a9914..184b2b2bd3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/DetermineBasalAdapterAMAJS.kt @@ -7,6 +7,7 @@ import info.nightscout.androidaps.data.MealData import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes import info.nightscout.androidaps.extensions.plannedRemainingMinutes +import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface import info.nightscout.androidaps.interfaces.GlucoseUnit import info.nightscout.androidaps.interfaces.IobCobCalculator import info.nightscout.androidaps.interfaces.Profile @@ -14,6 +15,7 @@ import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback +import info.nightscout.androidaps.plugins.aps.loop.APSResult import info.nightscout.androidaps.plugins.aps.loop.ScriptReader import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker @@ -30,7 +32,7 @@ import java.nio.charset.StandardCharsets import javax.inject.Inject import kotlin.math.min -class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) { +class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader, injector: HasAndroidInjector) : DetermineBasalAdapterInterface { private val injector: HasAndroidInjector @@ -48,21 +50,15 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader private var currentTemp = JSONObject() private var autosensData = JSONObject() - var currentTempParam: String? = null - private set - var iobDataParam: String? = null - private set - var glucoseStatusParam: String? = null - private set - var profileParam: String? = null - private set - var mealDataParam: String? = null - private set - var scriptDebug = "" - private set + override var currentTempParam: String? = null + override var iobDataParam: String? = null + override var glucoseStatusParam: String? = null + override var profileParam: String? = null + override var mealDataParam: String? = null + override var scriptDebug = "" @Suppress("SpellCheckingInspection") - operator fun invoke(): DetermineBasalResultAMA? { + override operator fun invoke(): APSResult? { aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") aapsLogger.debug(LTag.APS, "Glucose status: " + glucoseStatus.toString().also { glucoseStatusParam = it }) aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it }) @@ -143,18 +139,25 @@ class DetermineBasalAdapterAMAJS internal constructor(scriptReader: ScriptReader } @Suppress("SpellCheckingInspection") - @Throws(JSONException::class) fun setData(profile: Profile, - maxIob: Double, - maxBasal: Double, - minBg: Double, - maxBg: Double, - targetBg: Double, - basalRate: Double, - iobArray: Array, - glucoseStatus: GlucoseStatus, - mealData: MealData, - autosensDataRatio: Double, - tempTargetSet: Boolean) { + @Throws(JSONException::class) + override fun setData( + profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean, + microBolusAllowed: Boolean, + uamAllowed: Boolean, + advancedFiltering: Boolean, + isSaveCgmSource: Boolean + ) { this.profile = JSONObject() this.profile.put("max_iob", maxIob) this.profile.put("dia", min(profile.dia, 3.0)) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt index 14deed2bb4..d9d8dd2c0b 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAFragment.kt @@ -96,7 +96,7 @@ class OpenAPSAMAFragment : DaggerFragment() { binding.result.text = jsonFormatter.format(lastAPSResult.json) binding.request.text = lastAPSResult.toSpanned() } - openAPSAMAPlugin.lastDetermineBasalAdapterAMAJS?.let { determineBasalAdapterAMAJS -> + openAPSAMAPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterAMAJS -> binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterAMAJS.glucoseStatusParam) binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterAMAJS.currentTempParam) try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt index 5c883413ed..229b8ecd14 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSAMA/OpenAPSAMAPlugin.kt @@ -60,8 +60,8 @@ class OpenAPSAMAPlugin @Inject constructor( // last values override var lastAPSRun: Long = 0 override var lastAPSResult: DetermineBasalResultAMA? = null - var lastDetermineBasalAdapterAMAJS: DetermineBasalAdapterAMAJS? = null - var lastAutosensResult: AutosensResult = AutosensResult() + override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null + override var lastAutosensResult: AutosensResult = AutosensResult() override fun specialEnableCondition(): Boolean { return try { @@ -158,7 +158,7 @@ class OpenAPSAMAPlugin @Inject constructor( // Fix bug determine basal if (determineBasalResultAMA == null) { aapsLogger.error(LTag.APS, "SMB calculation returned null") - lastDetermineBasalAdapterAMAJS = null + lastDetermineBasalAdapter = null lastAPSResult = null lastAPSRun = 0 } else { @@ -167,8 +167,8 @@ class OpenAPSAMAPlugin @Inject constructor( val now = System.currentTimeMillis() determineBasalResultAMA.json?.put("timestamp", dateUtil.toISOString(now)) determineBasalResultAMA.inputConstraints = inputConstraints - lastDetermineBasalAdapterAMAJS = determineBasalAdapterAMAJS - lastAPSResult = determineBasalResultAMA + lastDetermineBasalAdapter = determineBasalAdapterAMAJS + lastAPSResult = determineBasalResultAMA as DetermineBasalResultAMA lastAPSRun = now } rxBus.send(EventOpenAPSUpdateGui()) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt index 7dfbc0bf1e..33c5bb0aa4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.kt @@ -4,27 +4,19 @@ import dagger.android.HasAndroidInjector import info.nightscout.androidaps.R import info.nightscout.androidaps.data.IobTotal import info.nightscout.androidaps.data.MealData -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.database.entities.Bolus import info.nightscout.androidaps.extensions.convertedToAbsolute import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes import info.nightscout.androidaps.extensions.plannedRemainingMinutes -import info.nightscout.androidaps.interfaces.ActivePlugin -import info.nightscout.androidaps.interfaces.GlucoseUnit -import info.nightscout.androidaps.interfaces.IobCobCalculator -import info.nightscout.androidaps.interfaces.Profile -import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.interfaces.* import info.nightscout.shared.logging.AAPSLogger import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback +import info.nightscout.androidaps.plugins.aps.loop.APSResult import info.nightscout.androidaps.plugins.aps.loop.ScriptReader import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus -import info.nightscout.androidaps.utils.DateUtil import info.nightscout.shared.SafeParse -import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.stats.TddCalculator import info.nightscout.shared.sharedPreferences.SP import org.json.JSONArray import org.json.JSONException @@ -36,7 +28,7 @@ import java.lang.reflect.InvocationTargetException import java.nio.charset.StandardCharsets import javax.inject.Inject -class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) { +class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface { @Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var constraintChecker: ConstraintChecker @@ -45,10 +37,6 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: @Inject lateinit var profileFunction: ProfileFunction @Inject lateinit var iobCobCalculator: IobCobCalculator @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var repository: AppRepository - @Inject lateinit var dateUtil: DateUtil - //@Inject lateinit var danaPump: DanaPump - private var profile = JSONObject() private var mGlucoseStatus = JSONObject() @@ -60,26 +48,16 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: private var smbAlwaysAllowed = false private var currentTime: Long = 0 private var saveCgmSource = false - private var lastBolusNormalTime: Long = 0 - private val millsToThePast = T.hours(4).msecs() - private var tddAIMI: TddCalculator? = null - - var currentTempParam: String? = null - private set - var iobDataParam: String? = null - private set - var glucoseStatusParam: String? = null - private set - var profileParam: String? = null - private set - var mealDataParam: String? = null - private set - var scriptDebug = "" - private set + override var currentTempParam: String? = null + override var iobDataParam: String? = null + override var glucoseStatusParam: String? = null + override var profileParam: String? = null + override var mealDataParam: String? = null + override var scriptDebug = "" @Suppress("SpellCheckingInspection") - operator fun invoke(): DetermineBasalResultSMB? { + override operator fun invoke(): APSResult? { aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it }) aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it }) @@ -169,22 +147,24 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: return determineBasalResultSMB } - @Suppress("SpellCheckingInspection") fun setData(profile: Profile, - maxIob: Double, - maxBasal: Double, - minBg: Double, - maxBg: Double, - targetBg: Double, - basalRate: Double, - iobArray: Array, - glucoseStatus: GlucoseStatus, - mealData: MealData, - autosensDataRatio: Double, - tempTargetSet: Boolean, - microBolusAllowed: Boolean, - uamAllowed: Boolean, - advancedFiltering: Boolean, - isSaveCgmSource: Boolean + @Suppress("SpellCheckingInspection") + override fun setData( + profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean, + microBolusAllowed: Boolean, + uamAllowed: Boolean, + advancedFiltering: Boolean, + isSaveCgmSource: Boolean ) { val pump = activePlugin.activePump val pumpBolusStep = pump.pumpDescription.bolusStep @@ -255,7 +235,6 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: } else { mGlucoseStatus.put("delta", glucoseStatus.delta) } - mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta) mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta) mGlucoseStatus.put("date", glucoseStatus.date) @@ -264,13 +243,7 @@ class DetermineBasalAdapterSMBJS internal constructor(private val scriptReader: this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation) this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation) this.mealData.put("lastBolusTime", mealData.lastBolusTime) - this.mealData.put("lastBolusNormalTime", lastBolusNormalTime) this.mealData.put("lastCarbTime", mealData.lastCarbTime) - - tddAIMI = TddCalculator(aapsLogger,rh,activePlugin,profileFunction,dateUtil,iobCobCalculator, repository) - this.mealData.put("TDDAIMI7", tddAIMI!!.averageTDD(tddAIMI!!.calculate(7)).totalAmount) - this.mealData.put("TDDPUMP", tddAIMI!!.calculateDaily().totalAmount) - if (constraintChecker.isAutosensModeEnabled().value()) { autosensData.put("ratio", autosensDataRatio) } else { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt index b93170d2ac..785066e695 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBFragment.kt @@ -9,8 +9,7 @@ import android.view.ViewGroup import dagger.android.support.DaggerFragment import info.nightscout.androidaps.R import info.nightscout.androidaps.databinding.OpenapsamaFragmentBinding -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag +import info.nightscout.androidaps.interfaces.ActivePlugin import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui import info.nightscout.androidaps.plugins.bus.RxBus @@ -19,6 +18,8 @@ import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.JSONFormatter import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.rx.AapsSchedulers +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign import org.json.JSONArray @@ -34,7 +35,7 @@ class OpenAPSSMBFragment : DaggerFragment() { @Inject lateinit var rxBus: RxBus @Inject lateinit var rh: ResourceHelper @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var openAPSSMBPlugin: OpenAPSSMBPlugin + @Inject lateinit var activePlugin: ActivePlugin @Inject lateinit var dateUtil: DateUtil @Inject lateinit var jsonFormatter: JSONFormatter @@ -44,8 +45,7 @@ class OpenAPSSMBFragment : DaggerFragment() { // onDestroyView. private val binding get() = _binding!! - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = OpenapsamaFragmentBinding.inflate(inflater, container, false) return binding.root } @@ -54,7 +54,7 @@ class OpenAPSSMBFragment : DaggerFragment() { super.onViewCreated(view, savedInstanceState) binding.run.setOnClickListener { - openAPSSMBPlugin.invoke("OpenAPSSMB button", false) + activePlugin.activeAPS.invoke("OpenAPSSMB button", false) } } @@ -92,11 +92,12 @@ class OpenAPSSMBFragment : DaggerFragment() { @Synchronized fun updateGUI() { if (_binding == null) return + val openAPSSMBPlugin = activePlugin.activeAPS openAPSSMBPlugin.lastAPSResult?.let { lastAPSResult -> binding.result.text = jsonFormatter.format(lastAPSResult.json) binding.request.text = lastAPSResult.toSpanned() } - openAPSSMBPlugin.lastDetermineBasalAdapterSMBJS?.let { determineBasalAdapterSMBJS -> + openAPSSMBPlugin.lastDetermineBasalAdapter?.let { determineBasalAdapterSMBJS -> binding.glucosestatus.text = jsonFormatter.format(determineBasalAdapterSMBJS.glucoseStatusParam) binding.currenttemp.text = jsonFormatter.format(determineBasalAdapterSMBJS.currentTempParam) try { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt index 09e79999f4..873b0294e8 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/OpenAPSSMBPlugin.kt @@ -10,8 +10,6 @@ import info.nightscout.androidaps.database.AppRepository import info.nightscout.androidaps.database.ValueWrapper import info.nightscout.androidaps.extensions.target import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateResultGui import info.nightscout.androidaps.plugins.aps.loop.ScriptReader @@ -24,6 +22,8 @@ import info.nightscout.androidaps.utils.HardLimits import info.nightscout.androidaps.utils.Profiler import info.nightscout.androidaps.utils.Round import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag import info.nightscout.shared.sharedPreferences.SP import javax.inject.Inject import javax.inject.Singleton @@ -37,7 +37,7 @@ class OpenAPSSMBPlugin @Inject constructor( private val constraintChecker: ConstraintChecker, rh: ResourceHelper, private val profileFunction: ProfileFunction, - private val context: Context, + val context: Context, private val activePlugin: ActivePlugin, private val iobCobCalculator: IobCobCalculator, private val hardLimits: HardLimits, @@ -46,23 +46,24 @@ class OpenAPSSMBPlugin @Inject constructor( private val dateUtil: DateUtil, private val repository: AppRepository, private val glucoseStatusProvider: GlucoseStatusProvider -) : PluginBase(PluginDescription() - .mainType(PluginType.APS) - .fragmentClass(OpenAPSSMBFragment::class.java.name) - .pluginIcon(R.drawable.ic_generic_icon) - .pluginName(R.string.openapssmb) - .shortName(R.string.smb_shortname) - .preferencesId(R.xml.pref_openapssmb) - .description(R.string.description_smb) - .setDefault(), +) : PluginBase( + PluginDescription() + .mainType(PluginType.APS) + .fragmentClass(OpenAPSSMBFragment::class.java.name) + .pluginIcon(R.drawable.ic_generic_icon) + .pluginName(R.string.openapssmb) + .shortName(R.string.smb_shortname) + .preferencesId(R.xml.pref_openapssmb) + .description(R.string.description_smb) + .setDefault(), aapsLogger, rh, injector ), APS, Constraints { // last values override var lastAPSRun: Long = 0 override var lastAPSResult: DetermineBasalResultSMB? = null - var lastDetermineBasalAdapterSMBJS: DetermineBasalAdapterSMBJS? = null - var lastAutosensResult = AutosensResult() + override var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? = null + override var lastAutosensResult = AutosensResult() override fun specialEnableCondition(): Boolean { return try { @@ -120,15 +121,34 @@ class OpenAPSSMBPlugin @Inject constructor( }.value() var minBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetLowMgdl(), 0.1), R.string.profile_low_target, HardLimits.VERY_HARD_LIMIT_MIN_BG[0], HardLimits.VERY_HARD_LIMIT_MIN_BG[1]) - var maxBg = hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1]) + var maxBg = + hardLimits.verifyHardLimits(Round.roundTo(profile.getTargetHighMgdl(), 0.1), R.string.profile_high_target, HardLimits.VERY_HARD_LIMIT_MAX_BG[0], HardLimits.VERY_HARD_LIMIT_MAX_BG[1]) var targetBg = hardLimits.verifyHardLimits(profile.getTargetMgdl(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TARGET_BG[0], HardLimits.VERY_HARD_LIMIT_TARGET_BG[1]) var isTempTarget = false val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet() if (tempTarget is ValueWrapper.Existing) { isTempTarget = true - minBg = hardLimits.verifyHardLimits(tempTarget.value.lowTarget, R.string.temp_target_low_target, HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble()) - maxBg = hardLimits.verifyHardLimits(tempTarget.value.highTarget, R.string.temp_target_high_target, HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble()) - targetBg = hardLimits.verifyHardLimits(tempTarget.value.target(), R.string.temp_target_value, HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble()) + minBg = + hardLimits.verifyHardLimits( + tempTarget.value.lowTarget, + R.string.temp_target_low_target, + HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0].toDouble(), + HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1].toDouble() + ) + maxBg = + hardLimits.verifyHardLimits( + tempTarget.value.highTarget, + R.string.temp_target_high_target, + HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0].toDouble(), + HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1].toDouble() + ) + targetBg = + hardLimits.verifyHardLimits( + tempTarget.value.target(), + R.string.temp_target_value, + HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[0].toDouble(), + HardLimits.VERY_HARD_LIMIT_TEMP_TARGET_BG[1].toDouble() + ) } if (!hardLimits.checkHardLimits(profile.dia, R.string.profile_dia, hardLimits.minDia(), hardLimits.maxDia())) return if (!hardLimits.checkHardLimits(profile.getIcTimeFromMidnight(Profile.secondsFromMidnight()), R.string.profile_carbs_ratio_value, hardLimits.minIC(), hardLimits.maxIC())) return @@ -165,8 +185,9 @@ class OpenAPSSMBPlugin @Inject constructor( profiler.log(LTag.APS, "SMB data gathering", start) start = System.currentTimeMillis() - DetermineBasalAdapterSMBJS(ScriptReader(context), injector).also { determineBasalAdapterSMBJS -> - determineBasalAdapterSMBJS.setData(profile, maxIob, maxBasal, minBg, maxBg, targetBg, + provideDetermineBasalAdapter().also { determineBasalAdapterSMBJS -> + determineBasalAdapterSMBJS.setData( + profile, maxIob, maxBasal, minBg, maxBg, targetBg, activePlugin.activePump.baseBasalRate, iobArray, glucoseStatus, @@ -176,24 +197,26 @@ class OpenAPSSMBPlugin @Inject constructor( smbAllowed.value(), uam.value(), advancedFiltering.value(), - activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin") + activePlugin.activeBgSource.javaClass.simpleName == "DexcomPlugin" + ) val now = System.currentTimeMillis() val determineBasalResultSMB = determineBasalAdapterSMBJS.invoke() profiler.log(LTag.APS, "SMB calculation", start) if (determineBasalResultSMB == null) { aapsLogger.error(LTag.APS, "SMB calculation returned null") - lastDetermineBasalAdapterSMBJS = null + lastDetermineBasalAdapter = null lastAPSResult = null lastAPSRun = 0 } else { // TODO still needed with oref1? // Fix bug determine basal - if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested = false + if (determineBasalResultSMB.rate == 0.0 && determineBasalResultSMB.duration == 0 && iobCobCalculator.getTempBasalIncludingConvertedExtended(dateUtil.now()) == null) determineBasalResultSMB.tempBasalRequested = + false determineBasalResultSMB.iob = iobArray[0] determineBasalResultSMB.json?.put("timestamp", dateUtil.toISOString(now)) determineBasalResultSMB.inputConstraints = inputConstraints - lastDetermineBasalAdapterSMBJS = determineBasalAdapterSMBJS - lastAPSResult = determineBasalResultSMB + lastDetermineBasalAdapter = determineBasalAdapterSMBJS + lastAPSResult = determineBasalResultSMB as DetermineBasalResultSMB lastAPSRun = now } } @@ -204,4 +227,6 @@ class OpenAPSSMBPlugin @Inject constructor( value.set(aapsLogger, false) return value } + + fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBJS(ScriptReader(context), injector) } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/DetermineBasalAdapterSMBAutoISFJS.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/DetermineBasalAdapterSMBAutoISFJS.kt new file mode 100644 index 0000000000..903ea04909 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/DetermineBasalAdapterSMBAutoISFJS.kt @@ -0,0 +1,298 @@ +package info.nightscout.androidaps.plugins.aps.openAPSSMBAutoISF + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.MealData +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.extensions.convertedToAbsolute +import info.nightscout.androidaps.extensions.getPassedDurationToTimeInMinutes +import info.nightscout.androidaps.extensions.plannedRemainingMinutes +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.GlucoseUnit +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.Profile +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.aps.logger.LoggerCallback +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface +import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.stats.TddCalculator +import info.nightscout.shared.SafeParse +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import info.nightscout.shared.sharedPreferences.SP +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mozilla.javascript.* +import org.mozilla.javascript.Function +import java.io.IOException +import java.lang.reflect.InvocationTargetException +import java.nio.charset.StandardCharsets +import javax.inject.Inject + +class DetermineBasalAdapterSMBAutoISFJS internal constructor(private val scriptReader: ScriptReader, private val injector: HasAndroidInjector) : DetermineBasalAdapterInterface { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var constraintChecker: ConstraintChecker + @Inject lateinit var sp: SP + @Inject lateinit var rh: ResourceHelper + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var iobCobCalculator: IobCobCalculator + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var repository: AppRepository + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var tddCalculator: TddCalculator + + private var profile = JSONObject() + private var mGlucoseStatus = JSONObject() + private var iobData: JSONArray? = null + private var mealData = JSONObject() + private var currentTemp = JSONObject() + private var autosensData = JSONObject() + private var microBolusAllowed = false + private var smbAlwaysAllowed = false + private var currentTime: Long = 0 + private var saveCgmSource = false + private var lastBolusNormalTime: Long = 0 + + override var currentTempParam: String? = null + override var iobDataParam: String? = null + override var glucoseStatusParam: String? = null + override var profileParam: String? = null + override var mealDataParam: String? = null + override var scriptDebug = "" + + @Suppress("SpellCheckingInspection") + override operator fun invoke(): DetermineBasalResultSMB? { + aapsLogger.debug(LTag.APS, ">>> Invoking determine_basal <<<") + aapsLogger.debug(LTag.APS, "Glucose status: " + mGlucoseStatus.toString().also { glucoseStatusParam = it }) + aapsLogger.debug(LTag.APS, "IOB data: " + iobData.toString().also { iobDataParam = it }) + aapsLogger.debug(LTag.APS, "Current temp: " + currentTemp.toString().also { currentTempParam = it }) + aapsLogger.debug(LTag.APS, "Profile: " + profile.toString().also { profileParam = it }) + aapsLogger.debug(LTag.APS, "Meal data: " + mealData.toString().also { mealDataParam = it }) + aapsLogger.debug(LTag.APS, "Autosens data: $autosensData") + aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined") + aapsLogger.debug(LTag.APS, "MicroBolusAllowed: $microBolusAllowed") + aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: $smbAlwaysAllowed") + aapsLogger.debug(LTag.APS, "CurrentTime: $currentTime") + aapsLogger.debug(LTag.APS, "isSaveCgmSource: $saveCgmSource") + var determineBasalResultSMB: DetermineBasalResultSMB? = null + val rhino = Context.enter() + val scope: Scriptable = rhino.initStandardObjects() + // Turn off optimization to make Rhino Android compatible + rhino.optimizationLevel = -1 + try { + + //register logger callback for console.log and console.error + ScriptableObject.defineClass(scope, LoggerCallback::class.java) + val 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("OpenAPSSMBAutoISF/determine-basal.js"), "JavaScript", 0, null) + rhino.evaluateString(scope, readFile("OpenAPSSMB/basal-set-temp.js"), "setTempBasal.js", 0, null) + val determineBasalObj = scope["determine_basal", scope] + val setTempBasalFunctionsObj = scope["tempBasalFunctions", scope] + + //call determine-basal + if (determineBasalObj is Function && setTempBasalFunctionsObj is NativeObject) { + + //prepare parameters + val params = arrayOf( + makeParam(mGlucoseStatus, rhino, scope), + makeParam(currentTemp, rhino, scope), + makeParamArray(iobData, rhino, scope), + makeParam(profile, rhino, scope), + makeParam(autosensData, rhino, scope), + makeParam(mealData, rhino, scope), + setTempBasalFunctionsObj, + java.lang.Boolean.valueOf(microBolusAllowed), + makeParam(null, rhino, scope), // reservoir data as undefined + java.lang.Long.valueOf(currentTime), + java.lang.Boolean.valueOf(saveCgmSource) + ) + val jsResult = determineBasalObj.call(rhino, scope, scope, params) as NativeObject + scriptDebug = LoggerCallback.scriptDebug + + // Parse the jsResult object to a JSON-String + val result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString() + aapsLogger.debug(LTag.APS, "Result: $result") + try { + val resultJson = JSONObject(result) + determineBasalResultSMB = DetermineBasalResultSMB(injector, resultJson) + } catch (e: JSONException) { + aapsLogger.error(LTag.APS, "Unhandled exception", e) + } + } else { + aapsLogger.error(LTag.APS, "Problem loading JS Functions") + } + } catch (e: IOException) { + aapsLogger.error(LTag.APS, "IOException") + } catch (e: RhinoException) { + aapsLogger.error(LTag.APS, "RhinoException: (" + e.lineNumber() + "," + e.columnNumber() + ") " + e.toString()) + } catch (e: IllegalAccessException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InstantiationException) { + aapsLogger.error(LTag.APS, e.toString()) + } catch (e: InvocationTargetException) { + aapsLogger.error(LTag.APS, e.toString()) + } finally { + Context.exit() + } + glucoseStatusParam = mGlucoseStatus.toString() + iobDataParam = iobData.toString() + currentTempParam = currentTemp.toString() + profileParam = profile.toString() + mealDataParam = mealData.toString() + return determineBasalResultSMB + } + + @Suppress("SpellCheckingInspection") + override fun setData( + profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean, + microBolusAllowed: Boolean, + uamAllowed: Boolean, + advancedFiltering: Boolean, + isSaveCgmSource: Boolean + ) { + val pump = activePlugin.activePump + val pumpBolusStep = pump.pumpDescription.bolusStep + this.profile.put("max_iob", maxIob) + //mProfile.put("dia", profile.getDia()); + this.profile.put("type", "current") + this.profile.put("max_daily_basal", profile.getMaxDailyBasal()) + this.profile.put("max_basal", maxBasal) + this.profile.put("min_bg", minBg) + this.profile.put("max_bg", maxBg) + this.profile.put("target_bg", targetBg) + this.profile.put("carb_ratio", profile.getIc()) + this.profile.put("sens", profile.getIsfMgdl()) + this.profile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)) + this.profile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4.0)) + + //mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)); + this.profile.put("high_temptarget_raises_sensitivity", false) + //mProfile.put("low_temptarget_lowers_sensitivity", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity)); + this.profile.put("low_temptarget_lowers_sensitivity", false) + this.profile.put("sensitivity_raises_target", sp.getBoolean(R.string.key_sensitivity_raises_target, SMBDefaults.sensitivity_raises_target)) + this.profile.put("resistance_lowers_target", sp.getBoolean(R.string.key_resistance_lowers_target, SMBDefaults.resistance_lowers_target)) + this.profile.put("adv_target_adjustments", SMBDefaults.adv_target_adjustments) + this.profile.put("exercise_mode", SMBDefaults.exercise_mode) + this.profile.put("half_basal_exercise_target", SMBDefaults.half_basal_exercise_target) + this.profile.put("maxCOB", SMBDefaults.maxCOB) + this.profile.put("skip_neutral_temps", pump.setNeutralTempAtFullHour()) + // min_5m_carbimpact is not used within SMB determinebasal + //if (mealData.usedMinCarbsImpact > 0) { + // mProfile.put("min_5m_carbimpact", mealData.usedMinCarbsImpact); + //} else { + // mProfile.put("min_5m_carbimpact", SP.getDouble(R.string.key_openapsama_min_5m_carbimpact, SMBDefaults.min_5m_carbimpact)); + //} + this.profile.put("remainingCarbsCap", SMBDefaults.remainingCarbsCap) + this.profile.put("enableUAM", uamAllowed) + this.profile.put("A52_risk_enable", SMBDefaults.A52_risk_enable) + val smbEnabled = sp.getBoolean(R.string.key_use_smb, false) + this.profile.put("SMBInterval", sp.getInt(R.string.key_smbinterval, SMBDefaults.SMBInterval)) + this.profile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false)) + this.profile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false)) + this.profile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)) + this.profile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering) + this.profile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering) + this.profile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes)) + this.profile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes)) + //set the min SMB amount to be the amount set by the pump. + this.profile.put("bolus_increment", pumpBolusStep) + this.profile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold)) + this.profile.put("current_basal", basalRate) + this.profile.put("temptargetSet", tempTargetSet) + this.profile.put("autosens_max", SafeParse.stringToDouble(sp.getString(R.string.key_openapsama_autosens_max, "1.2"))) + if (profileFunction.getUnits() == GlucoseUnit.MMOL) { + this.profile.put("out_units", "mmol/L") + } + val now = System.currentTimeMillis() + val tb = iobCobCalculator.getTempBasalIncludingConvertedExtended(now) + currentTemp.put("temp", "absolute") + currentTemp.put("duration", tb?.plannedRemainingMinutes ?: 0) + currentTemp.put("rate", tb?.convertedToAbsolute(now, profile) ?: 0.0) + // as we have non default temps longer than 30 mintues + if (tb != null) currentTemp.put("minutesrunning", tb.getPassedDurationToTimeInMinutes(now)) + + iobData = iobCobCalculator.convertToJSONArray(iobArray) + mGlucoseStatus.put("glucose", glucoseStatus.glucose) + mGlucoseStatus.put("noise", glucoseStatus.noise) + if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { + mGlucoseStatus.put("delta", glucoseStatus.shortAvgDelta) + } else { + mGlucoseStatus.put("delta", glucoseStatus.delta) + } + + mGlucoseStatus.put("short_avgdelta", glucoseStatus.shortAvgDelta) + mGlucoseStatus.put("long_avgdelta", glucoseStatus.longAvgDelta) + mGlucoseStatus.put("date", glucoseStatus.date) + this.mealData.put("carbs", mealData.carbs) + this.mealData.put("mealCOB", mealData.mealCOB) + this.mealData.put("slopeFromMaxDeviation", mealData.slopeFromMaxDeviation) + this.mealData.put("slopeFromMinDeviation", mealData.slopeFromMinDeviation) + this.mealData.put("lastBolusTime", mealData.lastBolusTime) + this.mealData.put("lastBolusNormalTime", lastBolusNormalTime) + this.mealData.put("lastCarbTime", mealData.lastCarbTime) + + this.mealData.put("TDDAIMI7", tddCalculator.averageTDD(tddCalculator.calculate(7)).totalAmount) + this.mealData.put("TDDPUMP", tddCalculator.calculateDaily().totalAmount) + + if (constraintChecker.isAutosensModeEnabled().value()) { + autosensData.put("ratio", autosensDataRatio) + } else { + autosensData.put("ratio", 1.0) + } + this.microBolusAllowed = microBolusAllowed + smbAlwaysAllowed = advancedFiltering + currentTime = now + saveCgmSource = isSaveCgmSource + } + + private fun makeParam(jsonObject: JSONObject?, rhino: Context, scope: Scriptable): Any { + return if (jsonObject == null) Undefined.instance + else NativeJSON.parse(rhino, scope, jsonObject.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + private fun makeParamArray(jsonArray: JSONArray?, rhino: Context, scope: Scriptable): Any { + return NativeJSON.parse(rhino, scope, jsonArray.toString()) { _: Context?, _: Scriptable?, _: Scriptable?, objects: Array -> objects[1] } + } + + @Throws(IOException::class) private fun readFile(filename: String): String { + val bytes = scriptReader.readFile(filename) + var string = String(bytes, StandardCharsets.UTF_8) + if (string.startsWith("#!/usr/bin/env node")) { + string = string.substring(20) + } + return string + } + + init { + injector.androidInjector().inject(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/OpenAPSSMBAutoISFPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/OpenAPSSMBAutoISFPlugin.kt new file mode 100644 index 0000000000..53cf83f775 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMBAutoISF/OpenAPSSMBAutoISFPlugin.kt @@ -0,0 +1,74 @@ +package info.nightscout.androidaps.plugins.aps.openAPSSMBAutoISF + +import android.content.Context +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.annotations.OpenForTesting +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.interfaces.ActivePlugin +import info.nightscout.androidaps.interfaces.IobCobCalculator +import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.aps.loop.ScriptReader +import info.nightscout.androidaps.interfaces.DetermineBasalAdapterInterface +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.HardLimits +import info.nightscout.androidaps.utils.Profiler +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.sharedPreferences.SP +import javax.inject.Inject +import javax.inject.Singleton + +@OpenForTesting +@Singleton +class OpenAPSSMBAutoISFPlugin @Inject constructor( + injector: HasAndroidInjector, + aapsLogger: AAPSLogger, + rxBus: RxBus, + constraintChecker: ConstraintChecker, + rh: ResourceHelper, + profileFunction: ProfileFunction, + context: Context, + activePlugin: ActivePlugin, + iobCobCalculator: IobCobCalculator, + hardLimits: HardLimits, + profiler: Profiler, + sp: SP, + dateUtil: DateUtil, + repository: AppRepository, + glucoseStatusProvider: GlucoseStatusProvider, + private val buildHelper: BuildHelper +) : OpenAPSSMBPlugin( + injector, + aapsLogger, + rxBus, + constraintChecker, + rh, + profileFunction, + context, + activePlugin, + iobCobCalculator, + hardLimits, + profiler, + sp, + dateUtil, + repository, + glucoseStatusProvider +) { + + init { + pluginDescription + .pluginName(R.string.openapssmbautoisf) + .description(R.string.description_smb_auto_isf) + .setDefault(false) + } + + override fun specialEnableCondition(): Boolean = buildHelper.isEngineeringMode() && buildHelper.isDev() + + override fun provideDetermineBasalAdapter(): DetermineBasalAdapterInterface = DetermineBasalAdapterSMBAutoISFJS(ScriptReader(context), injector) +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt index 6eb83d2e8f..babf220943 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt @@ -96,7 +96,6 @@ class TddCalculator @Inject constructor( //result.put(midnight, tdd) } val calculationStep = T.mins(5).msecs() - val tempBasals = iobCobCalculator.getTempBasalIncludingConvertedExtendedForRange(startTime, endTime, calculationStep) for (t in startTime until endTime step calculationStep) { //val midnight = MidnightTime.calc(t) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a02e78274..6f048b092b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,7 @@ Synchronizes your data with Nightscout State of the algorithm in 2017 Most recent algorithm for advanced users + Most recent algorithm for advanced users with automatic ISF Displays the current state of your loop and buttons for most common actions Shows an ongoing notification with a short overview of what your loop is doing Define a profile which is available offline. @@ -535,6 +536,7 @@ Enable broadcasts to other apps (like xDrip+). Do not enable if you have more than one instance of AAPS or NSClient installed! Enable local Broadcasts. OpenAPS SMB + OpenAPS SMB Auto ISF use_smb use_uam smb_enable_carbs_suggestions_threshold diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/APS.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/APS.kt index cb2c2b4005..6302b76ebd 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/APS.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/APS.kt @@ -1,10 +1,14 @@ package info.nightscout.androidaps.interfaces import info.nightscout.androidaps.plugins.aps.loop.APSResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult interface APS { val lastAPSResult: APSResult? val lastAPSRun: Long + var lastDetermineBasalAdapter: DetermineBasalAdapterInterface? + var lastAutosensResult: AutosensResult + operator fun invoke(initiator: String, tempBasalFallback: Boolean) } \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/DetermineBasalAdapterInterface.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/DetermineBasalAdapterInterface.kt new file mode 100644 index 0000000000..4e1ba57422 --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/DetermineBasalAdapterInterface.kt @@ -0,0 +1,36 @@ +package info.nightscout.androidaps.interfaces + +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.MealData +import info.nightscout.androidaps.plugins.aps.loop.APSResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus + +interface DetermineBasalAdapterInterface { + + var currentTempParam: String? + var iobDataParam: String? + var glucoseStatusParam: String? + var profileParam: String? + var mealDataParam: String? + var scriptDebug: String + + fun setData(profile: Profile, + maxIob: Double, + maxBasal: Double, + minBg: Double, + maxBg: Double, + targetBg: Double, + basalRate: Double, + iobArray: Array, + glucoseStatus: GlucoseStatus, + mealData: MealData, + autosensDataRatio: Double, + tempTargetSet: Boolean, + microBolusAllowed: Boolean = false, + uamAllowed: Boolean = false, + advancedFiltering: Boolean = false, + isSaveCgmSource: Boolean = false + ) {} + + operator fun invoke(): APSResult? +} \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.kt index 156fa1781c..9fa484c162 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/PluginDescription.kt @@ -35,5 +35,5 @@ class PluginDescription { fun enableByDefault(enableByDefault: Boolean): PluginDescription = this.also { it.enableByDefault = enableByDefault } fun visibleByDefault(visibleByDefault: Boolean): PluginDescription = this.also { it.visibleByDefault = visibleByDefault } fun description(description: Int): PluginDescription = this.also { it.description = description } - fun setDefault(): PluginDescription = this.also { it.defaultPlugin = true } + fun setDefault(value: Boolean = true): PluginDescription = this.also { it.defaultPlugin = value } } \ No newline at end of file