diff --git a/app/src/main/assets/OpenAPSSMB/determine-basal.js b/app/src/main/assets/OpenAPSSMB/determine-basal.js index c2db0f270b..00a9c1d0a2 100644 --- a/app/src/main/assets/OpenAPSSMB/determine-basal.js +++ b/app/src/main/assets/OpenAPSSMB/determine-basal.js @@ -31,14 +31,13 @@ function calculate_expected_delta(target_bg, eventual_bg, bgi) { // (hours * mins_per_hour) / 5 = how many 5 minute periods in 2h = 24 var five_min_blocks = (2 * 60) / 5; var target_delta = target_bg - eventual_bg; - var expectedDelta = round(bgi + (target_delta / five_min_blocks), 1); - return expectedDelta; + return /* expectedDelta */ round(bgi + (target_delta / five_min_blocks), 1); } function convert_bg(value, profile) { - if (profile.out_units == "mmol/L") + if (profile.out_units === "mmol/L") { return round(value / 18, 1).toFixed(1); } @@ -48,10 +47,76 @@ function convert_bg(value, profile) } } -var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, tempBasalFunctions, microBolusAllowed, reservoir_data) { +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) { 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'; @@ -61,26 +126,41 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ 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; - if (bg < 39) { //Dexcom is in ??? mode or calibrating - rT.reason = "CGM is calibrating or in ??? state"; + 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 ) { + 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"; + } } - if (bg < 39 || minAgo > 12 || minAgo < -5) { - if (currenttemp.rate >= basal) { // high temp is running - rT.reason += ". Canceling high temp basal of "+currenttemp.rate; + //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 ) ) { + 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 = 0; - rT.rate = 0; + 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 + } 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'; @@ -115,14 +195,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var sensitivityRatio; var high_temptarget_raises_sensitivity = profile.exercise_mode || profile.high_temptarget_raises_sensitivity; - var normalTarget = 100; // evaluate high/low temptarget against 100, not scheduled basal (which might change) + 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 { - var halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%) + halfBasalTarget = 160; // when temptarget is 160 mg/dL, run 50% basal (120 = 75%; 140 = 60%) // 80 mg/dL with low_temptarget_lowers_sensitivity would give 1.5x basal, but is limited to autosens_max (1.2x by default) } - if ( high_temptarget_raises_sensitivity && profile.temptargetSet && target_bg > normalTarget + 10 + 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 @@ -132,36 +212,36 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // limit sensitivityRatio to profile.autosens_max (1.2x by default) sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); sensitivityRatio = round(sensitivityRatio,2); - console.error("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); - } else if (typeof autosens_data !== 'undefined' ) { + 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.error("Autosens ratio: "+sensitivityRatio+"; "); + console.log("Autosens ratio: "+sensitivityRatio+"; "); } if (sensitivityRatio) { basal = profile.current_basal * sensitivityRatio; basal = round_basal(basal, profile); - if (basal != profile_current_basal) { - console.error("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); + if (basal !== profile_current_basal) { + console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); } else { - console.error("Basal unchanged: "+basal+"; "); + 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.error("Temp Target set, not adjusting with autosens; "); - } else if (typeof autosens_data !== 'undefined' ) { + //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; - new_target_bg = round((target_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.error("target_bg unchanged: "+new_target_bg+"; "); + if (target_bg === new_target_bg) { + console.log("target_bg unchanged: "+new_target_bg+"; "); } else { - console.error("target_bg from "+target_bg+" to "+new_target_bg+"; "); + console.log("target_bg from "+target_bg+" to "+new_target_bg+"; "); } target_bg = new_target_bg; } @@ -197,34 +277,33 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var profile_sens = round(profile.sens,1) var sens = profile.sens; - if (typeof autosens_data !== 'undefined' ) { + if (typeof autosens_data !== 'undefined' && autosens_data) { sens = profile.sens / sensitivityRatio; sens = round(sens, 1); - if (sens != profile_sens) { - console.error("ISF from "+profile_sens+" to "+sens); + if (sens !== profile_sens) { + console.log("ISF from "+profile_sens+" to "+sens); } else { - console.error("ISF unchanged: "+sens); + console.log("ISF unchanged: "+sens); } - //console.error(" (autosens ratio "+sensitivityRatio+")"); + //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 var lastTempAge; if (typeof iob_data.lastTemp !== 'undefined' ) { - lastTempAge = round(( new Date().getTime() - iob_data.lastTemp.date ) / 60000); // in minutes - // } ---- added to not produce errors + 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"); - tempModulus = (lastTempAge + currenttemp.duration) % 30; + 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 ) { - rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; setting neutral temp of "+basal+"."; - return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); + if ( 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 @@ -234,10 +313,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ //} //console.error(lastTempAge, round(iob_data.lastTemp.duration,1), round(lastTempAge - iob_data.lastTemp.duration,1)); var lastTempEnded = lastTempAge - iob_data.lastTemp.duration - if ( lastTempEnded > 5 ) { - rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; setting neutral temp of "+basal+"."; + 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(basal, 30, profile, rT, currenttemp); + 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 ) { @@ -264,37 +343,44 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ if (iob_data.iob > 0) { var naive_eventualBG = round( bg - (iob_data.iob * sens) ); } else { // if IOB is negative, be more conservative and use the lower of sens, profile.sens - var naive_eventualBG = round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); + naive_eventualBG = round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); } // and adjust it for the deviation above var eventualBG = naive_eventualBG + deviation; - // calculate what portion of that is due to bolussnooze - //var bolusContrib = iob_data.bolussnooze * sens; - // and add it back in to get snoozeBG, plus another 50% to avoid low-temping at mealtime - //var naive_snoozeBG = round( naive_eventualBG + 1.5 * bolusContrib ); - // adjust that for deviation like we did eventualBG - //var snoozeBG = naive_snoozeBG + deviation; - // adjust target BG range if needed to safely bring down high BG faster without causing lows - if ( bg > max_bg && profile.adv_target_adjustments && ! profile.temptargetSet ) { + // 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 - var adjustedMinBG = round(Math.max(80, min_bg - (bg - min_bg)/3 ),0); - var adjustedTargetBG =round( Math.max(80, target_bg - (bg - target_bg)/3 ),0); - var adjustedMaxBG = round(Math.max(80, max_bg - (bg - max_bg)/3 ),0); + 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.error("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; "); + console.log("Adjusting targets for high BG: min_bg from "+min_bg+" to "+adjustedMinBG+"; "); min_bg = adjustedMinBG; } else { - console.error("min_bg unchanged: "+min_bg+"; "); + 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.error("target_bg from "+target_bg+" to "+adjustedTargetBG+"; "); + console.log("target_bg from "+target_bg+" to "+adjustedTargetBG+"; "); target_bg = adjustedTargetBG; } else { - console.error("target_bg unchanged: "+target_bg+"; "); + 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) { @@ -321,7 +407,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ , 'bg': bg , 'tick': tick , 'eventualBG': eventualBG - //, 'snoozeBG': snoozeBG , 'insulinReq': 0 , 'reservoir' : reservoir_data // The expected reservoir volume at which to deliver the microbolus (the reservoir volume from right before the last pumphistory run) , 'deliverAt' : deliverAt // The time at which the microbolus should be delivered @@ -341,71 +426,13 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ ZTpredBGs.push(bg); UAMpredBGs.push(bg); - // enable SMB whenever we have COB or UAM is enabled - // SMB is disabled by default, unless explicitly enabled in preferences.json - var enableSMB=false; - // disable SMB when a high temptarget is set - if (! microBolusAllowed) { - console.error("SMB disabled (!microBolusAllowed)") - } else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) { - console.error("SMB disabled due to high temptarget of",target_bg); - enableSMB=false; - // enable SMB/UAM (if enabled in preferences) while we have COB - } else if (profile.enableSMB_with_COB === true && meal_data.mealCOB) { - if (meal_data.bwCarbs) { - if (profile.A52_risk_enable) { - console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") - enableSMB=true; - } else { - console.error("SMB not enabled for Bolus Wizard COB"); - } - } else { - console.error("SMB enabled for COB of",meal_data.mealCOB); - enableSMB=true; - } - // enable SMB/UAM (if enabled in preferences) for a full 6 hours after any carb entry - // (6 hours is defined in carbWindow in lib/meal/total.js) - } else if (profile.enableSMB_after_carbs === true && meal_data.carbs ) { - if (meal_data.bwCarbs) { - if (profile.A52_risk_enable) { - console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard") - enableSMB=true; - } else { - console.error("SMB not enabled for Bolus Wizard carbs"); - } - } else { - console.error("SMB enabled for 6h after carb entry"); - enableSMB=true; - } - // enable SMB/UAM (if enabled in preferences) if a low temptarget is set - } else if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) { - if (meal_data.bwFound) { - if (profile.A52_risk_enable) { - console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") - enableSMB=true; - } else { - console.error("enableSMB_with_temptarget not supported within 6h of using Bolus Wizard"); - } - } else { - console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile)); - enableSMB=true; - } - // enable SMB/UAM if always-on (unless previously disabled for high temptarget) - } else if (profile.enableSMB_always === true) { - if (meal_data.bwFound) { - if (profile.A52_risk_enable === true) { - console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard") - enableSMB=true; - } else { - console.error("enableSMB_always not supported within 6h of using Bolus Wizard"); - } - } else { - console.error("SMB enabled due to enableSMB_always"); - enableSMB=true; - } - } else { - console.error("SMB disabled (no enableSMB preferences active)"); - } + var enableSMB = enable_smb( + profile, + microBolusAllowed, + meal_data, + target_bg + ); + // enable UAM (if enabled in preferences) var enableUAM=(profile.enableUAM); @@ -417,43 +444,48 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // calculate current carb absorption rate, and how long to absorb all carbs // CI = current carb impact on BG in mg/dL/5m ci = round((minDelta - bgi),1); - uci = round((minDelta - bgi),1); + var uci = round((minDelta - bgi),1); // ISF (mg/dL/U) / CR (g/U) = CSF (mg/dL/g) - if (profile.temptargetSet) { + + // 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 { + //var csf = profile.sens / profile.carb_ratio; + //} else { // otherwise, use autosens-adjusted sens to counteract autosens meal insulin dosing adjustments // so that autotuned CR is still in effect even when basals and ISF are being adjusted by autosens - var csf = sens / profile.carb_ratio; - } + //var 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 - maxCI = round(maxCarbAbsorptionRate*csf*5/60,1) + 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; } - // set meal_carbimpact high enough to absorb all meal carbs over 6 hours - // total_impact (mg/dL) = CSF (mg/dL/g) * carbs (g) - //console.error(csf * meal_data.carbs); - // meal_carbimpact (mg/dL/5m) = CSF (mg/dL/g) * carbs (g) / 6 (h) * (1h/60m) * 5 (m/5m) * 2 (for linear decay) - //var meal_carbimpact = round((csf * meal_data.carbs / 6 / 60 * 5 * 2),1) - var remainingCATimeMin = 3; // h; before carb absorption starts - // adjust remainingCATime (instead of CR) for autosens - remainingCATimeMin = remainingCATimeMin / sensitivityRatio; + 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; // added by mike https://github.com/openaps/oref0/issues/884 + 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().getTime() - meal_data.lastCarbTime ) / 60000); + var lastCarbAge = round(( new Date(systemTime).getTime() - meal_data.lastCarbTime ) / 60000); //console.error(meal_data.lastCarbTime, lastCarbAge); - fractionCOBAbsorbed = ( meal_data.carbs - meal_data.mealCOB ) / meal_data.carbs; + 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) @@ -478,7 +510,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // remainingCIpeak (mg/dL/5m) = remainingCarbs (g) * CSF (mg/dL/g) * 5 (m/5m) * 1h/60m / (remainingCATime/2) (h) var remainingCIpeak = remainingCarbs * csf * 5 / 60 / (remainingCATime/2); //console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI,remainingCATime); - //if (meal_data.mealCOB * 3 > meal_data.carbs) { } // calculate peak deviation in last hour, and slope from that to current deviation var slopeFromMaxDeviation = round(meal_data.slopeFromMaxDeviation,2); @@ -488,17 +519,17 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var slopeFromDeviations = Math.min(slopeFromMaxDeviation,-slopeFromMinDeviation/3); //console.error(slopeFromMaxDeviation); - aci = 10; + 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) { + if (ci === 0) { // avoid divide by zero cid = 0; } else { cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci )); } - acid = Math.max(0, meal_data.mealCOB * csf / aci ); + 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"); @@ -529,18 +560,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ try { iobArray.forEach(function(iobTick) { //console.error(iobTick); - predBGI = round(( -iobTick.activity * sens * 5 ), 2); - predZTBGI = round(( -iobTick.iobWithZeroTemp.activity * sens * 5 ), 2); + 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) - predDev = ci * ( 1 - Math.min(1,IOBpredBGs.length/(60/5)) ); + 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 - ZTpredBG = ZTpredBGs[ZTpredBGs.length-1] + predZTBGI; + 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) - predCI = Math.max(0, Math.max(0,ci) * ( 1 - COBpredBGs.length/Math.max(cid*2,1) ) ); - predACI = Math.max(0, Math.max(0,aci) * ( 1 - COBpredBGs.length/Math.max(acid*2,1) ) ); + 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) @@ -549,18 +580,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ remainingCItotal += predCI+remainingCI; remainingCIs.push(round(remainingCI,0)); predCIs.push(round(predCI,0)); - //console.error(round(predCI,1)+"+"+round(remainingCI,1)+" "); + //console.log(round(predCI,1)+"+"+round(remainingCI,1)+" "); COBpredBG = COBpredBGs[COBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predCI + remainingCI; - aCOBpredBG = aCOBpredBGs[aCOBpredBGs.length-1] + predBGI + Math.min(0,predDev) + predACI; + 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 - predUCIslope = Math.max(0, uci + ( UAMpredBGs.length*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) - predUCImax = Math.max(0, uci * ( 1 - UAMpredBGs.length/Math.max(3*60/5,1) ) ); + 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 - predUCI = Math.min(predUCIslope, predUCImax); + var predUCI = Math.min(predUCIslope, predUCImax); if(predUCI>0) { //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); UAMduration=round((UAMpredBGs.length+1)*5/60,1); @@ -582,7 +613,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // set minPredBGs starting when currently-dosed insulin activity will peak // look ahead 60m (regardless of insulin type) so as to be less aggressive on slower insulins var insulinPeakTime = 60; - // add 30m to allow for insluin delivery (SMBs or temps) + // 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); @@ -599,19 +630,18 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // set eventualBG to include effect of carbs //console.error("PredBGs:",JSON.stringify(predBGs)); } catch (e) { - console.error("Problem with iobArray. Optional feature Advanced Meal Assist disabled:",e); + 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(" ")); } - //,"totalCA:",round(totalCA,2),"remainingCItotal/csf+totalCA:",round(remainingCItotal/csf+totalCA,2)); rT.predBGs = {}; IOBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); for (var i=IOBpredBGs.length-1; i > 12; i--) { - if (IOBpredBGs[i-1] != IOBpredBGs[i]) { break; } + if (IOBpredBGs[i-1] !== IOBpredBGs[i]) { break; } else { IOBpredBGs.pop(); } } rT.predBGs.IOB = IOBpredBGs; @@ -619,10 +649,9 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ ZTpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); - for (var i=ZTpredBGs.length-1; i > 6; i--) { - //if (ZTpredBGs[i-1] != ZTpredBGs[i]) { break; } + 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; } + if (ZTpredBGs[i-1] >= ZTpredBGs[i] || ZTpredBGs[i] <= target_bg) { break; } else { ZTpredBGs.pop(); } } rT.predBGs.ZT = ZTpredBGs; @@ -631,19 +660,17 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ aCOBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); - for (var i=aCOBpredBGs.length-1; i > 12; i--) { - if (aCOBpredBGs[i-1] != aCOBpredBGs[i]) { break; } + for (i=aCOBpredBGs.length-1; i > 12; i--) { + if (aCOBpredBGs[i-1] !== aCOBpredBGs[i]) { break; } else { aCOBpredBGs.pop(); } } - // disable for now. may want to add a preference to re-enable - //rT.predBGs.aCOB = aCOBpredBGs; } if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { COBpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); - for (var i=COBpredBGs.length-1; i > 12; i--) { - if (COBpredBGs[i-1] != COBpredBGs[i]) { break; } + for (i=COBpredBGs.length-1; i > 12; i--) { + if (COBpredBGs[i-1] !== COBpredBGs[i]) { break; } else { COBpredBGs.pop(); } } rT.predBGs.COB = COBpredBGs; @@ -655,8 +682,8 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ UAMpredBGs.forEach(function(p, i, theArray) { theArray[i] = round(Math.min(401,Math.max(39,p))); }); - for (var i=UAMpredBGs.length-1; i > 12; i--) { - if (UAMpredBGs[i-1] != UAMpredBGs[i]) { break; } + for (i=UAMpredBGs.length-1; i > 12; i--) { + if (UAMpredBGs[i-1] !== UAMpredBGs[i]) { break; } else { UAMpredBGs.pop(); } } rT.predBGs.UAM = UAMpredBGs; @@ -666,7 +693,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } } - // set eventualBG and snoozeBG based on COB or UAM predBGs + // set eventualBG based on COB or UAM predBGs rT.eventualBG = eventualBG; } @@ -733,14 +760,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ //console.error("minUAMPredBG:",minUAMPredBG,"minZTGuardBG:",minZTGuardBG,"minZTUAMPredBG:",minZTUAMPredBG); // if any carbs have been entered recently if (meal_data.carbs) { - // average the minIOBPredBG and minUAMPredBG if available - /* - if ( minUAMPredBG < 999 ) { - avgMinPredBG = round( (minIOBPredBG+minUAMPredBG)/2 ); - } else { - avgMinPredBG = minIOBPredBG; - } - */ // if UAM is disabled, use max of minIOBPredBG, minCOBPredBG if ( ! enableUAM && minCOBPredBG < 999 ) { @@ -748,30 +767,29 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // if we have COB, use minCOBPredBG, or blendedMinPredBG if it's higher } else if ( minCOBPredBG < 999 ) { // calculate blendedMinPredBG based on how many carbs remain as COB - //blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minUAMPredBG; - blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG; + 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 { - //minPredBG = 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,minUAMPredBG)); minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); } // make sure minPredBG isn't higher than avgPredBG minPredBG = Math.min( minPredBG, avgPredBG ); - console.error("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); + console.log("minPredBG: "+minPredBG+" minIOBPredBG: "+minIOBPredBG+" minZTGuardBG: "+minZTGuardBG); if (minCOBPredBG < 999) { - console.error(" minCOBPredBG: "+minCOBPredBG); + console.log(" minCOBPredBG: "+minCOBPredBG); } if (minUAMPredBG < 999) { - console.error(" minUAMPredBG: "+minUAMPredBG); + 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: @@ -790,9 +808,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ rT.reason += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile) } rT.reason += "; "; - //var bgUndershoot = threshold - Math.min(minGuardBG, Math.max( naive_eventualBG, eventualBG )); // use naive_eventualBG if above 40, but switch to minGuardBG if both eventualBGs hit floor of 39 - //var carbsReqBG = Math.max( naive_eventualBG, eventualBG ); var carbsReqBG = naive_eventualBG; if ( carbsReqBG < 40 ) { carbsReqBG = Math.min( minGuardBG, carbsReqBG ); @@ -802,14 +818,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var minutesAboveMinBG = 240; var minutesAboveThreshold = 240; if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { - for (var i=0; i= 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 @@ -904,9 +926,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } // calculate 30m low-temp required to get projected BG up to target - // use snoozeBG to more gradually ramp in any counteraction of the user's boluses // multiply by 2 to low-temp faster for increased hypo safety - //var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / sens); insulinReq = round( insulinReq , 2); // calculate naiveInsulinReq based on naive_eventualBG @@ -914,7 +934,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ naiveInsulinReq = round( naiveInsulinReq , 2); if (minDelta < 0 && minDelta > expectedDelta) { // if we're barely falling, newinsulinReq should be barely negative - //rT.reason += ", Snooze BG " + convert_bg(snoozeBG, profile); var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2); //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); insulinReq = newinsulinReq; @@ -922,6 +941,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // 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, @@ -937,18 +957,17 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } else { // calculate a long enough zero temp to eventually correct back up to target if ( rate <=0 ) { - var bgUndershoot = target_bg - naive_eventualBG; - var worstCaseInsulinReq = bgUndershoot / sens; - var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + bgUndershoot = target_bg - naive_eventualBG; + worstCaseInsulinReq = bgUndershoot / sens; + durationReq = round(60*worstCaseInsulinReq / profile.current_basal); if (durationReq < 0) { durationReq = 0; - // don't set an SMB zero temp longer than 60 minutess + // don't set a temp longer than 120 minutes } else { durationReq = round(durationReq/30)*30; - durationReq = Math.min(60,Math.max(0,durationReq)); + durationReq = Math.min(120,Math.max(0,durationReq)); } //console.error(durationReq); - //rT.reason += "insulinReq " + insulinReq + "; " if (durationReq > 0) { rT.reason += ", setting " + durationReq + "m zero temp. "; return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); @@ -995,8 +1014,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // eventual BG is at/above target // if iob is over max, just cancel any temps - // if we're not here because of SMB, eventual BG is at/above target - if (! (microBolusAllowed && rT.COB)) { + if ( eventualBG >= max_bg ) { rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", "; } if (iob_data.iob > max_iob) { @@ -1012,15 +1030,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // insulinReq is the additional insulin required to get minPredBG down to target_bg //console.error(minPredBG,eventualBG); - //var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); - var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); - // when dropping, but not as fast as expected, reduce insulinReq proportionally - // to the what fraction of expectedDelta we're dropping at - //if (minDelta < 0 && minDelta > expectedDelta) { - //var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2); - //console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq + " for minDelta " + minDelta + " vs. expectedDelta " + expectedDelta); - //insulinReq = newinsulinReq; - //} + 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 + ", "; @@ -1028,49 +1038,56 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } // rate required to deliver insulinReq more insulin over 30m: - var rate = basal + (2 * insulinReq); + rate = basal + (2 * insulinReq); rate = round_basal(rate, profile); insulinReq = round(insulinReq,3); rT.insulinReq = insulinReq; //console.error(iob_data.lastBolusTime); // minutes since last bolus - var lastBolusAge = round(( new Date().getTime() - iob_data.lastBolusTime ) / 60000,1); + 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 - mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); - if (typeof profile.maxSMBBasalMinutes == 'undefined' ) { - maxBolus = round( profile.current_basal * 30 / 60 ,1); + 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); - maxBolus = round( profile.current_basal * 30 / 60 ,1); + 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 0.1U - microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*10)/10; + // 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; - var worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; - var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); + 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 < 0.1) { + if (insulinReq > 0 && microBolus < profile.bolus_increment) { durationReq = 0; } var smbLowTempReq = 0; if (durationReq <= 0) { durationReq = 0; - // don't set a temp longer than 120 minutes + // don't set an SMB zero temp longer than 60 minutes } else if (durationReq >= 30) { durationReq = round(durationReq/30)*30; - durationReq = Math.min(120,Math.max(0,durationReq)); + 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); @@ -1085,17 +1102,23 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } rT.reason += ". "; - //allow SMBs every 3 minutes - var nextBolusMins = round(3-lastBolusAge,1); + //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 > 3) { + if (lastBolusAge > SMBInterval) { if (microBolus > 0) { rT.units = microBolus; rT.reason += "Microbolusing " + microBolus + "U. "; } } else { - rT.reason += "Waiting " + nextBolusMins + "m to microbolus again. "; + rT.reason += "Waiting " + nextBolusMins + "m " + nextBolusSeconds + "s to microbolus again. "; } //rT.reason += ". "; @@ -1106,11 +1129,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ return rT; } - // if insulinReq is negative, snoozeBG > target_bg, and lastCOBpredBG > target_bg, set a neutral temp - //if (insulinReq < 0 && snoozeBG > target_bg && lastCOBpredBG > target_bg) { - //rT.reason += "; SMB bolus snooze: setting current basal of " + basal + " as temp. "; - //return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); - //} } var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile); @@ -1120,13 +1138,13 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ rate = round_basal(maxSafeBasal, profile); } - var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + 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 + 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); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java index cd5285cdf2..af56162640 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/DetermineBasalAdapterSMBJS.java @@ -34,11 +34,14 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.interfaces.ActivePluginProvider; + import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.SafeParse; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; + public class DetermineBasalAdapterSMBJS { private final HasAndroidInjector injector; @Inject AAPSLogger aapsLogger; @@ -47,6 +50,8 @@ public class DetermineBasalAdapterSMBJS { @Inject ResourceHelper resourceHelper; @Inject ProfileFunction profileFunction; @Inject TreatmentsPlugin treatmentsPlugin; + @Inject ActivePluginProvider activePluginProvider; + private ScriptReader mScriptReader; private JSONObject mProfile; @@ -57,6 +62,7 @@ public class DetermineBasalAdapterSMBJS { private JSONObject mAutosensData = null; private boolean mMicrobolusAllowed; private boolean mSMBAlwaysAllowed; + private long mCurrentTime; private String storedCurrentTemp = null; private String storedIobData = null; @@ -67,6 +73,7 @@ public class DetermineBasalAdapterSMBJS { private String storedAutosens_data = null; private String storedMicroBolusAllowed = null; private String storedSMBAlwaysAllowed = null; + private String storedCurrentTime = null; private String scriptDebug = ""; @@ -98,6 +105,7 @@ public class DetermineBasalAdapterSMBJS { aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined"); aapsLogger.debug(LTag.APS, "MicroBolusAllowed: " + (storedMicroBolusAllowed = "" + mMicrobolusAllowed)); aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: " + (storedSMBAlwaysAllowed = "" + mSMBAlwaysAllowed)); + aapsLogger.debug(LTag.APS, "CurrentTime: " + (storedCurrentTime = "" + mCurrentTime)); DetermineBasalResultSMB determineBasalResultSMB = null; @@ -140,7 +148,8 @@ public class DetermineBasalAdapterSMBJS { makeParam(mMealData, rhino, scope), setTempBasalFunctionsObj, new Boolean(mMicrobolusAllowed), - makeParam(null, rhino, scope) // reservoir data as undefined + makeParam(null, rhino, scope), // reservoir data as undefined + new Long(mCurrentTime) }; @@ -227,6 +236,8 @@ public class DetermineBasalAdapterSMBJS { boolean advancedFiltering ) throws JSONException { + String units = profile.getUnits(); + Double pumpbolusstep = activePluginProvider.getActivePump().getPumpDescription().bolusStep; mProfile = new JSONObject(); mProfile.put("max_iob", maxIob); @@ -242,8 +253,7 @@ public class DetermineBasalAdapterSMBJS { mProfile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); mProfile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); - // TODO AS-FIX - // mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)); + //mProfile.put("high_temptarget_raises_sensitivity", SP.getBoolean(R.string.key_high_temptarget_raises_sensitivity, SMBDefaults.high_temptarget_raises_sensitivity)); mProfile.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)); mProfile.put("low_temptarget_lowers_sensitivity", false); @@ -267,13 +277,17 @@ public class DetermineBasalAdapterSMBJS { mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); boolean smbEnabled = sp.getBoolean(resourceHelper.gs(R.string.key_use_smb), false); + mProfile.put("SMBInterval", sp.getInt("key_smbinterval", SMBDefaults.SMBInterval)); mProfile.put("enableSMB_with_COB", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_COB, false)); mProfile.put("enableSMB_with_temptarget", smbEnabled && sp.getBoolean(R.string.key_enableSMB_with_temptarget, false)); mProfile.put("allowSMB_with_high_temptarget", smbEnabled && sp.getBoolean(R.string.key_allowSMB_with_high_temptarget, false)); mProfile.put("enableSMB_always", smbEnabled && sp.getBoolean(R.string.key_enableSMB_always, false) && advancedFiltering); mProfile.put("enableSMB_after_carbs", smbEnabled && sp.getBoolean(R.string.key_enableSMB_after_carbs, false) && advancedFiltering); mProfile.put("maxSMBBasalMinutes", sp.getInt(R.string.key_smbmaxminutes, SMBDefaults.maxSMBBasalMinutes)); - mProfile.put("carbsReqThreshold", SMBDefaults.carbsReqThreshold); + mProfile.put("maxUAMSMBBasalMinutes", sp.getInt(R.string.key_uamsmbmaxminutes, SMBDefaults.maxUAMSMBBasalMinutes)); + //set the min SMB amount to be the amount set by the pump. + mProfile.put("bolus_increment", pumpbolusstep); + mProfile.put("carbsReqThreshold", sp.getInt(R.string.key_carbsReqThreshold, SMBDefaults.carbsReqThreshold)); mProfile.put("current_basal", basalrate); mProfile.put("temptargetSet", tempTargetSet); @@ -302,6 +316,7 @@ public class DetermineBasalAdapterSMBJS { mGlucoseStatus = new JSONObject(); mGlucoseStatus.put("glucose", glucoseStatus.glucose); + mGlucoseStatus.put("noise", glucoseStatus.noise); if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); @@ -332,6 +347,8 @@ public class DetermineBasalAdapterSMBJS { mMicrobolusAllowed = microBolusAllowed; mSMBAlwaysAllowed = advancedFiltering; + mCurrentTime = now; + } private Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/SMBDefaults.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/SMBDefaults.java index f6b8f20233..5f2216b5ae 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/SMBDefaults.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSSMB/SMBDefaults.java @@ -55,10 +55,13 @@ public class SMBDefaults { // *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar //public final static boolean enableSMB_after_carbs = false; // enable supermicrobolus for 6h after carbs, even with 0 COB //public final static boolean allowSMB_with_high_temptarget = false; // allow supermicrobolus (if otherwise enabled) even with high temp targets + public final static int SMBInterval = 3; // minimum interval between SMBs, in minutes. (limited between 1 and 10 min) public final static int maxSMBBasalMinutes = 30; // maximum minutes of basal that can be delivered as a single SMB with uncovered COB + public final static int maxUAMSMBBasalMinutes = 30; // maximum minutes of basal that can be delivered as a single SMB when IOB exceeds COB // curve:"rapid-acting" // Supported curves: "bilinear", "rapid-acting" (Novolog, Novorapid, Humalog, Apidra) and "ultra-rapid" (Fiasp) // useCustomPeakTime:false // allows changing insulinPeakTime // insulinPeakTime:75 // number of minutes after a bolus activity peaks. defaults to 55m for Fiasp if useCustomPeakTime: false public final static int carbsReqThreshold = 1; // grams of carbsReq to trigger a pushover // offline_hotspot:false // enabled an offline-only local wifi hotspot if no Internet available + public final static double bolus_increment = 0.1; // minimum bolus that can be delivered as an SMB } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java index cfe9c75727..36585f97c3 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/GlucoseStatus.java @@ -26,6 +26,7 @@ public class GlucoseStatus { private HasAndroidInjector injector; public double glucose = 0d; + public double noise = 0d; public double delta = 0d; public double avgdelta = 0d; public double short_avgdelta = 0d; @@ -35,6 +36,7 @@ public class GlucoseStatus { public String log() { return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + + "Noise: " + DecimalFormatter.to0Decimal(noise) + " " + "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " + "Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; @@ -47,6 +49,7 @@ public class GlucoseStatus { public GlucoseStatus round() { this.glucose = Round.roundTo(this.glucose, 0.1); + this.noise = Round.roundTo(this.noise, 0.01); this.delta = Round.roundTo(this.delta, 0.01); this.avgdelta = Round.roundTo(this.avgdelta, 0.01); this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01); @@ -93,6 +96,7 @@ public class GlucoseStatus { if (sizeRecords == 1) { GlucoseStatus status = new GlucoseStatus(injector); status.glucose = now.value; + status.noise = 0d; status.short_avgdelta = 0d; status.delta = 0d; status.long_avgdelta = 0d; @@ -150,6 +154,7 @@ public class GlucoseStatus { GlucoseStatus status = new GlucoseStatus(injector); status.glucose = now.value; status.date = now_date; + status.noise = 0d; //for now set to nothing as not all CGMs report noise status.short_avgdelta = average(short_deltas); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 041004a25a..1ac8463df0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -862,8 +862,15 @@ BG upload settings Show detailed delta Show delta with one more decimal place + smbinterval + How frequently SMBs will be given in min SMB max minutes Max minutes of basal to limit SMB to + UAM SMB max minutes + Max minutes of basal to limit SMB to for UAM + carbsReqThreshold + Carb suggestion threshold + When Carbs are suggested, how many carbs will prompt a notification Unsupported pump firmware Send BG data to xDrip+ dexcomg5_xdripupload @@ -1180,6 +1187,12 @@ = 100]]> Low temptarget lowers sensitivity + resistance_lowers_target + Resistance lowers target + When resistance is detected, lower the target glucose + sensitivity_raises_target + Sensitivity raises target + When sensitivity is detected, raise the target glucose Invalid pump setup, check the docs and verify that the Quick Info menu is named QUICK INFO using the 360 configuration software. Custom Large Time Difference @@ -1384,6 +1397,7 @@ Upload BG tests smbmaxminutes + uamsmbmaxminutes Daylight Saving time Daylight Saving time change in 24h or less Daylight Saving time change less than 3 hours ago - Closed loop disabled diff --git a/app/src/main/res/xml/pref_openapssmb.xml b/app/src/main/res/xml/pref_openapssmb.xml index 0cecf93c73..859a75a82b 100644 --- a/app/src/main/res/xml/pref_openapssmb.xml +++ b/app/src/main/res/xml/pref_openapssmb.xml @@ -73,13 +73,41 @@ android:summary="@string/enablesmbaftercarbs_summary" android:title="@string/enablesmbaftercarbs" /> - + + + + + + + + + @@ -150,4 +204,4 @@ - \ No newline at end of file +