Merge remote-tracking branch 'origin/dagger3' into rs

This commit is contained in:
Milos Kozak 2020-04-06 09:53:20 +02:00
commit aa8ddc67e5
38 changed files with 1743 additions and 2011 deletions

View file

@ -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 // (hours * mins_per_hour) / 5 = how many 5 minute periods in 2h = 24
var five_min_blocks = (2 * 60) / 5; var five_min_blocks = (2 * 60) / 5;
var target_delta = target_bg - eventual_bg; var target_delta = target_bg - eventual_bg;
var expectedDelta = round(bgi + (target_delta / five_min_blocks), 1); return /* expectedDelta */ round(bgi + (target_delta / five_min_blocks), 1);
return expectedDelta;
} }
function convert_bg(value, profile) function convert_bg(value, profile)
{ {
if (profile.out_units == "mmol/L") if (profile.out_units === "mmol/L")
{ {
return round(value / 18, 1).toFixed(1); 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 rT = {}; //short for requestedTemp
var deliverAt = new Date(); var deliverAt = new Date();
if (currentTime) {
deliverAt = new Date(currentTime);
}
if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') { if (typeof profile === 'undefined' || typeof profile.current_basal === 'undefined') {
rT.error ='Error: could not get current basal rate'; 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 basal = profile_current_basal;
var systemTime = new Date(); var systemTime = new Date();
if (currentTime) {
systemTime = currentTime;
}
var bgTime = new Date(glucose_status.date); var bgTime = new Date(glucose_status.date);
var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1); var minAgo = round( (systemTime - bgTime) / 60 / 1000 ,1);
var bg = glucose_status.glucose; var bg = glucose_status.glucose;
if (bg < 39) { //Dexcom is in ??? mode or calibrating var noise = glucose_status.noise;
rT.reason = "CGM is calibrating or in ??? state"; // 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 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; 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 //cherry pick from oref upstream dev cb8e94990301277fb1016c778b4e9efa55a6edbc
rT.reason += ". Canceling high temp basal of "+currenttemp.rate; 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.deliverAt = deliverAt;
rT.temp = 'absolute'; rT.temp = 'absolute';
rT.duration = 0; rT.duration = 30;
rT.rate = 0; rT.rate = basal;
return rT; return rT;
//return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); //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.reason += ". Shortening " + currenttemp.duration + "m long zero temp to 30m. ";
rT.deliverAt = deliverAt; rT.deliverAt = deliverAt;
rT.temp = 'absolute'; rT.temp = 'absolute';
@ -115,14 +195,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
var sensitivityRatio; var sensitivityRatio;
var high_temptarget_raises_sensitivity = profile.exercise_mode || profile.high_temptarget_raises_sensitivity; 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 ) { if ( profile.half_basal_exercise_target ) {
var halfBasalTarget = profile.half_basal_exercise_target; var halfBasalTarget = profile.half_basal_exercise_target;
} else { } 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) // 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 ) { || 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 // 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 // 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) // limit sensitivityRatio to profile.autosens_max (1.2x by default)
sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max); sensitivityRatio = Math.min(sensitivityRatio, profile.autosens_max);
sensitivityRatio = round(sensitivityRatio,2); sensitivityRatio = round(sensitivityRatio,2);
console.error("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; "); console.log("Sensitivity ratio set to "+sensitivityRatio+" based on temp target of "+target_bg+"; ");
} else if (typeof autosens_data !== 'undefined' ) { } else if (typeof autosens_data !== 'undefined' && autosens_data) {
sensitivityRatio = autosens_data.ratio; sensitivityRatio = autosens_data.ratio;
console.error("Autosens ratio: "+sensitivityRatio+"; "); console.log("Autosens ratio: "+sensitivityRatio+"; ");
} }
if (sensitivityRatio) { if (sensitivityRatio) {
basal = profile.current_basal * sensitivityRatio; basal = profile.current_basal * sensitivityRatio;
basal = round_basal(basal, profile); basal = round_basal(basal, profile);
if (basal != profile_current_basal) { if (basal !== profile_current_basal) {
console.error("Adjusting basal from "+profile_current_basal+" to "+basal+"; "); console.log("Adjusting basal from "+profile_current_basal+" to "+basal+"; ");
} else { } 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 // adjust min, max, and target BG for sensitivity, such that 50% increase in ISF raises target from 100 to 120
if (profile.temptargetSet) { if (profile.temptargetSet) {
//console.error("Temp Target set, not adjusting with autosens; "); //console.log("Temp Target set, not adjusting with autosens; ");
} else if (typeof autosens_data !== 'undefined' ) { } 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 ) { 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 // 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; min_bg = round((min_bg - 60) / autosens_data.ratio) + 60;
max_bg = round((max_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 // don't allow target_bg below 80
new_target_bg = Math.max(80, new_target_bg); new_target_bg = Math.max(80, new_target_bg);
if (target_bg == new_target_bg) { if (target_bg === new_target_bg) {
console.error("target_bg unchanged: "+new_target_bg+"; "); console.log("target_bg unchanged: "+new_target_bg+"; ");
} else { } 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; 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 profile_sens = round(profile.sens,1)
var sens = profile.sens; var sens = profile.sens;
if (typeof autosens_data !== 'undefined' ) { if (typeof autosens_data !== 'undefined' && autosens_data) {
sens = profile.sens / sensitivityRatio; sens = profile.sens / sensitivityRatio;
sens = round(sens, 1); sens = round(sens, 1);
if (sens != profile_sens) { if (sens !== profile_sens) {
console.error("ISF from "+profile_sens+" to "+sens); console.log("ISF from "+profile_sens+" to "+sens);
} else { } 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); console.error("; CR:",profile.carb_ratio);
// compare currenttemp to iob_data.lastTemp and cancel temp if they don't match // compare currenttemp to iob_data.lastTemp and cancel temp if they don't match
var lastTempAge; var lastTempAge;
if (typeof iob_data.lastTemp !== 'undefined' ) { if (typeof iob_data.lastTemp !== 'undefined' ) {
lastTempAge = round(( new Date().getTime() - iob_data.lastTemp.date ) / 60000); // in minutes lastTempAge = round(( new Date(systemTime).getTime() - iob_data.lastTemp.date ) / 60000); // in minutes
// } ---- added to not produce errors
} else { } else {
lastTempAge = 0; lastTempAge = 0;
} }
//console.error("currenttemp:",currenttemp,"lastTemp:",JSON.stringify(iob_data.lastTemp),"lastTempAge:",lastTempAge,"m"); //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"); console.error("currenttemp:",currenttemp,"lastTempAge:",lastTempAge,"m","tempModulus:",tempModulus,"m");
rT.temp = 'absolute'; rT.temp = 'absolute';
rT.deliverAt = deliverAt; rT.deliverAt = deliverAt;
if ( microBolusAllowed && currenttemp && iob_data.lastTemp && currenttemp.rate != iob_data.lastTemp.rate ) { 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; setting neutral temp of "+basal+"."; rT.reason = "Warning: currenttemp rate "+currenttemp.rate+" != lastTemp rate "+iob_data.lastTemp.rate+" from pumphistory; canceling temp";
return tempBasalFunctions.setTempBasal(basal, 30, profile, rT, currenttemp); return tempBasalFunctions.setTempBasal(0, 0, profile, rT, currenttemp);
} }
if ( currenttemp && iob_data.lastTemp && currenttemp.duration > 0 ) { if ( currenttemp && iob_data.lastTemp && currenttemp.duration > 0 ) {
// TODO: fix this (lastTemp.duration is how long it has run; currenttemp.duration is time left // 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)); //console.error(lastTempAge, round(iob_data.lastTemp.duration,1), round(lastTempAge - iob_data.lastTemp.duration,1));
var lastTempEnded = lastTempAge - iob_data.lastTemp.duration var lastTempEnded = lastTempAge - iob_data.lastTemp.duration
if ( lastTempEnded > 5 ) { if ( lastTempEnded > 5 && lastTempAge > 10 ) {
rT.reason = "Warning: currenttemp running but lastTemp from pumphistory ended "+lastTempEnded+"m ago; setting neutral temp of "+basal+"."; 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)); //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 // TODO: figure out a way to do this check that doesn't fail across basal schedule boundaries
//if ( tempModulus < 25 && tempModulus > 5 ) { //if ( tempModulus < 25 && tempModulus > 5 ) {
@ -264,37 +343,44 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if (iob_data.iob > 0) { if (iob_data.iob > 0) {
var naive_eventualBG = round( bg - (iob_data.iob * sens) ); 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 } 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 // and adjust it for the deviation above
var eventualBG = naive_eventualBG + deviation; 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 // raise target for noisy / raw CGM data
if ( bg > max_bg && profile.adv_target_adjustments && ! profile.temptargetSet ) { 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 // 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); 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); 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); adjustedMaxBG = round(Math.max(80, max_bg - (bg - max_bg)/3 ),0);
// if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedMinBG, dont use it // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedMinBG, dont use it
//console.error("naive_eventualBG:",naive_eventualBG+", eventualBG:",eventualBG); //console.error("naive_eventualBG:",naive_eventualBG+", eventualBG:",eventualBG);
if (eventualBG > adjustedMinBG && naive_eventualBG > adjustedMinBG && min_bg > adjustedMinBG) { 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; min_bg = adjustedMinBG;
} else { } 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, dont use it // if eventualBG, naive_eventualBG, and target_bg aren't all above adjustedTargetBG, dont use it
if (eventualBG > adjustedTargetBG && naive_eventualBG > adjustedTargetBG && target_bg > adjustedTargetBG) { 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; target_bg = adjustedTargetBG;
} else { } 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, dont use it // if eventualBG, naive_eventualBG, and max_bg aren't all above adjustedMaxBG, dont use it
if (eventualBG > adjustedMaxBG && naive_eventualBG > adjustedMaxBG && max_bg > adjustedMaxBG) { 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 , 'bg': bg
, 'tick': tick , 'tick': tick
, 'eventualBG': eventualBG , 'eventualBG': eventualBG
//, 'snoozeBG': snoozeBG
, 'insulinReq': 0 , '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) , '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 , '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); ZTpredBGs.push(bg);
UAMpredBGs.push(bg); UAMpredBGs.push(bg);
// enable SMB whenever we have COB or UAM is enabled var enableSMB = enable_smb(
// SMB is disabled by default, unless explicitly enabled in preferences.json profile,
var enableSMB=false; microBolusAllowed,
// disable SMB when a high temptarget is set meal_data,
if (! microBolusAllowed) { target_bg
console.error("SMB disabled (!microBolusAllowed)") );
} else if (! profile.allowSMB_with_high_temptarget && profile.temptargetSet && target_bg > 100) {
console.error("SMB disabled due to high temptarget of",target_bg);
enableSMB=false;
// enable SMB/UAM (if enabled in preferences) while we have COB
} else if (profile.enableSMB_with_COB === true && meal_data.mealCOB) {
if (meal_data.bwCarbs) {
if (profile.A52_risk_enable) {
console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard")
enableSMB=true;
} else {
console.error("SMB not enabled for Bolus Wizard COB");
}
} else {
console.error("SMB enabled for COB of",meal_data.mealCOB);
enableSMB=true;
}
// enable SMB/UAM (if enabled in preferences) for a full 6 hours after any carb entry
// (6 hours is defined in carbWindow in lib/meal/total.js)
} else if (profile.enableSMB_after_carbs === true && meal_data.carbs ) {
if (meal_data.bwCarbs) {
if (profile.A52_risk_enable) {
console.error("Warning: SMB enabled with Bolus Wizard carbs: be sure to easy bolus 30s before using Bolus Wizard")
enableSMB=true;
} else {
console.error("SMB not enabled for Bolus Wizard carbs");
}
} else {
console.error("SMB enabled for 6h after carb entry");
enableSMB=true;
}
// enable SMB/UAM (if enabled in preferences) if a low temptarget is set
} else if (profile.enableSMB_with_temptarget === true && (profile.temptargetSet && target_bg < 100)) {
if (meal_data.bwFound) {
if (profile.A52_risk_enable) {
console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard")
enableSMB=true;
} else {
console.error("enableSMB_with_temptarget not supported within 6h of using Bolus Wizard");
}
} else {
console.error("SMB enabled for temptarget of",convert_bg(target_bg, profile));
enableSMB=true;
}
// enable SMB/UAM if always-on (unless previously disabled for high temptarget)
} else if (profile.enableSMB_always === true) {
if (meal_data.bwFound) {
if (profile.A52_risk_enable === true) {
console.error("Warning: SMB enabled within 6h of using Bolus Wizard: be sure to easy bolus 30s before using Bolus Wizard")
enableSMB=true;
} else {
console.error("enableSMB_always not supported within 6h of using Bolus Wizard");
}
} else {
console.error("SMB enabled due to enableSMB_always");
enableSMB=true;
}
} else {
console.error("SMB disabled (no enableSMB preferences active)");
}
// enable UAM (if enabled in preferences) // enable UAM (if enabled in preferences)
var enableUAM=(profile.enableUAM); 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 // calculate current carb absorption rate, and how long to absorb all carbs
// CI = current carb impact on BG in mg/dL/5m // CI = current carb impact on BG in mg/dL/5m
ci = round((minDelta - bgi),1); 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) // 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 // if temptargetSet, use unadjusted profile.sens to allow activity mode sensitivityRatio to adjust CR
var csf = profile.sens / profile.carb_ratio; //var csf = profile.sens / profile.carb_ratio;
} else { //} else {
// otherwise, use autosens-adjusted sens to counteract autosens meal insulin dosing adjustments // 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 // 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 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 // 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) { if (ci > maxCI) {
console.error("Limiting carb impact from",ci,"to",maxCI,"mg/dL/5m (",maxCarbAbsorptionRate,"g/h )"); console.error("Limiting carb impact from",ci,"to",maxCI,"mg/dL/5m (",maxCarbAbsorptionRate,"g/h )");
ci = maxCI; ci = maxCI;
} }
// set meal_carbimpact high enough to absorb all meal carbs over 6 hours var remainingCATimeMin = 3; // h; duration of expected not-yet-observed carb absorption
// total_impact (mg/dL) = CSF (mg/dL/g) * carbs (g) // adjust remainingCATime (instead of CR) for autosens if sensitivityRatio defined
//console.error(csf * meal_data.carbs); if (sensitivityRatio){
// 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; remainingCATimeMin = remainingCATimeMin / sensitivityRatio;
}
// 20 g/h means that anything <= 60g will get a remainingCATimeMin, 80g will get 4h, and 120g 6h // 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 // 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 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 (meal_data.carbs) {
// if carbs * assumedCarbAbsorptionRate > remainingCATimeMin, raise it // if carbs * assumedCarbAbsorptionRate > remainingCATimeMin, raise it
// so <= 90g is assumed to take 3h, and 120g=4h // so <= 90g is assumed to take 3h, and 120g=4h
remainingCATimeMin = Math.max(remainingCATimeMin, meal_data.mealCOB/assumedCarbAbsorptionRate); 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); //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 = remainingCATimeMin + 1.5 * lastCarbAge/60;
remainingCATime = round(remainingCATime,1); remainingCATime = round(remainingCATime,1);
//console.error(fractionCOBAbsorbed, remainingCATimeAdjustment, remainingCATime) //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) // 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); var remainingCIpeak = remainingCarbs * csf * 5 / 60 / (remainingCATime/2);
//console.error(profile.min_5m_carbimpact,ci,totalCI,totalCA,remainingCarbs,remainingCI,remainingCATime); //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 // calculate peak deviation in last hour, and slope from that to current deviation
var slopeFromMaxDeviation = round(meal_data.slopeFromMaxDeviation,2); 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); var slopeFromDeviations = Math.min(slopeFromMaxDeviation,-slopeFromMinDeviation/3);
//console.error(slopeFromMaxDeviation); //console.error(slopeFromMaxDeviation);
aci = 10; var aci = 10;
//5m data points = g * (1U/10g) * (40mg/dL/1U) / (mg/dL/5m) //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) // 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 // limit cid to remainingCATime hours: the reset goes to remainingCI
if (ci == 0) { if (ci === 0) {
// avoid divide by zero // avoid divide by zero
cid = 0; cid = 0;
} else { } else {
cid = Math.min(remainingCATime*60/5/2,Math.max(0, meal_data.mealCOB * csf / ci )); 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) // 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("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"); //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 { try {
iobArray.forEach(function(iobTick) { iobArray.forEach(function(iobTick) {
//console.error(iobTick); //console.error(iobTick);
predBGI = round(( -iobTick.activity * sens * 5 ), 2); var predBGI = round(( -iobTick.activity * sens * 5 ), 2);
predZTBGI = round(( -iobTick.iobWithZeroTemp.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 // for IOBpredBGs, predicted deviation impact drops linearly from current deviation down to zero
// over 60 minutes (data points every 5m) // 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; IOBpredBG = IOBpredBGs[IOBpredBGs.length-1] + predBGI + predDev;
// calculate predBGs with long zero temp without deviations // 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 // 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) // 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) ) ); var 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 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 // 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) // bilinear curve peaking at remainingCIpeak at remainingCATime/2 hours (remainingCATime/2*12 * 5m)
// and ending at remainingCATime h (remainingCATime*12 * 5m intervals) // 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; remainingCItotal += predCI+remainingCI;
remainingCIs.push(round(remainingCI,0)); remainingCIs.push(round(remainingCI,0));
predCIs.push(round(predCI,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; 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 // for UAMpredBGs, predicted carb impact drops at slopeFromDeviations
// calculate predicted CI from UAM based on 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 // if slopeFromDeviations is too flat, predicted deviation impact drops linearly from
// current deviation down to zero over 3h (data points every 5m) // 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); //console.error(predUCIslope, predUCImax);
// predicted CI from UAM is the lesser of CI based on deviationSlope or DIA // 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) { if(predUCI>0) {
//console.error(UAMpredBGs.length,slopeFromDeviations, predUCI); //console.error(UAMpredBGs.length,slopeFromDeviations, predUCI);
UAMduration=round((UAMpredBGs.length+1)*5/60,1); 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 // 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 // look ahead 60m (regardless of insulin type) so as to be less aggressive on slower insulins
var insulinPeakTime = 60; 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; insulinPeakTime = 90;
var insulinPeak5m = (insulinPeakTime/60)*12; var insulinPeak5m = (insulinPeakTime/60)*12;
//console.error(insulinPeakTime, insulinPeak5m, profile.insulinPeakTime, profile.curve); //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 // set eventualBG to include effect of carbs
//console.error("PredBGs:",JSON.stringify(predBGs)); //console.error("PredBGs:",JSON.stringify(predBGs));
} catch (e) { } 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) { if (meal_data.mealCOB) {
console.error("predCIs (mg/dL/5m):",predCIs.join(" ")); console.error("predCIs (mg/dL/5m):",predCIs.join(" "));
console.error("remainingCIs: ",remainingCIs.join(" ")); console.error("remainingCIs: ",remainingCIs.join(" "));
} }
//,"totalCA:",round(totalCA,2),"remainingCItotal/csf+totalCA:",round(remainingCItotal/csf+totalCA,2));
rT.predBGs = {}; rT.predBGs = {};
IOBpredBGs.forEach(function(p, i, theArray) { IOBpredBGs.forEach(function(p, i, theArray) {
theArray[i] = round(Math.min(401,Math.max(39,p))); theArray[i] = round(Math.min(401,Math.max(39,p)));
}); });
for (var i=IOBpredBGs.length-1; i > 12; i--) { 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(); } else { IOBpredBGs.pop(); }
} }
rT.predBGs.IOB = IOBpredBGs; rT.predBGs.IOB = IOBpredBGs;
@ -619,10 +649,9 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
ZTpredBGs.forEach(function(p, i, theArray) { ZTpredBGs.forEach(function(p, i, theArray) {
theArray[i] = round(Math.min(401,Math.max(39,p))); theArray[i] = round(Math.min(401,Math.max(39,p)));
}); });
for (var i=ZTpredBGs.length-1; i > 6; i--) { for (i=ZTpredBGs.length-1; i > 6; i--) {
//if (ZTpredBGs[i-1] != ZTpredBGs[i]) { break; }
// stop displaying ZTpredBGs once they're rising and above target // 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(); } else { ZTpredBGs.pop(); }
} }
rT.predBGs.ZT = ZTpredBGs; rT.predBGs.ZT = ZTpredBGs;
@ -631,19 +660,17 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
aCOBpredBGs.forEach(function(p, i, theArray) { aCOBpredBGs.forEach(function(p, i, theArray) {
theArray[i] = round(Math.min(401,Math.max(39,p))); theArray[i] = round(Math.min(401,Math.max(39,p)));
}); });
for (var i=aCOBpredBGs.length-1; i > 12; i--) { for (i=aCOBpredBGs.length-1; i > 12; i--) {
if (aCOBpredBGs[i-1] != aCOBpredBGs[i]) { break; } if (aCOBpredBGs[i-1] !== aCOBpredBGs[i]) { break; }
else { aCOBpredBGs.pop(); } 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 )) { if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) {
COBpredBGs.forEach(function(p, i, theArray) { COBpredBGs.forEach(function(p, i, theArray) {
theArray[i] = round(Math.min(401,Math.max(39,p))); theArray[i] = round(Math.min(401,Math.max(39,p)));
}); });
for (var i=COBpredBGs.length-1; i > 12; i--) { for (i=COBpredBGs.length-1; i > 12; i--) {
if (COBpredBGs[i-1] != COBpredBGs[i]) { break; } if (COBpredBGs[i-1] !== COBpredBGs[i]) { break; }
else { COBpredBGs.pop(); } else { COBpredBGs.pop(); }
} }
rT.predBGs.COB = COBpredBGs; rT.predBGs.COB = COBpredBGs;
@ -655,8 +682,8 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
UAMpredBGs.forEach(function(p, i, theArray) { UAMpredBGs.forEach(function(p, i, theArray) {
theArray[i] = round(Math.min(401,Math.max(39,p))); theArray[i] = round(Math.min(401,Math.max(39,p)));
}); });
for (var i=UAMpredBGs.length-1; i > 12; i--) { for (i=UAMpredBGs.length-1; i > 12; i--) {
if (UAMpredBGs[i-1] != UAMpredBGs[i]) { break; } if (UAMpredBGs[i-1] !== UAMpredBGs[i]) { break; }
else { UAMpredBGs.pop(); } else { UAMpredBGs.pop(); }
} }
rT.predBGs.UAM = UAMpredBGs; 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; 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); //console.error("minUAMPredBG:",minUAMPredBG,"minZTGuardBG:",minZTGuardBG,"minZTUAMPredBG:",minZTUAMPredBG);
// if any carbs have been entered recently // if any carbs have been entered recently
if (meal_data.carbs) { 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 UAM is disabled, use max of minIOBPredBG, minCOBPredBG
if ( ! enableUAM && minCOBPredBG < 999 ) { 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 // if we have COB, use minCOBPredBG, or blendedMinPredBG if it's higher
} else if ( minCOBPredBG < 999 ) { } else if ( minCOBPredBG < 999 ) {
// calculate blendedMinPredBG based on how many carbs remain as COB // calculate blendedMinPredBG based on how many carbs remain as COB
//blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minUAMPredBG; var blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG;
blendedMinPredBG = fractionCarbsLeft*minCOBPredBG + (1-fractionCarbsLeft)*minZTUAMPredBG;
// if blendedMinPredBG > minCOBPredBG, use that instead // if blendedMinPredBG > minCOBPredBG, use that instead
minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG, blendedMinPredBG)); minPredBG = round(Math.max(minIOBPredBG, minCOBPredBG, blendedMinPredBG));
// if carbs have been entered, but have expired, use minUAMPredBG // if carbs have been entered, but have expired, use minUAMPredBG
} else { } else if ( enableUAM ) {
//minPredBG = minUAMPredBG;
minPredBG = minZTUAMPredBG; minPredBG = minZTUAMPredBG;
} else {
minPredBG = minGuardBG;
} }
// in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG // in pure UAM mode, use the higher of minIOBPredBG,minUAMPredBG
} else if ( enableUAM ) { } else if ( enableUAM ) {
//minPredBG = round(Math.max(minIOBPredBG,minUAMPredBG));
minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG)); minPredBG = round(Math.max(minIOBPredBG,minZTUAMPredBG));
} }
// make sure minPredBG isn't higher than avgPredBG // make sure minPredBG isn't higher than avgPredBG
minPredBG = Math.min( minPredBG, 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) { if (minCOBPredBG < 999) {
console.error(" minCOBPredBG: "+minCOBPredBG); console.log(" minCOBPredBG: "+minCOBPredBG);
} }
if (minUAMPredBG < 999) { if (minUAMPredBG < 999) {
console.error(" minUAMPredBG: "+minUAMPredBG); console.log(" minUAMPredBG: "+minUAMPredBG);
} }
console.error(" avgPredBG:",avgPredBG,"COB:",meal_data.mealCOB,"/",meal_data.carbs); 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: // 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 += ", UAMpredBG " + convert_bg(lastUAMpredBG, profile)
} }
rT.reason += "; "; 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 // 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; var carbsReqBG = naive_eventualBG;
if ( carbsReqBG < 40 ) { if ( carbsReqBG < 40 ) {
carbsReqBG = Math.min( minGuardBG, carbsReqBG ); carbsReqBG = Math.min( minGuardBG, carbsReqBG );
@ -802,14 +818,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
var minutesAboveMinBG = 240; var minutesAboveMinBG = 240;
var minutesAboveThreshold = 240; var minutesAboveThreshold = 240;
if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) { if (meal_data.mealCOB > 0 && ( ci > 0 || remainingCIpeak > 0 )) {
for (var i=0; i<COBpredBGs.length; i++) { for (i=0; i<COBpredBGs.length; i++) {
//console.error(COBpredBGs[i], min_bg); //console.error(COBpredBGs[i], min_bg);
if ( COBpredBGs[i] < min_bg ) { if ( COBpredBGs[i] < min_bg ) {
minutesAboveMinBG = 5*i; minutesAboveMinBG = 5*i;
break; break;
} }
} }
for (var i=0; i<COBpredBGs.length; i++) { for (i=0; i<COBpredBGs.length; i++) {
//console.error(COBpredBGs[i], threshold); //console.error(COBpredBGs[i], threshold);
if ( COBpredBGs[i] < threshold ) { if ( COBpredBGs[i] < threshold ) {
minutesAboveThreshold = 5*i; minutesAboveThreshold = 5*i;
@ -817,14 +833,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
} }
} }
} else { } else {
for (var i=0; i<IOBpredBGs.length; i++) { for (i=0; i<IOBpredBGs.length; i++) {
//console.error(IOBpredBGs[i], min_bg); //console.error(IOBpredBGs[i], min_bg);
if ( IOBpredBGs[i] < min_bg ) { if ( IOBpredBGs[i] < min_bg ) {
minutesAboveMinBG = 5*i; minutesAboveMinBG = 5*i;
break; break;
} }
} }
for (var i=0; i<IOBpredBGs.length; i++) { for (i=0; i<IOBpredBGs.length; i++) {
//console.error(IOBpredBGs[i], threshold); //console.error(IOBpredBGs[i], threshold);
if ( IOBpredBGs[i] < threshold ) { if ( IOBpredBGs[i] < threshold ) {
minutesAboveThreshold = 5*i; minutesAboveThreshold = 5*i;
@ -848,9 +864,8 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
if ( minutesAboveThreshold < 240 || minutesAboveMinBG < 60 ) { if ( minutesAboveThreshold < 240 || minutesAboveMinBG < 60 ) {
console.error("BG projected to remain above",convert_bg(threshold,profile),"for",minutesAboveThreshold,"minutes"); console.error("BG projected to remain above",convert_bg(threshold,profile),"for",minutesAboveThreshold,"minutes");
} }
// include at least minutesAboveMinBG worth of zero temps in calculating carbsReq // 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) // always include at least 30m worth of zero temp (carbs to 80, low temp up to target)
//var zeroTempDuration = Math.max(30,minutesAboveMinBG);
var zeroTempDuration = minutesAboveThreshold; var zeroTempDuration = minutesAboveThreshold;
// BG undershoot, minus effect of zero temps until hitting min_bg, converted to grams, minus COB // BG undershoot, minus effect of zero temps until hitting min_bg, converted to grams, minus COB
var zeroTempEffect = profile.current_basal*sens*zeroTempDuration/60; var zeroTempEffect = profile.current_basal*sens*zeroTempDuration/60;
@ -871,7 +886,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
// predictive low glucose suspend mode: BG is / is projected to be < threshold // predictive low glucose suspend mode: BG is / is projected to be < threshold
} else if ( bg < threshold || minGuardBG < threshold ) { } else if ( bg < threshold || minGuardBG < threshold ) {
rT.reason += "minGuardBG " + convert_bg(minGuardBG, profile) + "<" + convert_bg(threshold, profile); rT.reason += "minGuardBG " + convert_bg(minGuardBG, profile) + "<" + convert_bg(threshold, profile);
var bgUndershoot = target_bg - minGuardBG; bgUndershoot = target_bg - minGuardBG;
var worstCaseInsulinReq = bgUndershoot / sens; var worstCaseInsulinReq = bgUndershoot / sens;
var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); var durationReq = round(60*worstCaseInsulinReq / profile.current_basal);
durationReq = round(durationReq/30)*30; durationReq = round(durationReq/30)*30;
@ -880,6 +895,13 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
return tempBasalFunctions.setTempBasal(0, durationReq, profile, rT, currenttemp); 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: if (eventualBG < min_bg) { // if eventual BG is below target:
rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " < " + convert_bg(min_bg, profile); 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 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 // 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 // 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); var insulinReq = 2 * Math.min(0, (eventualBG - target_bg) / sens);
insulinReq = round( insulinReq , 2); insulinReq = round( insulinReq , 2);
// calculate naiveInsulinReq based on naive_eventualBG // calculate naiveInsulinReq based on naive_eventualBG
@ -914,7 +934,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
naiveInsulinReq = round( naiveInsulinReq , 2); naiveInsulinReq = round( naiveInsulinReq , 2);
if (minDelta < 0 && minDelta > expectedDelta) { if (minDelta < 0 && minDelta > expectedDelta) {
// if we're barely falling, newinsulinReq should be barely negative // 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); var newinsulinReq = round(( insulinReq * (minDelta / expectedDelta) ), 2);
//console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); //console.error("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq);
insulinReq = 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: // rate required to deliver insulinReq less insulin over 30m:
var rate = basal + (2 * insulinReq); var rate = basal + (2 * insulinReq);
rate = round_basal(rate, profile); rate = round_basal(rate, profile);
// if required temp < existing temp basal // if required temp < existing temp basal
var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60;
// if current temp would deliver a lot (30% of basal) less than the required insulin, // 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 { } else {
// calculate a long enough zero temp to eventually correct back up to target // calculate a long enough zero temp to eventually correct back up to target
if ( rate <=0 ) { if ( rate <=0 ) {
var bgUndershoot = target_bg - naive_eventualBG; bgUndershoot = target_bg - naive_eventualBG;
var worstCaseInsulinReq = bgUndershoot / sens; worstCaseInsulinReq = bgUndershoot / sens;
var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); durationReq = round(60*worstCaseInsulinReq / profile.current_basal);
if (durationReq < 0) { if (durationReq < 0) {
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 { } else {
durationReq = round(durationReq/30)*30; durationReq = round(durationReq/30)*30;
durationReq = Math.min(60,Math.max(0,durationReq)); durationReq = Math.min(120,Math.max(0,durationReq));
} }
//console.error(durationReq); //console.error(durationReq);
//rT.reason += "insulinReq " + insulinReq + "; "
if (durationReq > 0) { if (durationReq > 0) {
rT.reason += ", setting " + durationReq + "m zero temp. "; rT.reason += ", setting " + durationReq + "m zero temp. ";
return tempBasalFunctions.setTempBasal(rate, durationReq, profile, rT, currenttemp); 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 // eventual BG is at/above target
// if iob is over max, just cancel any temps // if iob is over max, just cancel any temps
// if we're not here because of SMB, eventual BG is at/above target if ( eventualBG >= max_bg ) {
if (! (microBolusAllowed && rT.COB)) {
rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", "; rT.reason += "Eventual BG " + convert_bg(eventualBG, profile) + " >= " + convert_bg(max_bg, profile) + ", ";
} }
if (iob_data.iob > max_iob) { 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 // insulinReq is the additional insulin required to get minPredBG down to target_bg
//console.error(minPredBG,eventualBG); //console.error(minPredBG,eventualBG);
//var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2); insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2);
var insulinReq = round( (Math.min(minPredBG,eventualBG) - target_bg) / sens, 2);
// when dropping, but not as fast as expected, reduce insulinReq proportionally
// to the what fraction of expectedDelta we're dropping at
//if (minDelta < 0 && minDelta > expectedDelta) {
//var newinsulinReq = round(( insulinReq * (1 - (minDelta / expectedDelta)) ), 2);
//console.error("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq + " for minDelta " + minDelta + " vs. expectedDelta " + expectedDelta);
//insulinReq = newinsulinReq;
//}
// if that would put us over max_iob, then reduce accordingly // if that would put us over max_iob, then reduce accordingly
if (insulinReq > max_iob-iob_data.iob) { if (insulinReq > max_iob-iob_data.iob) {
rT.reason += "max_iob " + max_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: // rate required to deliver insulinReq more insulin over 30m:
var rate = basal + (2 * insulinReq); rate = basal + (2 * insulinReq);
rate = round_basal(rate, profile); rate = round_basal(rate, profile);
insulinReq = round(insulinReq,3); insulinReq = round(insulinReq,3);
rT.insulinReq = insulinReq; rT.insulinReq = insulinReq;
//console.error(iob_data.lastBolusTime); //console.error(iob_data.lastBolusTime);
// minutes since last bolus // 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(lastBolusAge);
//console.error(profile.temptargetSet, target_bg, rT.COB); //console.error(profile.temptargetSet, target_bg, rT.COB);
// only allow microboluses with COB or low temp targets, or within DIA hours of a bolus // only allow microboluses with COB or low temp targets, or within DIA hours of a bolus
if (microBolusAllowed && enableSMB && bg > threshold) { if (microBolusAllowed && enableSMB && bg > threshold) {
// never bolus more than maxSMBBasalMinutes worth of basal // never bolus more than maxSMBBasalMinutes worth of basal
mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3); var mealInsulinReq = round( meal_data.mealCOB / profile.carb_ratio ,3);
if (typeof profile.maxSMBBasalMinutes == 'undefined' ) { if (typeof profile.maxSMBBasalMinutes === 'undefined' ) {
maxBolus = round( profile.current_basal * 30 / 60 ,1); var maxBolus = round( profile.current_basal * 30 / 60 ,1);
console.error("profile.maxSMBBasalMinutes undefined: defaulting to 30m"); console.error("profile.maxSMBBasalMinutes undefined: defaulting to 30m");
// if IOB covers more than COB, limit maxBolus to 30m of basal // if IOB covers more than COB, limit maxBolus to 30m of basal
} else if ( iob_data.iob > mealInsulinReq && iob_data.iob > 0 ) { } else if ( iob_data.iob > mealInsulinReq && iob_data.iob > 0 ) {
console.error("IOB",iob_data.iob,"> COB",meal_data.mealCOB+"; mealInsulinReq =",mealInsulinReq); 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); maxBolus = round( profile.current_basal * 30 / 60 ,1);
}
} else { } else {
console.error("profile.maxSMBBasalMinutes:",profile.maxSMBBasalMinutes,"profile.current_basal:",profile.current_basal); console.error("profile.maxSMBBasalMinutes:",profile.maxSMBBasalMinutes,"profile.current_basal:",profile.current_basal);
maxBolus = round( profile.current_basal * profile.maxSMBBasalMinutes / 60 ,1); maxBolus = round( profile.current_basal * profile.maxSMBBasalMinutes / 60 ,1);
} }
// bolus 1/2 the insulinReq, up to maxBolus, rounding down to nearest 0.1U // bolus 1/2 the insulinReq, up to maxBolus, rounding down to nearest bolus increment
microBolus = Math.floor(Math.min(insulinReq/2,maxBolus)*10)/10; 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 // calculate a long enough zero temp to eventually correct back up to target
var smbTarget = target_bg; var smbTarget = target_bg;
var worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens; worstCaseInsulinReq = (smbTarget - (naive_eventualBG + minIOBPredBG)/2 ) / sens;
var durationReq = round(60*worstCaseInsulinReq / profile.current_basal); 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 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; durationReq = 0;
} }
var smbLowTempReq = 0; var smbLowTempReq = 0;
if (durationReq <= 0) { if (durationReq <= 0) {
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) { } else if (durationReq >= 30) {
durationReq = round(durationReq/30)*30; durationReq = round(durationReq/30)*30;
durationReq = Math.min(120,Math.max(0,durationReq)); durationReq = Math.min(60,Math.max(0,durationReq));
} else { } else {
// if SMB durationReq is less than 30m, set a nonzero low temp // if SMB durationReq is less than 30m, set a nonzero low temp
smbLowTempReq = round( basal * durationReq/30 ,2); smbLowTempReq = round( basal * durationReq/30 ,2);
@ -1085,17 +1102,23 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
} }
rT.reason += ". "; rT.reason += ". ";
//allow SMBs every 3 minutes //allow SMBs every 3 minutes by default
var nextBolusMins = round(3-lastBolusAge,1); 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, insulinReq, worstCaseInsulinReq, durationReq);
console.error("naive_eventualBG",naive_eventualBG+",",durationReq+"m "+smbLowTempReq+"U/h temp needed; last bolus",lastBolusAge+"m ago; maxBolus: "+maxBolus); 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) { if (microBolus > 0) {
rT.units = microBolus; rT.units = microBolus;
rT.reason += "Microbolusing " + microBolus + "U. "; rT.reason += "Microbolusing " + microBolus + "U. ";
} }
} else { } else {
rT.reason += "Waiting " + nextBolusMins + "m to microbolus again. "; rT.reason += "Waiting " + nextBolusMins + "m " + nextBolusSeconds + "s to microbolus again. ";
} }
//rT.reason += ". "; //rT.reason += ". ";
@ -1106,11 +1129,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
return rT; 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); var maxSafeBasal = tempBasalFunctions.getMaxSafeBasal(profile);
@ -1120,13 +1138,13 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
rate = round_basal(maxSafeBasal, profile); 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 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. "; 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); 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. "; rT.reason += "no temp, setting " + rate + "U/hr. ";
return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp); return tempBasalFunctions.setTempBasal(rate, 30, profile, rT, currenttemp);
} }

View file

@ -107,10 +107,6 @@ public class CareportalEvent implements DataPointWithLabelInterface, Interval {
return diff.get(TimeUnit.DAYS) + days + diff.get(TimeUnit.HOURS) + hours; return diff.get(TimeUnit.DAYS) + days + diff.get(TimeUnit.HOURS) + hours;
} }
public String age() {
return age(OverviewFragment.shorttextmode);
}
public boolean isOlderThan(double hours) { public boolean isOlderThan(double hours) {
return getHoursFromStart() > hours; return getHoursFromStart() > hours;
} }

View file

@ -167,7 +167,7 @@ abstract class PluginsModule {
abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase
@Binds @Binds
@NotNSClient @AllConfigs
@IntoMap @IntoMap
@IntKey(180) @IntKey(180)
abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase

View file

@ -28,6 +28,7 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.activities.NoSplashAppCompatActivity; import info.nightscout.androidaps.activities.NoSplashAppCompatActivity;
import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.Profile;
import info.nightscout.androidaps.events.EventCustomCalculationFinished; import info.nightscout.androidaps.events.EventCustomCalculationFinished;
import info.nightscout.androidaps.events.EventRefreshOverview;
import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
@ -35,6 +36,7 @@ import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction;
import info.nightscout.androidaps.plugins.general.overview.OverviewFragment; import info.nightscout.androidaps.plugins.general.overview.OverviewFragment;
import info.nightscout.androidaps.plugins.general.overview.OverviewMenus;
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData; import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress;
@ -60,6 +62,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
@Inject ActivePluginProvider activePlugin; @Inject ActivePluginProvider activePlugin;
@Inject BuildHelper buildHelper; @Inject BuildHelper buildHelper;
@Inject FabricPrivacy fabricPrivacy; @Inject FabricPrivacy fabricPrivacy;
@Inject OverviewMenus overviewMenus;
private CompositeDisposable disposable = new CompositeDisposable(); private CompositeDisposable disposable = new CompositeDisposable();
@ -171,7 +174,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
iobGraph.getGridLabelRenderer().setLabelVerticalWidth(50); iobGraph.getGridLabelRenderer().setLabelVerticalWidth(50);
iobGraph.getGridLabelRenderer().setNumVerticalLabels(5); iobGraph.getGridLabelRenderer().setNumVerticalLabels(5);
setupChartMenu(); overviewMenus.setupChartMenu(findViewById(R.id.overview_chartMenuButton));
} }
@Override @Override
@ -194,7 +197,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
updateGUI("EventAutosensCalculationFinished"); updateGUI("EventAutosensCalculationFinished");
} }
} }
}, exception -> fabricPrivacy.logException(exception)) }, fabricPrivacy::logException)
); );
disposable.add(rxBus disposable.add(rxBus
.toObservable(EventIobCalculationProgress.class) .toObservable(EventIobCalculationProgress.class)
@ -204,6 +207,11 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
iobCalculationProgressView.setText(event.getProgress()); iobCalculationProgressView.setText(event.getProgress());
}, exception -> fabricPrivacy.logException(exception)) }, exception -> fabricPrivacy.logException(exception))
); );
disposable.add(rxBus
.toObservable(EventRefreshOverview.class)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(event -> updateGUI("EventRefreshOverview") , fabricPrivacy::logException)
);
// set start of current day // set start of current day
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis()); calendar.setTimeInMillis(System.currentTimeMillis());
@ -367,124 +375,4 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity {
}); });
}).start(); }).start();
} }
private void setupChartMenu() {
chartButton = findViewById(R.id.overview_chartMenuButton);
chartButton.setOnClickListener(v -> {
MenuItem item, dividerItem;
CharSequence title;
int titleMaxChars = 0;
SpannableString s;
PopupMenu popup = new PopupMenu(v.getContext(), v);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.BAS.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_basals));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.basal, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showBasal);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.ACTPRIM.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_activity));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.activity, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showActPrim);
dividerItem = popup.getMenu().add("");
dividerItem.setEnabled(false);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.IOB.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_iob));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.iob, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showIob);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.COB.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_cob));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.cob, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showCob);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEV.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_deviations));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.deviations, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showDev);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.SEN.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_sensitivity));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.ratio, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showRat);
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.ACTSEC.ordinal(), Menu.NONE, resourceHelper.gs(R.string.overview_show_activity));
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.activity, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showActSec);
if (buildHelper.isDev()) {
item = popup.getMenu().add(Menu.NONE, OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal(), Menu.NONE, "Deviation slope");
title = item.getTitle();
if (titleMaxChars < title.length()) titleMaxChars = title.length();
s = new SpannableString(title);
s.setSpan(new ForegroundColorSpan(ResourcesCompat.getColor(getResources(), R.color.devslopepos, null)), 0, s.length(), 0);
item.setTitle(s);
item.setCheckable(true);
item.setChecked(showDevslope);
}
// Fairly good guestimate for required divider text size...
title = new String(new char[titleMaxChars + 10]).replace("\0", "_");
dividerItem.setTitle(title);
popup.setOnMenuItemClickListener(item1 -> {
if (item1.getItemId() == OverviewFragment.CHARTTYPE.BAS.ordinal()) {
sp.putBoolean("hist_showbasals", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.IOB.ordinal()) {
sp.putBoolean("hist_showiob", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.COB.ordinal()) {
sp.putBoolean("hist_showcob", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.DEV.ordinal()) {
sp.putBoolean("hist_showdeviations", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.SEN.ordinal()) {
sp.putBoolean("hist_showratios", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.ACTPRIM.ordinal()) {
sp.putBoolean("hist_showactivityprimary", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.ACTSEC.ordinal()) {
sp.putBoolean("hist_showactivitysecondary", !item1.isChecked());
} else if (item1.getItemId() == OverviewFragment.CHARTTYPE.DEVSLOPE.ordinal()) {
sp.putBoolean("hist_showdevslope", !item1.isChecked());
}
updateGUI("onGraphCheckboxesCheckedChanged");
return true;
});
chartButton.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp);
popup.setOnDismissListener(menu -> chartButton.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp));
popup.show();
});
}
} }

View file

@ -17,7 +17,6 @@ enum class LTag(val tag: String, val defaultValue : Boolean = true, val requires
LOCATION("LOCATION"), LOCATION("LOCATION"),
NOTIFICATION("NOTIFICATION"), NOTIFICATION("NOTIFICATION"),
NSCLIENT("NSCLIENT"), NSCLIENT("NSCLIENT"),
OVERVIEW("OVERVIEW", defaultValue = false),
PUMP("PUMP"), PUMP("PUMP"),
PUMPBTCOMM("PUMPBTCOMM", defaultValue = false), PUMPBTCOMM("PUMPBTCOMM", defaultValue = false),
PUMPCOMM("PUMPCOMM"), PUMPCOMM("PUMPCOMM"),

View file

@ -81,7 +81,7 @@ class LoopFragment : DaggerFragment() {
loop_request?.text = it.request?.toSpanned() ?: "" loop_request?.text = it.request?.toSpanned() ?: ""
loop_constraintsprocessed?.text = it.constraintsProcessed?.toSpanned() ?: "" loop_constraintsprocessed?.text = it.constraintsProcessed?.toSpanned() ?: ""
loop_source?.text = it.source ?: "" loop_source?.text = it.source ?: ""
loop_lastrun?.text = it.lastAPSRun?.let { lastRun -> DateUtil.dateAndTimeString(lastRun.time) } loop_lastrun?.text = DateUtil.dateAndTimeString(it.lastAPSRun)
?: "" ?: ""
loop_smbrequest_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBRequest) loop_smbrequest_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBRequest)
loop_smbexecution_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBEnact) loop_smbexecution_time?.text = DateUtil.dateAndTimeAndSecondsString(it.lastSMBEnact)

View file

@ -13,6 +13,7 @@ import android.os.SystemClock;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import org.jetbrains.annotations.Nullable;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -104,7 +105,7 @@ public class LoopPlugin extends PluginBase {
public PumpEnactResult tbrSetByPump = null; public PumpEnactResult tbrSetByPump = null;
public PumpEnactResult smbSetByPump = null; public PumpEnactResult smbSetByPump = null;
public String source = null; public String source = null;
public Date lastAPSRun = null; public long lastAPSRun = DateUtil.now();
public long lastTBREnact = 0; public long lastTBREnact = 0;
public long lastSMBEnact = 0; public long lastSMBEnact = 0;
public long lastTBRRequest = 0; public long lastTBRRequest = 0;
@ -112,7 +113,7 @@ public class LoopPlugin extends PluginBase {
public long lastOpenModeAccept; public long lastOpenModeAccept;
} }
public LastRun lastRun = null; @Nullable public LastRun lastRun = null;
@Inject @Inject
public LoopPlugin( public LoopPlugin(
@ -381,7 +382,6 @@ public class LoopPlugin extends PluginBase {
if (lastRun == null) lastRun = new LastRun(); if (lastRun == null) lastRun = new LastRun();
lastRun.request = result; lastRun.request = result;
lastRun.constraintsProcessed = resultAfterConstraints; lastRun.constraintsProcessed = resultAfterConstraints;
lastRun.lastAPSRun = new Date();
lastRun.source = ((PluginBase) usedAPS).getName(); lastRun.source = ((PluginBase) usedAPS).getName();
lastRun.tbrSetByPump = null; lastRun.tbrSetByPump = null;
lastRun.smbSetByPump = null; lastRun.smbSetByPump = null;
@ -423,7 +423,7 @@ public class LoopPlugin extends PluginBase {
public void run() { public void run() {
if (result.enacted || result.success) { if (result.enacted || result.success) {
lastRun.tbrSetByPump = result; lastRun.tbrSetByPump = result;
lastRun.lastTBRRequest = lastRun.lastAPSRun.getTime(); lastRun.lastTBRRequest = lastRun.lastAPSRun;
lastRun.lastTBREnact = DateUtil.now(); lastRun.lastTBREnact = DateUtil.now();
rxBus.send(new EventLoopUpdateGui()); rxBus.send(new EventLoopUpdateGui());
applySMBRequest(resultAfterConstraints, new Callback() { applySMBRequest(resultAfterConstraints, new Callback() {
@ -432,7 +432,7 @@ public class LoopPlugin extends PluginBase {
//Callback is only called if a bolus was acutally requested //Callback is only called if a bolus was acutally requested
if (result.enacted || result.success) { if (result.enacted || result.success) {
lastRun.smbSetByPump = result; lastRun.smbSetByPump = result;
lastRun.lastSMBRequest = lastRun.lastAPSRun.getTime(); lastRun.lastSMBRequest = lastRun.lastAPSRun;
lastRun.lastSMBEnact = DateUtil.now(); lastRun.lastSMBEnact = DateUtil.now();
} else { } else {
new Thread(() -> { new Thread(() -> {
@ -512,7 +512,7 @@ public class LoopPlugin extends PluginBase {
public void run() { public void run() {
if (result.enacted) { if (result.enacted) {
lastRun.tbrSetByPump = result; lastRun.tbrSetByPump = result;
lastRun.lastTBRRequest = lastRun.lastAPSRun.getTime(); lastRun.lastTBRRequest = lastRun.lastAPSRun;
lastRun.lastTBREnact = DateUtil.now(); lastRun.lastTBREnact = DateUtil.now();
lastRun.lastOpenModeAccept = DateUtil.now(); lastRun.lastOpenModeAccept = DateUtil.now();
NSUpload.uploadDeviceStatus(lp, iobCobCalculatorPlugin, profileFunction, activePlugin.getActivePump()); NSUpload.uploadDeviceStatus(lp, iobCobCalculatorPlugin, profileFunction, activePlugin.getActivePump());

View file

@ -34,11 +34,14 @@ import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker;
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; 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.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.SafeParse; import info.nightscout.androidaps.utils.SafeParse;
import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.sharedPreferences.SP; import info.nightscout.androidaps.utils.sharedPreferences.SP;
public class DetermineBasalAdapterSMBJS { public class DetermineBasalAdapterSMBJS {
private final HasAndroidInjector injector; private final HasAndroidInjector injector;
@Inject AAPSLogger aapsLogger; @Inject AAPSLogger aapsLogger;
@ -47,6 +50,8 @@ public class DetermineBasalAdapterSMBJS {
@Inject ResourceHelper resourceHelper; @Inject ResourceHelper resourceHelper;
@Inject ProfileFunction profileFunction; @Inject ProfileFunction profileFunction;
@Inject TreatmentsPlugin treatmentsPlugin; @Inject TreatmentsPlugin treatmentsPlugin;
@Inject ActivePluginProvider activePluginProvider;
private ScriptReader mScriptReader; private ScriptReader mScriptReader;
private JSONObject mProfile; private JSONObject mProfile;
@ -57,6 +62,7 @@ public class DetermineBasalAdapterSMBJS {
private JSONObject mAutosensData = null; private JSONObject mAutosensData = null;
private boolean mMicrobolusAllowed; private boolean mMicrobolusAllowed;
private boolean mSMBAlwaysAllowed; private boolean mSMBAlwaysAllowed;
private long mCurrentTime;
private String storedCurrentTemp = null; private String storedCurrentTemp = null;
private String storedIobData = null; private String storedIobData = null;
@ -67,6 +73,7 @@ public class DetermineBasalAdapterSMBJS {
private String storedAutosens_data = null; private String storedAutosens_data = null;
private String storedMicroBolusAllowed = null; private String storedMicroBolusAllowed = null;
private String storedSMBAlwaysAllowed = null; private String storedSMBAlwaysAllowed = null;
private String storedCurrentTime = null;
private String scriptDebug = ""; private String scriptDebug = "";
@ -98,6 +105,7 @@ public class DetermineBasalAdapterSMBJS {
aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined"); aapsLogger.debug(LTag.APS, "Reservoir data: " + "undefined");
aapsLogger.debug(LTag.APS, "MicroBolusAllowed: " + (storedMicroBolusAllowed = "" + mMicrobolusAllowed)); aapsLogger.debug(LTag.APS, "MicroBolusAllowed: " + (storedMicroBolusAllowed = "" + mMicrobolusAllowed));
aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: " + (storedSMBAlwaysAllowed = "" + mSMBAlwaysAllowed)); aapsLogger.debug(LTag.APS, "SMBAlwaysAllowed: " + (storedSMBAlwaysAllowed = "" + mSMBAlwaysAllowed));
aapsLogger.debug(LTag.APS, "CurrentTime: " + (storedCurrentTime = "" + mCurrentTime));
DetermineBasalResultSMB determineBasalResultSMB = null; DetermineBasalResultSMB determineBasalResultSMB = null;
@ -140,7 +148,8 @@ public class DetermineBasalAdapterSMBJS {
makeParam(mMealData, rhino, scope), makeParam(mMealData, rhino, scope),
setTempBasalFunctionsObj, setTempBasalFunctionsObj,
new Boolean(mMicrobolusAllowed), 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 boolean advancedFiltering
) throws JSONException { ) throws JSONException {
String units = profile.getUnits();
Double pumpbolusstep = activePluginProvider.getActivePump().getPumpDescription().bolusStep;
mProfile = new JSONObject(); mProfile = new JSONObject();
mProfile.put("max_iob", maxIob); mProfile.put("max_iob", maxIob);
@ -242,7 +253,6 @@ public class DetermineBasalAdapterSMBJS {
mProfile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3)); mProfile.put("max_daily_safety_multiplier", sp.getInt(R.string.key_openapsama_max_daily_safety_multiplier, 3));
mProfile.put("current_basal_safety_multiplier", sp.getDouble(R.string.key_openapsama_current_basal_safety_multiplier, 4d)); mProfile.put("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("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", SP.getBoolean(R.string.key_low_temptarget_lowers_sensitivity, SMBDefaults.low_temptarget_lowers_sensitivity));
@ -267,13 +277,17 @@ public class DetermineBasalAdapterSMBJS {
mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable); mProfile.put("A52_risk_enable", SMBDefaults.A52_risk_enable);
boolean smbEnabled = sp.getBoolean(resourceHelper.gs(R.string.key_use_smb), false); 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_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("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("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_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("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("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("current_basal", basalrate);
mProfile.put("temptargetSet", tempTargetSet); mProfile.put("temptargetSet", tempTargetSet);
@ -302,6 +316,7 @@ public class DetermineBasalAdapterSMBJS {
mGlucoseStatus = new JSONObject(); mGlucoseStatus = new JSONObject();
mGlucoseStatus.put("glucose", glucoseStatus.glucose); mGlucoseStatus.put("glucose", glucoseStatus.glucose);
mGlucoseStatus.put("noise", glucoseStatus.noise);
if (sp.getBoolean(R.string.key_always_use_shortavg, false)) { if (sp.getBoolean(R.string.key_always_use_shortavg, false)) {
mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta); mGlucoseStatus.put("delta", glucoseStatus.short_avgdelta);
@ -332,6 +347,8 @@ public class DetermineBasalAdapterSMBJS {
mMicrobolusAllowed = microBolusAllowed; mMicrobolusAllowed = microBolusAllowed;
mSMBAlwaysAllowed = advancedFiltering; mSMBAlwaysAllowed = advancedFiltering;
mCurrentTime = now;
} }
private Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) { private Object makeParam(JSONObject jsonObject, Context rhino, Scriptable scope) {

View file

@ -55,10 +55,13 @@ public class SMBDefaults {
// *** WARNING *** DO NOT USE enableSMB_always or enableSMB_after_carbs with xDrip+, Libre, or similar // *** 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 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 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 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) // curve:"rapid-acting" // Supported curves: "bilinear", "rapid-acting" (Novolog, Novorapid, Humalog, Apidra) and "ultra-rapid" (Fiasp)
// useCustomPeakTime:false // allows changing insulinPeakTime // useCustomPeakTime:false // allows changing insulinPeakTime
// insulinPeakTime:75 // number of minutes after a bolus activity peaks. defaults to 55m for Fiasp if useCustomPeakTime: false // 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 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 // 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
} }

View file

@ -17,6 +17,7 @@ import info.nightscout.androidaps.activities.PreferencesActivity
import info.nightscout.androidaps.events.EventRebuildTabs import info.nightscout.androidaps.events.EventRebuildTabs
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui
import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.extensions.plusAssign import info.nightscout.androidaps.utils.extensions.plusAssign
import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck

View file

@ -9,7 +9,6 @@ import java.util.ArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import dagger.Lazy;
import dagger.android.HasAndroidInjector; import dagger.android.HasAndroidInjector;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.events.EventAppInitialized; import info.nightscout.androidaps.events.EventAppInitialized;
@ -18,7 +17,6 @@ import info.nightscout.androidaps.events.EventRebuildTabs;
import info.nightscout.androidaps.interfaces.APSInterface; import info.nightscout.androidaps.interfaces.APSInterface;
import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.ActivePluginProvider;
import info.nightscout.androidaps.interfaces.BgSourceInterface; import info.nightscout.androidaps.interfaces.BgSourceInterface;
import info.nightscout.androidaps.interfaces.CommandQueueProvider;
import info.nightscout.androidaps.interfaces.InsulinInterface; import info.nightscout.androidaps.interfaces.InsulinInterface;
import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginDescription;
@ -30,13 +28,7 @@ import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.AAPSLogger;
import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.logging.LTag;
import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.bus.RxBusWrapper;
import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin; import info.nightscout.androidaps.plugins.configBuilder.events.EventConfigBuilderUpdateGui;
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin;
import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin;
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin;
import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin;
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.OKDialog;
import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.sharedPreferences.SP; import info.nightscout.androidaps.utils.sharedPreferences.SP;

View file

@ -1,12 +1,11 @@
package info.nightscout.androidaps.plugins.configBuilder package info.nightscout.androidaps.plugins.configBuilder
import info.nightscout.androidaps.Config
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.collections.ArrayList
@Singleton @Singleton
class PluginStore @Inject constructor( class PluginStore @Inject constructor(
@ -27,6 +26,7 @@ class PluginStore @Inject constructor(
return pluginStore!! return pluginStore!!
} }
} }
lateinit var plugins: List<@JvmSuppressWildcards PluginBase> lateinit var plugins: List<@JvmSuppressWildcards PluginBase>
private var activeBgSource: BgSourceInterface? = null private var activeBgSource: BgSourceInterface? = null
@ -83,6 +83,7 @@ class PluginStore @Inject constructor(
var pluginsInCategory: ArrayList<PluginBase>? var pluginsInCategory: ArrayList<PluginBase>?
// PluginType.APS // PluginType.APS
if (!Config.NSCLIENT && !Config.PUMPCONTROL) {
pluginsInCategory = getSpecificPluginsList(PluginType.APS) pluginsInCategory = getSpecificPluginsList(PluginType.APS)
activeAPS = getTheOneEnabledInArray(pluginsInCategory, PluginType.APS) as APSInterface? activeAPS = getTheOneEnabledInArray(pluginsInCategory, PluginType.APS) as APSInterface?
if (activeAPS == null) { if (activeAPS == null) {
@ -91,6 +92,7 @@ class PluginStore @Inject constructor(
aapsLogger.debug(LTag.CONFIGBUILDER, "Defaulting APSInterface") aapsLogger.debug(LTag.CONFIGBUILDER, "Defaulting APSInterface")
} }
setFragmentVisiblities((activeAPS as PluginBase).name, pluginsInCategory, PluginType.APS) setFragmentVisiblities((activeAPS as PluginBase).name, pluginsInCategory, PluginType.APS)
}
// PluginType.INSULIN // PluginType.INSULIN
pluginsInCategory = getSpecificPluginsList(PluginType.INSULIN) pluginsInCategory = getSpecificPluginsList(PluginType.INSULIN)

View file

@ -1,4 +1,4 @@
package info.nightscout.androidaps.plugins.configBuilder package info.nightscout.androidaps.plugins.configBuilder.events
import info.nightscout.androidaps.events.EventUpdateGui import info.nightscout.androidaps.events.EventUpdateGui

View file

@ -56,7 +56,7 @@ class VersionCheckerUtils @Inject constructor(
} }
}.start() }.start()
} else } else
aapsLogger.debug(LTag.CORE, "Github master version no checked. No connectivity") aapsLogger.debug(LTag.CORE, "Github master version not checked. No connectivity")
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) { fun compareWithCurrentVersion(newVersion: String?, currentVersion: String) {

View file

@ -125,7 +125,7 @@ class DataBroadcastPlugin @Inject constructor(
private fun bgStatus(bundle: Bundle) { private fun bgStatus(bundle: Bundle) {
val lastBG: BgReading = iobCobCalculatorPlugin.lastBg() ?: return val lastBG: BgReading = iobCobCalculatorPlugin.lastBg() ?: return
val glucoseStatus = GlucoseStatus(injector).getGlucoseStatusData() ?: return val glucoseStatus = GlucoseStatus(injector).glucoseStatusData ?: return
bundle.putDouble("glucoseMgdl", lastBG.value) // last BG in mgdl bundle.putDouble("glucoseMgdl", lastBG.value) // last BG in mgdl
bundle.putLong("glucoseTimeStamp", lastBG.date) // timestamp bundle.putLong("glucoseTimeStamp", lastBG.date) // timestamp
@ -158,10 +158,9 @@ class DataBroadcastPlugin @Inject constructor(
bundle.putInt("rigBattery", nsDeviceStatus.uploaderStatus.replace("%", "").trim { it <= ' ' }.toInt()) bundle.putInt("rigBattery", nsDeviceStatus.uploaderStatus.replace("%", "").trim { it <= ' ' }.toInt())
if (Config.APS && lazyLoopPlugin.get().lastRun?.lastTBREnact != 0L) { //we are AndroidAPS if (Config.APS && lazyLoopPlugin.get().lastRun?.lastTBREnact != 0L) { //we are AndroidAPS
bundle.putLong("suggestedTimeStamp", lazyLoopPlugin.get().lastRun?.lastAPSRun?.time bundle.putLong("suggestedTimeStamp", lazyLoopPlugin.get().lastRun?.lastAPSRun ?: -1L)
?: -1L)
bundle.putString("suggested", lazyLoopPlugin.get().lastRun?.request?.json().toString()) bundle.putString("suggested", lazyLoopPlugin.get().lastRun?.request?.json().toString())
if (lazyLoopPlugin.get().lastRun.tbrSetByPump != null && lazyLoopPlugin.get().lastRun.tbrSetByPump.enacted) { if (lazyLoopPlugin.get().lastRun?.tbrSetByPump != null && lazyLoopPlugin.get().lastRun?.tbrSetByPump?.enacted == true) {
bundle.putLong("enactedTimeStamp", lazyLoopPlugin.get().lastRun?.lastTBREnact bundle.putLong("enactedTimeStamp", lazyLoopPlugin.get().lastRun?.lastTBREnact
?: -1L) ?: -1L)
bundle.putString("enacted", lazyLoopPlugin.get().lastRun?.request?.json().toString()) bundle.putString("enacted", lazyLoopPlugin.get().lastRun?.request?.json().toString())

View file

@ -125,13 +125,13 @@ class ImportExportPrefs @Inject constructor(
val n5 = Settings.Secure.getString(context.contentResolver, "lock_screen_owner_info") val n5 = Settings.Secure.getString(context.contentResolver, "lock_screen_owner_info")
val n6 = Settings.Global.getString(context.contentResolver, "device_name") val n6 = Settings.Global.getString(context.contentResolver, "device_name")
// name we use for SMS OTP token in communicator // name provided (hopefully) by user
val otpName = otp.name().trim() val patientName = sp.getString(R.string.key_patient_name, "")
val defaultOtpName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name) val defaultPatientName = resourceHelper.gs(R.string.patient_name_default)
// name we detect from OS // name we detect from OS
val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultOtpName val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultPatientName
val name = if (otpName.length > 0 && otpName != defaultOtpName) otpName else systemName val name = if (patientName.isNotEmpty() && patientName != defaultPatientName) patientName else systemName
return name return name
} }

View file

@ -173,7 +173,7 @@ public class NSUpload {
DeviceStatus deviceStatus = new DeviceStatus(); DeviceStatus deviceStatus = new DeviceStatus();
try { try {
LoopPlugin.LastRun lastRun = loopPlugin.lastRun; LoopPlugin.LastRun lastRun = loopPlugin.lastRun;
if (lastRun != null && lastRun.lastAPSRun.getTime() > System.currentTimeMillis() - 300 * 1000L) { if (lastRun != null && lastRun.lastAPSRun > System.currentTimeMillis() - 300 * 1000L) {
// do not send if result is older than 1 min // do not send if result is older than 1 min
APSResult apsResult = lastRun.request; APSResult apsResult = lastRun.request;
apsResult.json().put("timestamp", DateUtil.toISOString(lastRun.lastAPSRun)); apsResult.json().put("timestamp", DateUtil.toISOString(lastRun.lastAPSRun));

View file

@ -0,0 +1,825 @@
package info.nightscout.androidaps.plugins.general.overview
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Paint
import android.os.Bundle
import android.os.Handler
import android.util.DisplayMetrics
import android.util.TypedValue
import android.view.ContextMenu
import android.view.ContextMenu.ContextMenuInfo
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.android.HasAndroidInjector
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.Config
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.dialogs.CalibrationDialog
import info.nightscout.androidaps.dialogs.CarbsDialog
import info.nightscout.androidaps.dialogs.InsulinDialog
import info.nightscout.androidaps.dialogs.TreatmentDialog
import info.nightscout.androidaps.dialogs.WizardDialog
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.Constraint
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.aps.loop.events.EventNewOpenLoopNotification
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.general.wear.ActionStringHandler
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventIobCalculationProgress
import info.nightscout.androidaps.plugins.source.DexcomPlugin
import info.nightscout.androidaps.plugins.source.XdripPlugin
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import info.nightscout.androidaps.utils.wizard.QuickWizard
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.careportal_stats_fragment.*
import kotlinx.android.synthetic.main.overview_fragment.*
import kotlinx.android.synthetic.main.overview_fragment.overview_activeprofile
import kotlinx.android.synthetic.main.overview_fragment.overview_apsmode
import kotlinx.android.synthetic.main.overview_fragment.overview_arrow
import kotlinx.android.synthetic.main.overview_fragment.overview_basebasal
import kotlinx.android.synthetic.main.overview_fragment.overview_bg
import kotlinx.android.synthetic.main.overview_fragment.overview_bggraph
import kotlinx.android.synthetic.main.overview_fragment.overview_carbsbutton
import kotlinx.android.synthetic.main.overview_fragment.overview_chartMenuButton
import kotlinx.android.synthetic.main.overview_fragment.overview_cob
import kotlinx.android.synthetic.main.overview_fragment.overview_extendedbolus
import kotlinx.android.synthetic.main.overview_fragment.overview_insulinbutton
import kotlinx.android.synthetic.main.overview_fragment.overview_iob
import kotlinx.android.synthetic.main.overview_fragment.overview_iobcalculationprogess
import kotlinx.android.synthetic.main.overview_fragment.overview_iobgraph
import kotlinx.android.synthetic.main.overview_fragment.overview_looplayout
import kotlinx.android.synthetic.main.overview_fragment.overview_notifications
import kotlinx.android.synthetic.main.overview_fragment.overview_pumpstatus
import kotlinx.android.synthetic.main.overview_fragment.overview_pumpstatuslayout
import kotlinx.android.synthetic.main.overview_fragment.overview_quickwizardbutton
import kotlinx.android.synthetic.main.overview_fragment.overview_sensitivity
import kotlinx.android.synthetic.main.overview_fragment.overview_temptarget
import kotlinx.android.synthetic.main.overview_fragment.overview_treatmentbutton
import kotlinx.android.synthetic.main.overview_fragment.overview_wizardbutton
import kotlinx.android.synthetic.main.overview_fragment_nsclient_tablet.*
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickListener {
@Inject lateinit var injector: HasAndroidInjector
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var sp: SP
@Inject lateinit var rxBus: RxBusWrapper
@Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var statusLightHandler: StatusLightHandler
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var loopPlugin: LoopPlugin
@Inject lateinit var configBuilderPlugin: ConfigBuilderPlugin
@Inject lateinit var activePlugin: ActivePluginProvider
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin
@Inject lateinit var dexcomPlugin: DexcomPlugin
@Inject lateinit var xdripPlugin: XdripPlugin
@Inject lateinit var notificationStore: NotificationStore
@Inject lateinit var actionStringHandler: ActionStringHandler
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var buildHelper: BuildHelper
@Inject lateinit var commandQueue: CommandQueue
@Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var overviewMenus: OverviewMenus
private val disposable = CompositeDisposable()
private var smallWidth = false
private var smallHeight = false
private lateinit var dm: DisplayMetrics
private var rangeToDisplay = 6 // for graph
private var loopHandler = Handler()
private var refreshLoop: Runnable? = null
private val worker = Executors.newSingleThreadScheduledExecutor()
private var scheduledUpdate: ScheduledFuture<*>? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
//check screen width
dm = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getMetrics(dm)
val screenWidth = dm.widthPixels
val screenHeight = dm.heightPixels
smallWidth = screenWidth <= Constants.SMALL_WIDTH
smallHeight = screenHeight <= Constants.SMALL_HEIGHT
val landscape = screenHeight < screenWidth
return when {
resourceHelper.gb(R.bool.isTablet) && Config.NSCLIENT ->
inflater.inflate(R.layout.overview_fragment_nsclient_tablet, container, false)
Config.NSCLIENT ->
inflater.inflate(R.layout.overview_fragment_nsclient, container, false)
smallHeight || landscape ->
inflater.inflate(R.layout.overview_fragment_landscape, container, false)
else ->
inflater.inflate(R.layout.overview_fragment, container, false)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (smallWidth) overview_arrow?.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 35f)
overview_pumpstatus?.setBackgroundColor(resourceHelper.gc(R.color.colorInitializingBorder))
overview_notifications?.setHasFixedSize(false)
overview_notifications?.layoutManager = LinearLayoutManager(view.context)
val axisWidth = if (dm.densityDpi <= 120) 3 else if (dm.densityDpi <= 160) 10 else if (dm.densityDpi <= 320) 35 else if (dm.densityDpi <= 420) 50 else if (dm.densityDpi <= 560) 70 else 80
overview_bggraph?.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
overview_bggraph?.gridLabelRenderer?.reloadStyles()
overview_iobgraph?.gridLabelRenderer?.gridColor = resourceHelper.gc(R.color.graphgrid)
overview_iobgraph?.gridLabelRenderer?.reloadStyles()
overview_iobgraph?.gridLabelRenderer?.isHorizontalLabelsVisible = false
overview_bggraph?.gridLabelRenderer?.labelVerticalWidth = axisWidth
overview_iobgraph?.gridLabelRenderer?.labelVerticalWidth = axisWidth
overview_iobgraph?.gridLabelRenderer?.numVerticalLabels = 3
rangeToDisplay = sp.getInt(R.string.key_rangetodisplay, 6)
overview_bggraph?.setOnLongClickListener {
rangeToDisplay += 6
rangeToDisplay = if (rangeToDisplay > 24) 6 else rangeToDisplay
sp.putInt(R.string.key_rangetodisplay, rangeToDisplay)
updateGUI("rangeChange")
sp.putBoolean(R.string.key_objectiveusescale, true)
false
}
overviewMenus.setupChartMenu(overview_chartMenuButton)
overview_accepttempbutton?.setOnClickListener(this)
overview_treatmentbutton?.setOnClickListener(this)
overview_wizardbutton?.setOnClickListener(this)
overview_calibrationbutton?.setOnClickListener(this)
overview_cgmbutton?.setOnClickListener(this)
overview_insulinbutton?.setOnClickListener(this)
overview_carbsbutton?.setOnClickListener(this)
overview_quickwizardbutton?.setOnClickListener(this)
overview_quickwizardbutton?.setOnLongClickListener(this)
}
override fun onPause() {
super.onPause()
disposable.clear()
loopHandler.removeCallbacksAndMessages(null)
overview_apsmode?.let { unregisterForContextMenu(it) }
overview_activeprofile?.let { unregisterForContextMenu(it) }
overview_temptarget?.let { unregisterForContextMenu(it) }
}
override fun onResume() {
super.onResume()
disposable.add(rxBus
.toObservable(EventRefreshOverview::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI(it.from) }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventExtendedBolusChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventTempBasalChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventTreatmentChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventTempTargetChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventTempTargetChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventAcceptOpenLoopChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventAcceptOpenLoopChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventCareportalEventChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventCareportalEventChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventInitializationChanged::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventInitializationChanged") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventAutosensCalculationFinished") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventProfileNeedsUpdate::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventProfileNeedsUpdate") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventPreferenceChange") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventNewOpenLoopNotification::class.java)
.observeOn(Schedulers.io())
.subscribe({ scheduleUpdateGUI("EventNewOpenLoopNotification") }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ updatePumpStatus(it) }) { fabricPrivacy.logException(it) })
disposable.add(rxBus
.toObservable(EventIobCalculationProgress::class.java)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ overview_iobcalculationprogess?.text = it.progress }) { fabricPrivacy.logException(it) })
refreshLoop = Runnable {
scheduleUpdateGUI("refreshLoop")
loopHandler.postDelayed(refreshLoop, 60 * 1000L)
}
loopHandler.postDelayed(refreshLoop, 60 * 1000L)
overview_apsmode?.let { registerForContextMenu(overview_apsmode) }
overview_activeprofile?.let { registerForContextMenu(it) }
overview_temptarget?.let { registerForContextMenu(it) }
updateGUI("onResume")
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
overviewMenus.createContextMenu(menu, v)
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val manager = fragmentManager
return if (manager != null && overviewMenus.onContextItemSelected(item, manager)) true else super.onContextItemSelected(item)
}
override fun onClick(v: View) {
val manager = fragmentManager ?: return
// try to fix https://fabric.io/nightscout3/android/apps/info.nightscout.androidaps/issues/5aca7a1536c7b23527eb4be7?time=last-seven-days
// https://stackoverflow.com/questions/14860239/checking-if-state-is-saved-before-committing-a-fragmenttransaction
if (manager.isStateSaved) return
activity?.let { activity ->
when (v.id) {
R.id.overview_treatmentbutton -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, Runnable { TreatmentDialog().show(manager, "Overview") })
R.id.overview_wizardbutton -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, Runnable { WizardDialog().show(manager, "Overview") })
R.id.overview_insulinbutton -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, Runnable { InsulinDialog().show(manager, "Overview") })
R.id.overview_quickwizardbutton -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, Runnable { onClickQuickWizard() })
R.id.overview_carbsbutton -> protectionCheck.queryProtection(activity, ProtectionCheck.Protection.BOLUS, Runnable { CarbsDialog().show(manager, "Overview") })
R.id.overview_pumpstatus -> {
if (activePlugin.activePump.isSuspended || !activePlugin.activePump.isInitialized) commandQueue.readStatus("RefreshClicked", null)
}
R.id.overview_cgmbutton -> {
if (xdripPlugin.isEnabled(PluginType.BGSOURCE))
openCgmApp("com.eveningoutpost.dexdrip")
else if (dexcomPlugin.isEnabled(PluginType.BGSOURCE)) {
dexcomPlugin.findDexcomPackageName()?.let {
openCgmApp(it)
}
?: ToastUtils.showToastInUiThread(activity, resourceHelper.gs(R.string.dexcom_app_not_installed))
}
}
R.id.overview_calibrationbutton -> {
if (xdripPlugin.isEnabled(PluginType.BGSOURCE)) {
CalibrationDialog().show(manager, "CalibrationDialog")
} else if (dexcomPlugin.isEnabled(PluginType.BGSOURCE)) {
try {
dexcomPlugin.findDexcomPackageName()?.let {
startActivity(Intent("com.dexcom.cgm.activities.MeterEntryActivity").setPackage(it))
}
?: ToastUtils.showToastInUiThread(activity, resourceHelper.gs(R.string.dexcom_app_not_installed))
} catch (e: ActivityNotFoundException) {
ToastUtils.showToastInUiThread(activity, resourceHelper.gs(R.string.g5appnotdetected))
}
}
}
R.id.overview_accepttempbutton -> {
profileFunction.getProfile() ?: return
if (loopPlugin.isEnabled(PluginType.LOOP)) {
val lastRun = loopPlugin.lastRun
loopPlugin.invoke("Accept temp button", false)
if (lastRun?.lastAPSRun != null && lastRun.constraintsProcessed.isChangeRequested) {
OKDialog.showConfirmation(activity, resourceHelper.gs(R.string.pump_tempbasal_label), lastRun.constraintsProcessed.toSpanned(), Runnable {
aapsLogger.debug("USER ENTRY: ACCEPT TEMP BASAL")
overview_accepttempbutton?.visibility = View.GONE
(context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).cancel(Constants.notificationID)
actionStringHandler.handleInitiate("cancelChangeRequest")
loopPlugin.acceptChangeRequest()
})
}
}
}
}
}
}
private fun openCgmApp(packageName: String) {
context?.let {
val packageManager = it.packageManager
try {
val intent = packageManager.getLaunchIntentForPackage(packageName)
?: throw ActivityNotFoundException()
intent.addCategory(Intent.CATEGORY_LAUNCHER)
it.startActivity(intent)
} catch (e: ActivityNotFoundException) {
OKDialog.show(it, "", resourceHelper.gs(R.string.error_starting_cgm))
}
}
}
override fun onLongClick(v: View): Boolean {
when (v.id) {
R.id.overview_quickwizardbutton -> {
startActivity(Intent(v.context, QuickWizardListActivity::class.java))
return true
}
}
return false
}
private fun onClickQuickWizard() {
val actualBg = iobCobCalculatorPlugin.actualBg()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val pump = activePlugin.activePump
val quickWizardEntry = quickWizard.getActive()
if (quickWizardEntry != null && actualBg != null && profile != null) {
overview_quickwizardbutton?.visibility = View.VISIBLE
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
if (wizard.calculatedTotalInsulin > 0.0 && quickWizardEntry.carbs() > 0.0) {
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
activity?.let {
if (abs(wizard.insulinAfterConstraints - wizard.calculatedTotalInsulin) >= pump.pumpDescription.pumpType.determineCorrectBolusStepSize(wizard.insulinAfterConstraints) || carbsAfterConstraints != quickWizardEntry.carbs()) {
OKDialog.show(it, resourceHelper.gs(R.string.treatmentdeliveryerror), resourceHelper.gs(R.string.constraints_violation) + "\n" + resourceHelper.gs(R.string.changeyourinput))
return
}
wizard.confirmAndExecute(it)
}
}
}
}
private fun updatePumpStatus(event: EventPumpStatusChanged) {
val status = event.getStatus(resourceHelper)
if (status != "") {
overview_pumpstatus?.text = status
overview_pumpstatuslayout?.visibility = View.VISIBLE
overview_looplayout?.visibility = View.GONE
} else {
overview_pumpstatuslayout?.visibility = View.GONE
overview_looplayout?.visibility = View.VISIBLE
}
}
@SuppressLint("SetTextI18n")
private fun processButtonsVisibility() {
val lastBG = iobCobCalculatorPlugin.lastBg()
val pump = activePlugin.activePump
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val actualBG = iobCobCalculatorPlugin.actualBg()
// QuickWizard button
val quickWizardEntry = quickWizard.getActive()
if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized && !pump.isSuspended) {
overview_quickwizardbutton?.visibility = View.VISIBLE
val wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false)
overview_quickwizardbutton?.text = quickWizardEntry.buttonText() + "\n" + resourceHelper.gs(R.string.format_carbs, quickWizardEntry.carbs()) +
" " + resourceHelper.gs(R.string.formatinsulinunits, wizard.calculatedTotalInsulin)
if (wizard.calculatedTotalInsulin <= 0) overview_quickwizardbutton?.visibility = View.GONE
} else overview_quickwizardbutton?.visibility = View.GONE
// **** Temp button ****
val lastRun = loopPlugin.lastRun
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
val showAcceptButton = !closedLoopEnabled.value() && // Open mode needed
lastRun != null &&
(lastRun.lastOpenModeAccept == 0L || lastRun.lastOpenModeAccept < lastRun.lastAPSRun) &&// never accepted or before last result
lastRun.constraintsProcessed.isChangeRequested // change is requested
if (showAcceptButton && pump.isInitialized && !pump.isSuspended && loopPlugin.isEnabled(PluginType.LOOP)) {
overview_accepttempbutton?.visibility = View.VISIBLE
overview_accepttempbutton?.text = "${resourceHelper.gs(R.string.setbasalquestion)}\n${lastRun!!.constraintsProcessed}"
} else {
overview_accepttempbutton?.visibility = View.GONE
}
// **** Various treatment buttons ****
overview_carbsbutton?.visibility = ((!activePlugin.activePump.pumpDescription.storesCarbInfo || pump.isInitialized && !pump.isSuspended) && profile != null && sp.getBoolean(R.string.key_show_carbs_button, true)).toVisibility()
overview_treatmentbutton?.visibility = (pump.isInitialized && !pump.isSuspended && profile != null && sp.getBoolean(R.string.key_show_treatment_button, false)).toVisibility()
overview_wizardbutton?.visibility = (pump.isInitialized && !pump.isSuspended && profile != null && sp.getBoolean(R.string.key_show_wizard_button, true)).toVisibility()
overview_insulinbutton?.visibility = (pump.isInitialized && !pump.isSuspended && profile != null && sp.getBoolean(R.string.key_show_insulin_button, true)).toVisibility()
// **** Calibration & CGM buttons ****
val xDripIsBgSource = xdripPlugin.isEnabled(PluginType.BGSOURCE)
val dexcomIsSource = dexcomPlugin.isEnabled(PluginType.BGSOURCE)
overview_calibrationbutton?.visibility = ((xDripIsBgSource || dexcomIsSource) && actualBG != null && sp.getBoolean(R.string.key_show_calibration_button, true)).toVisibility()
overview_cgmbutton?.visibility = (sp.getBoolean(R.string.key_show_cgm_button, false) && (xDripIsBgSource || dexcomIsSource)).toVisibility()
}
private fun scheduleUpdateGUI(from: String) {
class UpdateRunnable : Runnable {
override fun run() {
activity?.runOnUiThread {
updateGUI(from)
scheduledUpdate = null
}
}
}
// prepare task for execution in 500 milliseconds
// cancel waiting task to prevent multiple updates
scheduledUpdate?.cancel(false)
val task: Runnable = UpdateRunnable()
scheduledUpdate = worker.schedule(task, 500, TimeUnit.MILLISECONDS)
}
@SuppressLint("SetTextI18n")
fun updateGUI(from: String) {
aapsLogger.debug("UpdateGUI from $from")
overview_time?.text = DateUtil.timeString(Date())
if (!profileFunction.isProfileValid("Overview")) {
overview_pumpstatus?.setText(R.string.noprofileset)
overview_pumpstatuslayout?.visibility = View.VISIBLE
overview_looplayout?.visibility = View.GONE
return
}
notificationStore.updateNotifications(overview_notifications)
overview_pumpstatuslayout?.visibility = View.GONE
overview_looplayout?.visibility = View.VISIBLE
val profile = profileFunction.getProfile() ?: return
val actualBG = iobCobCalculatorPlugin.actualBg()
val lastBG = iobCobCalculatorPlugin.lastBg()
val pump = activePlugin.activePump
val units = profileFunction.getUnits()
val lowLine = defaultValueHelper.determineLowLine()
val highLine = defaultValueHelper.determineHighLine()
//Start with updating the BG as it is unaffected by loop.
// **** BG value ****
if (lastBG != null) {
val color = when {
lastBG.valueToUnits(units) < lowLine -> resourceHelper.gc(R.color.low)
lastBG.valueToUnits(units) > highLine -> resourceHelper.gc(R.color.high)
else -> resourceHelper.gc(R.color.inrange)
}
overview_bg?.text = lastBG.valueToUnitsToString(units)
overview_bg?.setTextColor(color)
overview_arrow?.text = lastBG.directionToSymbol()
overview_arrow?.setTextColor(color)
val glucoseStatus = GlucoseStatus(injector).glucoseStatusData
if (glucoseStatus != null) {
overview_delta?.text = "Δ ${Profile.toUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)} $units"
overview_deltashort?.text = Profile.toSignedUnitsString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units)
overview_avgdelta?.text = "øΔ15m: ${Profile.toUnitsString(glucoseStatus.short_avgdelta, glucoseStatus.short_avgdelta * Constants.MGDL_TO_MMOLL, units)}\nøΔ40m: ${Profile.toUnitsString(glucoseStatus.long_avgdelta, glucoseStatus.long_avgdelta * Constants.MGDL_TO_MMOLL, units)}"
} else {
overview_delta?.text = "Δ " + resourceHelper.gs(R.string.notavailable)
overview_deltashort?.text = "---"
overview_avgdelta?.text = ""
}
// strike through if BG is old
overview_bg?.let { overview_bg ->
var flag = overview_bg.paintFlags
flag = if (actualBG == null) {
flag or Paint.STRIKE_THRU_TEXT_FLAG
} else flag and Paint.STRIKE_THRU_TEXT_FLAG.inv()
overview_bg.paintFlags = flag
}
overview_timeago?.text = DateUtil.minAgo(resourceHelper, lastBG.date)
overview_timeagoshort?.text = "(" + DateUtil.minAgoShort(lastBG.date) + ")"
}
val closedLoopEnabled = constraintChecker.isClosedLoopAllowed()
// open loop mode
if (Config.APS && pump.pumpDescription.isTempBasalCapable) {
overview_apsmode?.visibility = View.VISIBLE
when {
loopPlugin.isEnabled(PluginType.LOOP) && loopPlugin.isSuperBolus -> {
overview_apsmode?.text = String.format(resourceHelper.gs(R.string.loopsuperbolusfor), loopPlugin.minutesToEndOfSuspend())
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
}
loopPlugin.isDisconnected -> {
overview_apsmode?.text = String.format(resourceHelper.gs(R.string.loopdisconnectedfor), loopPlugin.minutesToEndOfSuspend())
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonCritical))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextCritical))
}
loopPlugin.isEnabled(PluginType.LOOP) && loopPlugin.isSuspended -> {
overview_apsmode?.text = String.format(resourceHelper.gs(R.string.loopsuspendedfor), loopPlugin.minutesToEndOfSuspend())
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
}
pump.isSuspended -> {
overview_apsmode?.text = resourceHelper.gs(R.string.pumpsuspended)
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
}
loopPlugin.isEnabled(PluginType.LOOP) -> {
overview_apsmode?.text = if (closedLoopEnabled.value()) resourceHelper.gs(R.string.closedloop) else resourceHelper.gs(R.string.openloop)
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
}
else -> {
overview_apsmode?.text = resourceHelper.gs(R.string.disabledloop)
overview_apsmode?.setBackgroundColor(resourceHelper.gc(R.color.ribbonCritical))
overview_apsmode?.setTextColor(resourceHelper.gc(R.color.ribbonTextCritical))
}
}
} else {
overview_apsmode?.visibility = View.GONE
}
// temp target
val tempTarget = treatmentsPlugin.tempTargetFromHistory
if (tempTarget != null) {
overview_temptarget?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
overview_temptarget?.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
overview_temptarget?.text = Profile.toTargetRangeString(tempTarget.low, tempTarget.high, Constants.MGDL, units) + " " + DateUtil.untilString(tempTarget.end(), resourceHelper)
} else {
overview_temptarget?.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
overview_temptarget?.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
overview_temptarget?.text = Profile.toTargetRangeString(profile.targetLowMgdl, profile.targetHighMgdl, Constants.MGDL, units)
}
// Basal, TBR
val activeTemp = treatmentsPlugin.getTempBasalFromHistory(System.currentTimeMillis())
overview_basebasal?.text = activeTemp?.let { if (resourceHelper.shortTextMode()) "T: " + activeTemp.toStringVeryShort() else activeTemp.toStringFull() }
?: resourceHelper.gs(R.string.pump_basebasalrate, profile.basal)
overview_basebasal?.setOnClickListener {
var fullText = "${resourceHelper.gs(R.string.pump_basebasalrate_label)}: ${resourceHelper.gs(R.string.pump_basebasalrate, profile.basal)}"
if (activeTemp != null)
fullText += "\n" + resourceHelper.gs(R.string.pump_tempbasal_label) + ": " + activeTemp.toStringFull()
activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.basal), fullText)
}
}
overview_basebasal?.setTextColor(activeTemp?.let { resourceHelper.gc(R.color.basal) }
?: resourceHelper.gc(R.color.defaulttextcolor))
// Extended bolus
val extendedBolus = treatmentsPlugin.getExtendedBolusFromHistory(System.currentTimeMillis())
overview_extendedbolus?.text = if (extendedBolus != null && !pump.isFakingTempsByExtendedBoluses) {
if (resourceHelper.shortTextMode()) resourceHelper.gs(R.string.pump_basebasalrate, extendedBolus.absoluteRate())
else extendedBolus.toStringMedium()
} else ""
overview_extendedbolus?.setOnClickListener {
if (extendedBolus != null) activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.extended_bolus), extendedBolus.toString())
}
}
overview_activeprofile?.text = profileFunction.getProfileNameWithDuration()
if (profile.percentage != 100 || profile.timeshift != 0) {
overview_activeprofile?.setBackgroundColor(resourceHelper.gc(R.color.ribbonWarning))
overview_activeprofile?.setTextColor(resourceHelper.gc(R.color.ribbonTextWarning))
} else {
overview_activeprofile?.setBackgroundColor(resourceHelper.gc(R.color.ribbonDefault))
overview_activeprofile?.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault))
}
processButtonsVisibility()
// iob
treatmentsPlugin.updateTotalIOBTreatments()
treatmentsPlugin.updateTotalIOBTempBasals()
val bolusIob = treatmentsPlugin.lastCalculationTreatments.round()
val basalIob = treatmentsPlugin.lastCalculationTempBasals.round()
overview_iob?.text = when {
resourceHelper.shortTextMode() -> {
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob)
}
resourceHelper.gb(R.bool.isTablet) -> {
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + " (" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) + ")"
}
else -> {
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + " (" +
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "/" +
resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob) + ")"
}
}
overview_iob?.setOnClickListener {
activity?.let {
OKDialog.show(it, resourceHelper.gs(R.string.iob),
resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob + basalIob.basaliob) + "\n" +
resourceHelper.gs(R.string.bolus) + ": " + resourceHelper.gs(R.string.formatinsulinunits, bolusIob.iob) + "\n" +
resourceHelper.gs(R.string.basal) + ": " + resourceHelper.gs(R.string.formatinsulinunits, basalIob.basaliob)
)
}
}
// NSClient mode
statusLightHandler.updateAge(careportal_sensorage, careportal_insulinage, careportal_canulaage, careportal_pbage)
// Mode modes
if (sp.getBoolean(R.string.key_show_statuslights, false)) {
if (sp.getBoolean(R.string.key_show_statuslights_extended, false))
statusLightHandler.extendedStatusLight(overview_canulaage, overview_insulinage, overview_reservoirlevel, overview_sensorage, overview_batterylevel)
else
statusLightHandler.statusLight(overview_canulaage, overview_insulinage, overview_reservoirlevel, overview_sensorage, overview_batterylevel)
}
// cob
var cobText: String = resourceHelper.gs(R.string.value_unavailable_short)
val cobInfo = iobCobCalculatorPlugin.getCobInfo(false, "Overview COB")
if (cobInfo.displayCob != null) {
cobText = DecimalFormatter.to0Decimal(cobInfo.displayCob)
if (cobInfo.futureCarbs > 0) cobText += "(" + DecimalFormatter.to0Decimal(cobInfo.futureCarbs) + ")"
}
overview_cob?.text = cobText
val lastRun = loopPlugin.lastRun
val predictionsAvailable = if (Config.APS) lastRun?.request?.hasPredictions == true else Config.NSCLIENT
// pump status from ns
overview_pump?.text = nsDeviceStatus.pumpStatus
overview_pump?.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.pump), nsDeviceStatus.extendedPumpStatus) } }
// OpenAPS status from ns
overview_openaps?.text = nsDeviceStatus.openApsStatus
overview_openaps?.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.openaps), nsDeviceStatus.extendedOpenApsStatus) } }
// Uploader status from ns
overview_uploader?.text = nsDeviceStatus.uploaderStatusSpanned
overview_uploader?.setOnClickListener { activity?.let { OKDialog.show(it, resourceHelper.gs(R.string.uploader), nsDeviceStatus.extendedUploaderStatus) } }
// Sensitivity
iobCobCalculatorPlugin.getLastAutosensData("Overview")?.let { autosensData ->
overview_sensitivity?.text = String.format(Locale.ENGLISH, "%.0f%%", autosensData.autosensResult.ratio * 100)
}
// ****** GRAPH *******
Thread(Runnable {
// align to hours
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar[Calendar.MILLISECOND] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MINUTE] = 0
calendar.add(Calendar.HOUR, 1)
val hoursToFetch: Int
val toTime: Long
val fromTime: Long
val endTime: Long
val apsResult = if (Config.APS) lastRun?.constraintsProcessed else NSDeviceStatus.getAPSResult(injector)
if (predictionsAvailable && apsResult != null && sp.getBoolean("showprediction", false)) {
var predHours = (ceil(apsResult.latestPredictionsTime - System.currentTimeMillis().toDouble()) / (60 * 60 * 1000)).toInt()
predHours = min(2, predHours)
predHours = max(0, predHours)
hoursToFetch = rangeToDisplay - predHours
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime + T.hours(predHours.toLong()).msecs()
} else {
hoursToFetch = rangeToDisplay
toTime = calendar.timeInMillis + 100000 // little bit more to avoid wrong rounding - GraphView specific
fromTime = toTime - T.hours(hoursToFetch.toLong()).msecs()
endTime = toTime
}
val now = System.currentTimeMillis()
// ------------------ 1st graph
val graphData = GraphData(injector, overview_bggraph, iobCobCalculatorPlugin)
// **** In range Area ****
graphData.addInRangeArea(fromTime, endTime, lowLine, highLine)
// **** BG ****
if (predictionsAvailable && sp.getBoolean("showprediction", false)) graphData.addBgReadings(fromTime, toTime, lowLine, highLine,
apsResult?.predictions) else graphData.addBgReadings(fromTime, toTime, lowLine, highLine, null)
// set manual x bounds to have nice steps
graphData.formatAxis(fromTime, endTime)
// Treatments
graphData.addTreatments(fromTime, endTime)
if (sp.getBoolean("showactivityprimary", true))
graphData.addActivity(fromTime, endTime, false, 0.8)
// add basal data
if (pump.pumpDescription.isTempBasalCapable && sp.getBoolean("showbasals", true))
graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2)
// add target line
graphData.addTargetLine(fromTime, toTime, profile, loopPlugin.lastRun)
// **** NOW line ****
graphData.addNowLine(now)
// ------------------ 2nd graph
val secondGraphData = GraphData(injector, overview_iobgraph, iobCobCalculatorPlugin)
var useIobForScale = false
var useCobForScale = false
var useDevForScale = false
var useRatioForScale = false
var useDSForScale = false
var useIAForScale = false
// finally enforce drawing of graphs
when {
sp.getBoolean("showiob", true) ->
useIobForScale = true
sp.getBoolean("showcob", true) ->
useCobForScale = true
sp.getBoolean("showdeviations", false) ->
useDevForScale = true
sp.getBoolean("showratios", false) ->
useRatioForScale = true
sp.getBoolean("showactivitysecondary", false) ->
useIAForScale = true
sp.getBoolean("showdevslope", false) ->
useDSForScale = true
}
if (sp.getBoolean("showiob", true)) secondGraphData.addIob(fromTime, now, useIobForScale, 1.0, sp.getBoolean("showprediction", false))
if (sp.getBoolean("showcob", true)) secondGraphData.addCob(fromTime, now, useCobForScale, if (useCobForScale) 1.0 else 0.5)
if (sp.getBoolean("showdeviations", false)) secondGraphData.addDeviations(fromTime, now, useDevForScale, 1.0)
if (sp.getBoolean("showratios", false)) secondGraphData.addRatio(fromTime, now, useRatioForScale, 1.0)
if (sp.getBoolean("showactivitysecondary", true)) secondGraphData.addActivity(fromTime, endTime, useIAForScale, 0.8)
if (sp.getBoolean("showdevslope", false) && buildHelper.isDev()) secondGraphData.addDeviationSlope(fromTime, now, useDSForScale, 1.0)
// **** NOW line ****
// set manual x bounds to have nice steps
secondGraphData.formatAxis(fromTime, endTime)
secondGraphData.addNowLine(now)
// do GUI update
val activity = activity
activity?.runOnUiThread {
if (sp.getBoolean("showiob", true)
|| sp.getBoolean("showcob", true)
|| sp.getBoolean("showdeviations", false)
|| sp.getBoolean("showratios", false)
|| sp.getBoolean("showactivitysecondary", false)
|| sp.getBoolean("showdevslope", false)) {
overview_iobgraph?.visibility = View.VISIBLE
} else {
overview_iobgraph?.visibility = View.GONE
}
// finally enforce drawing of graphs
graphData.performUpdate()
secondGraphData.performUpdate()
}
}).start()
}
}

View file

@ -0,0 +1,436 @@
package info.nightscout.androidaps.plugins.general.overview
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.ContextMenu
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageButton
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.FragmentManager
import info.nightscout.androidaps.Config
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.ErrorHelperActivity
import info.nightscout.androidaps.data.Profile
import info.nightscout.androidaps.db.Source
import info.nightscout.androidaps.db.TempTarget
import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.dialogs.TempTargetDialog
import info.nightscout.androidaps.events.EventRefreshOverview
import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.PumpDescription
import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.ToastUtils
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class OverviewMenus @Inject constructor(
private val aapsLogger: AAPSLogger,
private val resourceHelper: ResourceHelper,
private val sp: SP,
private val rxBus: RxBusWrapper,
private val context: Context,
private val buildHelper: BuildHelper,
private val defaultValueHelper: DefaultValueHelper,
private val activePlugin: ActivePluginProvider,
private val profileFunction: ProfileFunction,
private val commandQueue: CommandQueueProvider,
private val configBuilderPlugin: ConfigBuilderPlugin,
private val loopPlugin: LoopPlugin
) {
enum class CharType {
PRE, BAS, IOB, COB, DEV, SEN, ACTPRIM, ACTSEC, DEVSLOPE
}
fun setupChartMenu(chartButton: ImageButton) {
chartButton.setOnClickListener { v: View ->
val predictionsAvailable: Boolean = when {
Config.APS -> loopPlugin.lastRun?.request?.hasPredictions ?: false
Config.NSCLIENT -> true
else -> false
}
//var item: MenuItem
val dividerItem: MenuItem
//var title: CharSequence
var titleMaxChars = 0
//var s: SpannableString
val popup = PopupMenu(v.context, v)
if (predictionsAvailable) {
val item = popup.menu.add(Menu.NONE, CharType.PRE.ordinal, Menu.NONE, "Predictions")
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.prediction)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showprediction", true)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.BAS.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_basals))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.basal)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showbasals", true)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.ACTPRIM.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_activity))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.activity)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showactivityprimary", true)
dividerItem = popup.menu.add("")
dividerItem.isEnabled = false
}
run {
val item = popup.menu.add(Menu.NONE, CharType.IOB.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_iob))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.iob)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showiob", true)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.COB.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_cob))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.cob)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showcob", true)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.DEV.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_deviations))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.deviations)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showdeviations", false)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.SEN.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_sensitivity))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.ratio)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showratios", false)
}
run {
val item = popup.menu.add(Menu.NONE, CharType.ACTSEC.ordinal, Menu.NONE, resourceHelper.gs(R.string.overview_show_activity))
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.activity)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showactivitysecondary", true)
}
if (buildHelper.isDev()) {
val item = popup.menu.add(Menu.NONE, CharType.DEVSLOPE.ordinal, Menu.NONE, "Deviation slope")
val title = item.title
if (titleMaxChars < title.length) titleMaxChars = title.length
val s = SpannableString(title)
s.setSpan(ForegroundColorSpan(resourceHelper.gc(R.color.devslopepos)), 0, s.length, 0)
item.title = s
item.isCheckable = true
item.isChecked = sp.getBoolean("showdevslope", false)
}
// Fairly good estimate for required divider text size...
dividerItem.title = String(CharArray(titleMaxChars + 10)).replace("\u0000", "_")
popup.setOnMenuItemClickListener {
when (it.itemId) {
CharType.PRE.ordinal -> sp.putBoolean("showprediction", !it.isChecked)
CharType.BAS.ordinal -> sp.putBoolean("showbasals", !it.isChecked)
CharType.IOB.ordinal -> sp.putBoolean("showiob", !it.isChecked)
CharType.COB.ordinal -> sp.putBoolean("showcob", !it.isChecked)
CharType.DEV.ordinal -> sp.putBoolean("showdeviations", !it.isChecked)
CharType.SEN.ordinal -> sp.putBoolean("showratios", !it.isChecked)
CharType.ACTPRIM.ordinal -> sp.putBoolean("showactivityprimary", !it.isChecked)
CharType.ACTSEC.ordinal -> sp.putBoolean("showactivitysecondary", !it.isChecked)
CharType.DEVSLOPE.ordinal -> sp.putBoolean("showdevslope", !it.isChecked)
}
rxBus.send(EventRefreshOverview("OnMenuItemClickListener"))
return@setOnMenuItemClickListener true
}
chartButton.setImageResource(R.drawable.ic_arrow_drop_up_white_24dp)
popup.setOnDismissListener { chartButton.setImageResource(R.drawable.ic_arrow_drop_down_white_24dp) }
popup.show()
}
}
fun createContextMenu(menu: ContextMenu, v: View) {
when (v.id) {
R.id.overview_apsmode -> {
val pumpDescription: PumpDescription = activePlugin.activePump.pumpDescription
if (!profileFunction.isProfileValid("ContextMenuCreation")) return
menu.setHeaderTitle(resourceHelper.gs(R.string.loop))
if (loopPlugin.isEnabled(PluginType.LOOP)) {
menu.add(resourceHelper.gs(R.string.disableloop))
if (!loopPlugin.isSuspended) {
menu.add(resourceHelper.gs(R.string.suspendloopfor1h))
menu.add(resourceHelper.gs(R.string.suspendloopfor2h))
menu.add(resourceHelper.gs(R.string.suspendloopfor3h))
menu.add(resourceHelper.gs(R.string.suspendloopfor10h))
} else {
if (!loopPlugin.isDisconnected) {
menu.add(resourceHelper.gs(R.string.resume))
}
}
}
if (!loopPlugin.isEnabled(PluginType.LOOP)) {
menu.add(resourceHelper.gs(R.string.enableloop))
}
if (!loopPlugin.isDisconnected) {
showSuspendPump(menu, pumpDescription)
} else {
menu.add(resourceHelper.gs(R.string.reconnect))
}
}
R.id.overview_activeprofile -> {
menu.setHeaderTitle(resourceHelper.gs(R.string.profile))
menu.add(resourceHelper.gs(R.string.danar_viewprofile))
if (activePlugin.activeProfileInterface.profile != null) {
menu.add(resourceHelper.gs(R.string.careportal_profileswitch))
}
}
R.id.overview_temptarget -> {
menu.setHeaderTitle(resourceHelper.gs(R.string.careportal_temporarytarget))
menu.add(resourceHelper.gs(R.string.custom))
menu.add(resourceHelper.gs(R.string.eatingsoon))
menu.add(resourceHelper.gs(R.string.activity))
menu.add(resourceHelper.gs(R.string.hypo))
if (activePlugin.activeTreatments.tempTargetFromHistory != null) {
menu.add(resourceHelper.gs(R.string.cancel))
}
}
}
}
private fun showSuspendPump(menu: ContextMenu, pumpDescription: PumpDescription) {
if (pumpDescription.tempDurationStep15mAllowed) menu.add(resourceHelper.gs(R.string.disconnectpumpfor15m))
if (pumpDescription.tempDurationStep30mAllowed) menu.add(resourceHelper.gs(R.string.disconnectpumpfor30m))
menu.add(resourceHelper.gs(R.string.disconnectpumpfor1h))
menu.add(resourceHelper.gs(R.string.disconnectpumpfor2h))
menu.add(resourceHelper.gs(R.string.disconnectpumpfor3h))
}
fun onContextItemSelected(item: MenuItem, manager: FragmentManager): Boolean {
val profile = profileFunction.getProfile() ?: return true
when (item.title) {
resourceHelper.gs(R.string.disableloop) -> {
aapsLogger.debug("USER ENTRY: LOOP DISABLED")
loopPlugin.setPluginEnabled(PluginType.LOOP, false)
loopPlugin.setFragmentVisible(PluginType.LOOP, false)
configBuilderPlugin.storeSettings("DisablingLoop")
rxBus.send(EventRefreshOverview("suspendmenu"))
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {
ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.tempbasaldeliveryerror))
}
}
})
loopPlugin.createOfflineEvent(24 * 60) // upload 24h, we don't know real duration
return true
}
resourceHelper.gs(R.string.enableloop) -> {
aapsLogger.debug("USER ENTRY: LOOP ENABLED")
loopPlugin.setPluginEnabled(PluginType.LOOP, true)
loopPlugin.setFragmentVisible(PluginType.LOOP, true)
configBuilderPlugin.storeSettings("EnablingLoop")
rxBus.send(EventRefreshOverview("suspendmenu"))
loopPlugin.createOfflineEvent(0)
return true
}
resourceHelper.gs(R.string.resume), resourceHelper.gs(R.string.reconnect) -> {
aapsLogger.debug("USER ENTRY: RESUME")
loopPlugin.suspendTo(0L)
rxBus.send(EventRefreshOverview("suspendmenu"))
commandQueue.cancelTempBasal(true, object : Callback() {
override fun run() {
if (!result.success) {
val i = Intent(context, ErrorHelperActivity::class.java)
i.putExtra("soundid", R.raw.boluserror)
i.putExtra("status", result.comment)
i.putExtra("title", resourceHelper.gs(R.string.tempbasaldeliveryerror))
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(i)
}
}
})
sp.putBoolean(R.string.key_objectiveusereconnect, true)
loopPlugin.createOfflineEvent(0)
return true
}
resourceHelper.gs(R.string.suspendloopfor1h) -> {
aapsLogger.debug("USER ENTRY: SUSPEND 1h")
loopPlugin.suspendLoop(60)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.suspendloopfor2h) -> {
aapsLogger.debug("USER ENTRY: SUSPEND 2h")
loopPlugin.suspendLoop(120)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.suspendloopfor3h) -> {
aapsLogger.debug("USER ENTRY: SUSPEND 3h")
loopPlugin.suspendLoop(180)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.suspendloopfor10h) -> {
aapsLogger.debug("USER ENTRY: SUSPEND 10h")
loopPlugin.suspendLoop(600)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.disconnectpumpfor15m) -> {
aapsLogger.debug("USER ENTRY: DISCONNECT 15m")
loopPlugin.disconnectPump(15, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.disconnectpumpfor30m) -> {
aapsLogger.debug("USER ENTRY: DISCONNECT 30m")
loopPlugin.disconnectPump(30, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.disconnectpumpfor1h) -> {
aapsLogger.debug("USER ENTRY: DISCONNECT 1h")
loopPlugin.disconnectPump(60, profile)
sp.putBoolean(R.string.key_objectiveusedisconnect, true)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.disconnectpumpfor2h) -> {
aapsLogger.debug("USER ENTRY: DISCONNECT 2h")
loopPlugin.disconnectPump(120, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.disconnectpumpfor3h) -> {
aapsLogger.debug("USER ENTRY: DISCONNECT 3h")
loopPlugin.disconnectPump(180, profile)
rxBus.send(EventRefreshOverview("suspendmenu"))
return true
}
resourceHelper.gs(R.string.careportal_profileswitch) -> {
ProfileSwitchDialog().show(manager, "Overview")
}
resourceHelper.gs(R.string.danar_viewprofile) -> {
val args = Bundle()
args.putLong("time", DateUtil.now())
args.putInt("mode", ProfileViewerDialog.Mode.RUNNING_PROFILE.ordinal)
val pvd = ProfileViewerDialog()
pvd.arguments = args
pvd.show(manager, "ProfileViewDialog")
}
resourceHelper.gs(R.string.eatingsoon) -> {
aapsLogger.debug("USER ENTRY: TEMP TARGET EATING SOON")
val target = Profile.toMgdl(defaultValueHelper.determineEatingSoonTT(), profileFunction.getUnits())
val tempTarget = TempTarget()
.date(System.currentTimeMillis())
.duration(defaultValueHelper.determineEatingSoonTTDuration())
.reason(resourceHelper.gs(R.string.eatingsoon))
.source(Source.USER)
.low(target)
.high(target)
activePlugin.activeTreatments.addToHistoryTempTarget(tempTarget)
}
resourceHelper.gs(R.string.activity) -> {
aapsLogger.debug("USER ENTRY: TEMP TARGET ACTIVITY")
val target = Profile.toMgdl(defaultValueHelper.determineActivityTT(), profileFunction.getUnits())
val tempTarget = TempTarget()
.date(DateUtil.now())
.duration(defaultValueHelper.determineActivityTTDuration())
.reason(resourceHelper.gs(R.string.activity))
.source(Source.USER)
.low(target)
.high(target)
activePlugin.activeTreatments.addToHistoryTempTarget(tempTarget)
}
resourceHelper.gs(R.string.hypo) -> {
aapsLogger.debug("USER ENTRY: TEMP TARGET HYPO")
val target = Profile.toMgdl(defaultValueHelper.determineHypoTT(), profileFunction.getUnits())
val tempTarget = TempTarget()
.date(DateUtil.now())
.duration(defaultValueHelper.determineHypoTTDuration())
.reason(resourceHelper.gs(R.string.hypo))
.source(Source.USER)
.low(target)
.high(target)
activePlugin.activeTreatments.addToHistoryTempTarget(tempTarget)
}
resourceHelper.gs(R.string.custom) -> {
TempTargetDialog().show(manager, "Overview")
}
resourceHelper.gs(R.string.cancel) -> {
aapsLogger.debug("USER ENTRY: TEMP TARGET CANCEL")
val tempTarget = TempTarget()
.source(Source.USER)
.date(DateUtil.now())
.duration(0)
.low(0.0)
.high(0.0)
activePlugin.activeTreatments.addToHistoryTempTarget(tempTarget)
}
}
return false
}
}

View file

@ -89,9 +89,9 @@ class StatusLightHandler @Inject constructor(
/** /**
* applies the extended statusLight subview on the overview fragment * applies the extended statusLight subview on the overview fragment
*/ */
fun extendedStatusLight(cageView: TextView, iAgeView: TextView, fun extendedStatusLight(cageView: TextView?, iAgeView: TextView?,
reservoirView: TextView, sageView: TextView, reservoirView: TextView?, sageView: TextView?,
batteryView: TextView) { batteryView: TextView?) {
val pump = activePlugin.activePump val pump = activePlugin.activePump
handleAge("cage", CareportalEvent.SITECHANGE, cageView, "CAN ", handleAge("cage", CareportalEvent.SITECHANGE, cageView, "CAN ",
48, 72) 48, 72)
@ -112,11 +112,11 @@ class StatusLightHandler @Inject constructor(
} }
} }
private fun handleAge(nsSettingPlugin: String, eventName: String, view: TextView, text: String, private fun handleAge(nsSettingPlugin: String, eventName: String, view: TextView?, text: String,
defaultWarnThreshold: Int, defaultUrgentThreshold: Int) { defaultWarnThreshold: Int, defaultUrgentThreshold: Int) {
val urgent = nsSettingsStatus.getExtendedWarnValue(nsSettingPlugin, "urgent", defaultUrgentThreshold.toDouble()) val urgent = nsSettingsStatus.getExtendedWarnValue(nsSettingPlugin, "urgent", defaultUrgentThreshold.toDouble())
val warn = nsSettingsStatus.getExtendedWarnValue(nsSettingPlugin, "warn", defaultWarnThreshold.toDouble()) val warn = nsSettingsStatus.getExtendedWarnValue(nsSettingPlugin, "warn", defaultWarnThreshold.toDouble())
handleAge(view, text, eventName, warn, urgent, true) handleAge(view, text, eventName, warn, urgent)
} }
private fun handleLevel(criticalSetting: Int, criticalDefaultValue: Double, private fun handleLevel(criticalSetting: Int, criticalDefaultValue: Double,
@ -132,14 +132,14 @@ class StatusLightHandler @Inject constructor(
} }
private fun handleAge(age: TextView?, eventType: String, warnThreshold: Double, urgentThreshold: Double) = private fun handleAge(age: TextView?, eventType: String, warnThreshold: Double, urgentThreshold: Double) =
handleAge(age, "", eventType, warnThreshold, urgentThreshold, OverviewFragment.shorttextmode) handleAge(age, "", eventType, warnThreshold, urgentThreshold)
fun handleAge(age: TextView?, prefix: String, eventType: String, warnThreshold: Double, urgentThreshold: Double, useShortText: Boolean) { fun handleAge(age: TextView?, prefix: String, eventType: String, warnThreshold: Double, urgentThreshold: Double) {
val notavailable = if (useShortText) "-" else resourceHelper.gs(R.string.notavailable) val notavailable = if (resourceHelper.shortTextMode()) "-" else resourceHelper.gs(R.string.notavailable)
val careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(eventType) val careportalEvent = MainApp.getDbHelper().getLastCareportalEvent(eventType)
if (careportalEvent != null) { if (careportalEvent != null) {
age?.setTextColor(determineTextColor(careportalEvent, warnThreshold, urgentThreshold)) age?.setTextColor(determineTextColor(careportalEvent, warnThreshold, urgentThreshold))
age?.text = prefix + careportalEvent.age(useShortText) age?.text = prefix + careportalEvent.age(resourceHelper.shortTextMode())
} else { } else {
age?.text = notavailable age?.text = notavailable
} }

View file

@ -61,7 +61,7 @@ class GraphData(injector: HasAndroidInjector, private val graph: GraphView, priv
var maxBgValue = Double.MIN_VALUE var maxBgValue = Double.MIN_VALUE
bgReadingsArray = iobCobCalculatorPlugin.bgReadings bgReadingsArray = iobCobCalculatorPlugin.bgReadings
if (bgReadingsArray?.isEmpty() != false) { if (bgReadingsArray?.isEmpty() != false) {
aapsLogger.debug(LTag.OVERVIEW, "No BG data.") aapsLogger.debug("No BG data.")
maxY = 10.0 maxY = 10.0
minY = 0.0 minY = 0.0
return return

View file

@ -719,7 +719,9 @@ class SmsCommunicatorPlugin @Inject constructor(
override fun run() { override fun run() {
val detailedBolusInfo = DetailedBolusInfo() val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.carbs = anInteger().toDouble() detailedBolusInfo.carbs = anInteger().toDouble()
detailedBolusInfo.source = Source.USER
detailedBolusInfo.date = secondLong() detailedBolusInfo.date = secondLong()
if (activePlugin.activePump.pumpDescription.storesCarbInfo) {
commandQueue.bolus(detailedBolusInfo, object : Callback() { commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() { override fun run() {
if (result.success) { if (result.success) {
@ -733,6 +735,12 @@ class SmsCommunicatorPlugin @Inject constructor(
} }
} }
}) })
} else {
activePlugin.activeTreatments.addToHistoryTreatment(detailedBolusInfo, true)
var replyText = String.format(resourceHelper.gs(R.string.smscommunicator_carbsset), anInteger)
replyText += "\n" + activePlugin.activePump.shortStatus(true)
sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, replyText))
}
} }
}) })
} }

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.smsCommunicator.otp package info.nightscout.androidaps.plugins.general.smsCommunicator.otp
import android.util.Base64 import android.util.Base64
import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator import com.eatthepath.otp.HmacOneTimePasswordGenerator
import com.google.common.io.BaseEncoding import com.google.common.io.BaseEncoding
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
@ -23,7 +23,7 @@ class OneTimePassword @Inject constructor(
private var key: SecretKey? = null private var key: SecretKey? = null
private var pin: String = "" private var pin: String = ""
private val totp = TimeBasedOneTimePasswordGenerator() private val totp = HmacOneTimePasswordGenerator()
init { init {
instance = this instance = this
@ -48,8 +48,8 @@ class OneTimePassword @Inject constructor(
* Name of master device (target of OTP) * Name of master device (target of OTP)
*/ */
fun name(): String { fun name(): String {
val defaultUserName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name) val defaultUserName = resourceHelper.gs(R.string.patient_name_default)
var userName = sp.getString(R.string.key_smscommunicator_otp_name, defaultUserName).replace(":", "").trim() var userName = sp.getString(R.string.key_patient_name, defaultUserName).replace(":", "").trim()
if (userName.isEmpty()) if (userName.isEmpty())
userName = defaultUserName userName = defaultUserName
return userName return userName
@ -119,6 +119,6 @@ class OneTimePassword @Inject constructor(
* Return URI used to provision Authenticator apps * Return URI used to provision Authenticator apps
*/ */
fun provisioningURI(): String? = fun provisioningURI(): String? =
key?.let { "otpauth://totp/AndroidAPS:" + URLEncoder.encode(name(), "utf-8") + "?secret=" + BaseEncoding.base32().encode(it.encoded).replace("=", "") + "&issuer=AndroidAPS" } key?.let { "otpauth://totp/AndroidAPS:" + URLEncoder.encode(name(), "utf-8").replace("+", "%20") + "?secret=" + BaseEncoding.base32().encode(it.encoded).replace("=", "") + "&issuer=AndroidAPS" }
} }

View file

@ -291,11 +291,12 @@ class ActionStringHandler @Inject constructor(
} else if ("changeRequest" == act[0]) { ////////////////////////////////////////////// CHANGE REQUEST } else if ("changeRequest" == act[0]) { ////////////////////////////////////////////// CHANGE REQUEST
rTitle = resourceHelper.gs(R.string.openloop_newsuggestion) rTitle = resourceHelper.gs(R.string.openloop_newsuggestion)
rAction = "changeRequest" rAction = "changeRequest"
val finalLastRun = loopPlugin.lastRun loopPlugin.lastRun?.let {
rMessage += finalLastRun.constraintsProcessed rMessage += it.constraintsProcessed
wearPlugin.requestChangeConfirmation(rTitle, rMessage, rAction) wearPlugin.requestChangeConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis() lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = rAction lastConfirmActionString = rAction
}
return return
} else if ("cancelChangeRequest" == act[0]) { ////////////////////////////////////////////// CANCEL CHANGE REQUEST NOTIFICATION } else if ("cancelChangeRequest" == act[0]) { ////////////////////////////////////////////// CANCEL CHANGE REQUEST NOTIFICATION
rAction = "cancelChangeRequest" rAction = "cancelChangeRequest"
@ -406,9 +407,10 @@ class ActionStringHandler @Inject constructor(
} }
val aps = activePlugin.activeAPS val aps = activePlugin.activeAPS
ret += "APS: " + (aps as PluginBase).name ret += "APS: " + (aps as PluginBase).name
if (loopPlugin.lastRun != null) { val lastRun = loopPlugin.lastRun
if (loopPlugin.lastRun.lastAPSRun != null) ret += "\nLast Run: " + DateUtil.timeString(loopPlugin.lastRun.lastAPSRun) if (lastRun != null) {
if (loopPlugin.lastRun.lastTBREnact != 0L) ret += "\nLast Enact: " + DateUtil.timeString(loopPlugin.lastRun.lastTBREnact) ret += "\nLast Run: " + DateUtil.timeString(lastRun.lastAPSRun)
if (lastRun.lastTBREnact != 0L) ret += "\nLast Enact: " + DateUtil.timeString(lastRun.lastTBREnact)
} }
} else { } else {
ret += "LOOP DISABLED\n" ret += "LOOP DISABLED\n"

View file

@ -26,6 +26,7 @@ public class GlucoseStatus {
private HasAndroidInjector injector; private HasAndroidInjector injector;
public double glucose = 0d; public double glucose = 0d;
public double noise = 0d;
public double delta = 0d; public double delta = 0d;
public double avgdelta = 0d; public double avgdelta = 0d;
public double short_avgdelta = 0d; public double short_avgdelta = 0d;
@ -35,6 +36,7 @@ public class GlucoseStatus {
public String log() { public String log() {
return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " + return "Glucose: " + DecimalFormatter.to0Decimal(glucose) + " mg/dl " +
"Noise: " + DecimalFormatter.to0Decimal(noise) + " " +
"Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" + "Delta: " + DecimalFormatter.to0Decimal(delta) + " mg/dl" +
"Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " + "Short avg. delta: " + " " + DecimalFormatter.to2Decimal(short_avgdelta) + " mg/dl " +
"Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl"; "Long avg. delta: " + DecimalFormatter.to2Decimal(long_avgdelta) + " mg/dl";
@ -47,6 +49,7 @@ public class GlucoseStatus {
public GlucoseStatus round() { public GlucoseStatus round() {
this.glucose = Round.roundTo(this.glucose, 0.1); 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.delta = Round.roundTo(this.delta, 0.01);
this.avgdelta = Round.roundTo(this.avgdelta, 0.01); this.avgdelta = Round.roundTo(this.avgdelta, 0.01);
this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01); this.short_avgdelta = Round.roundTo(this.short_avgdelta, 0.01);
@ -93,6 +96,7 @@ public class GlucoseStatus {
if (sizeRecords == 1) { if (sizeRecords == 1) {
GlucoseStatus status = new GlucoseStatus(injector); GlucoseStatus status = new GlucoseStatus(injector);
status.glucose = now.value; status.glucose = now.value;
status.noise = 0d;
status.short_avgdelta = 0d; status.short_avgdelta = 0d;
status.delta = 0d; status.delta = 0d;
status.long_avgdelta = 0d; status.long_avgdelta = 0d;
@ -150,6 +154,7 @@ public class GlucoseStatus {
GlucoseStatus status = new GlucoseStatus(injector); GlucoseStatus status = new GlucoseStatus(injector);
status.glucose = now.value; status.glucose = now.value;
status.date = now_date; status.date = now_date;
status.noise = 0d; //for now set to nothing as not all CGMs report noise
status.short_avgdelta = average(short_deltas); status.short_avgdelta = average(short_deltas);

View file

@ -281,6 +281,10 @@ public class TreatmentService extends OrmLiteBaseService<DatabaseHelper> {
// return true if new record is created // return true if new record is created
public UpdateReturn createOrUpdate(Treatment treatment) { public UpdateReturn createOrUpdate(Treatment treatment) {
if (treatment != null && treatment.source == Source.NONE) {
log.error("Coder error: source is not set for treatment: " + treatment, new Exception());
//FabricPrivacy.logException(new Exception("Coder error: source is not set for treatment: " + treatment));
}
try { try {
Treatment old; Treatment old;
treatment.date = DatabaseHelper.roundDateToSec(treatment.date); treatment.date = DatabaseHelper.roundDateToSec(treatment.date);

View file

@ -98,13 +98,12 @@ class KeepAliveReceiver : DaggerBroadcastReceiver() {
// if there is no BG available, we have to upload anyway to have correct // if there is no BG available, we have to upload anyway to have correct
// IOB displayed in NS // IOB displayed in NS
private fun checkAPS() { private fun checkAPS() {
val usedAPS = activePlugin.activeAPS
var shouldUploadStatus = false var shouldUploadStatus = false
if (Config.NSCLIENT) return if (Config.NSCLIENT) return
if (Config.PUMPCONTROL) shouldUploadStatus = true if (Config.PUMPCONTROL) shouldUploadStatus = true
if (!loopPlugin.isEnabled() || iobCobCalculatorPlugin.actualBg() == null) else if (!loopPlugin.isEnabled() || iobCobCalculatorPlugin.actualBg() == null)
shouldUploadStatus = true shouldUploadStatus = true
else if (DateUtil.isOlderThan(usedAPS.lastAPSRun, 5)) shouldUploadStatus = true else if (DateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true
if (DateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY) && shouldUploadStatus) { if (DateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY) && shouldUploadStatus) {
lastIobUpload = DateUtil.now() lastIobUpload = DateUtil.now()
NSUpload.uploadDeviceStatus(loopPlugin, iobCobCalculatorPlugin, profileFunction, activePlugin.activePump) NSUpload.uploadDeviceStatus(loopPlugin, iobCobCalculatorPlugin, profileFunction, activePlugin.activePump)

View file

@ -198,6 +198,14 @@ class SWDefinition @Inject constructor(
.add(SWBreak(injector)) .add(SWBreak(injector))
.validator(SWValidator { nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth }) .validator(SWValidator { nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth })
.visibility(SWValidator { !(nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth) }) .visibility(SWValidator { !(nsClientPlugin.nsClientService != null && NSClientService.isConnected && NSClientService.hasWriteAuth) })
private val screenPatientName = SWScreen(injector, R.string.patient_name)
.skippable(true)
.add(SWInfotext(injector)
.label(R.string.patient_name_summary))
.add(SWEditString(injector)
.validator(SWTextValidator { text: String -> text.length > 0 })
.preferenceId(R.string.key_patient_name)
.updateDelay(5))
private val screenAge = SWScreen(injector, R.string.patientage) private val screenAge = SWScreen(injector, R.string.patientage)
.skippable(false) .skippable(false)
.add(SWBreak(injector)) .add(SWBreak(injector))
@ -389,6 +397,7 @@ class SWDefinition @Inject constructor(
.add(screenUnits) .add(screenUnits)
.add(displaySettings) .add(displaySettings)
.add(screenNsClient) .add(screenNsClient)
.add(screenPatientName)
.add(screenAge) .add(screenAge)
.add(screenInsulin) .add(screenInsulin)
.add(screenBgSource) .add(screenBgSource)
@ -415,6 +424,7 @@ class SWDefinition @Inject constructor(
.add(screenUnits) .add(screenUnits)
.add(displaySettings) .add(displaySettings)
.add(screenNsClient) .add(screenNsClient)
.add(screenPatientName)
.add(screenAge) .add(screenAge)
.add(screenInsulin) .add(screenInsulin)
.add(screenBgSource) .add(screenBgSource)
@ -437,6 +447,7 @@ class SWDefinition @Inject constructor(
.add(displaySettings) .add(displaySettings)
.add(screenNsClient) .add(screenNsClient)
.add(screenBgSource) .add(screenBgSource)
.add(screenPatientName)
.add(screenAge) .add(screenAge)
.add(screenInsulin) .add(screenInsulin)
.add(screenSensitivity) .add(screenSensitivity)

View file

@ -4,6 +4,7 @@ import android.os.Bundle
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.Config
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.ActivePluginProvider
@ -132,6 +133,7 @@ class FabricPrivacy @Inject constructor(
val hashes: List<String> = signatureVerifierPlugin.shortHashes() val hashes: List<String> = signatureVerifierPlugin.shortHashes()
if (hashes.isNotEmpty()) mainApp.firebaseAnalytics.setUserProperty("Hash", hashes[0]) if (hashes.isNotEmpty()) mainApp.firebaseAnalytics.setUserProperty("Hash", hashes[0])
activePlugin.activePump.let { mainApp.firebaseAnalytics.setUserProperty("Pump", it::class.java.simpleName) } activePlugin.activePump.let { mainApp.firebaseAnalytics.setUserProperty("Pump", it::class.java.simpleName) }
if (!Config.NSCLIENT && !Config.PUMPCONTROL)
activePlugin.activeAPS.let { mainApp.firebaseAnalytics.setUserProperty("Aps", it::class.java.simpleName) } activePlugin.activeAPS.let { mainApp.firebaseAnalytics.setUserProperty("Aps", it::class.java.simpleName) }
activePlugin.activeBgSource.let { mainApp.firebaseAnalytics.setUserProperty("BgSource", it::class.java.simpleName) } activePlugin.activeBgSource.let { mainApp.firebaseAnalytics.setUserProperty("BgSource", it::class.java.simpleName) }
mainApp.firebaseAnalytics.setUserProperty("Profile", activePlugin.activeProfileInterface.javaClass.simpleName) mainApp.firebaseAnalytics.setUserProperty("Profile", activePlugin.activeProfileInterface.javaClass.simpleName)

View file

@ -11,8 +11,6 @@ import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper
object OKDialog { object OKDialog {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
@JvmStatic
@JvmOverloads
fun show(context: Context, title: String, message: String, runnable: Runnable? = null) { fun show(context: Context, title: String, message: String, runnable: Runnable? = null) {
var notEmptytitle = title var notEmptytitle = title
if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message) if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message)

View file

@ -20,4 +20,5 @@ interface ResourceHelper {
fun decodeResource(id : Int) : Bitmap fun decodeResource(id : Int) : Bitmap
fun getDisplayMetrics(): DisplayMetrics fun getDisplayMetrics(): DisplayMetrics
fun dpToPx(dp: Int): Int fun dpToPx(dp: Int): Int
fun shortTextMode(): Boolean
} }

View file

@ -68,4 +68,6 @@ class ResourceHelperImplementation @Inject constructor(private val context: Cont
val scale = context.resources.displayMetrics.density val scale = context.resources.displayMetrics.density
return (dp * scale + 0.5f).toInt() return (dp * scale + 0.5f).toInt()
} }
override fun shortTextMode() : Boolean = !gb(R.bool.isTablet) && Config.NSCLIENT
} }

View file

@ -35,6 +35,7 @@
<string name="objectives_useloop">Inhoud van loop plugin weergeven</string> <string name="objectives_useloop">Inhoud van loop plugin weergeven</string>
<string name="objectives_usescale">Gebruik de schaalfunctie: houd de BG grafiek lang ingedrukt</string> <string name="objectives_usescale">Gebruik de schaalfunctie: houd de BG grafiek lang ingedrukt</string>
<string name="objectives_button_enter">Enter</string> <string name="objectives_button_enter">Enter</string>
<string name="enter_code_obtained_from_developers_to_bypass_the_rest_of_objectives">Als je ten minste 3 maanden closed loop ervaring hebt met een ander doe-het-zelf systeem dan kun je wellicht een code aanvragen om doelen over te slaan. Zie https://androidaps.readthedocs.io/en/latest/CROWDIN/nl/Usage/Objectives.html#doelen-overslaan voor details.</string>
<string name="codeaccepted">Code geaccepteerd</string> <string name="codeaccepted">Code geaccepteerd</string>
<string name="codeinvalid">Code ongeldig</string> <string name="codeinvalid">Code ongeldig</string>
<string name="objectives_exam_objective">Bewijs je kennis</string> <string name="objectives_exam_objective">Bewijs je kennis</string>

View file

@ -276,7 +276,7 @@
<string name="metadata_label_created_at">Created at</string> <string name="metadata_label_created_at">Created at</string>
<string name="metadata_label_aaps_version">AAPS Version</string> <string name="metadata_label_aaps_version">AAPS Version</string>
<string name="metadata_label_aaps_flavour">Build Variant</string> <string name="metadata_label_aaps_flavour">Build Variant</string>
<string name="metadata_label_device_name">Exporting device name</string> <string name="metadata_label_device_name">Exporting device patient name</string>
<string name="metadata_label_device_model">Exporting device model</string> <string name="metadata_label_device_model">Exporting device model</string>
<string name="metadata_label_encryption">File encryption</string> <string name="metadata_label_encryption">File encryption</string>
@ -612,6 +612,10 @@
<string name="key_adult" translatable="false">adult</string> <string name="key_adult" translatable="false">adult</string>
<string name="key_resistantadult" translatable="false">resistantadult</string> <string name="key_resistantadult" translatable="false">resistantadult</string>
<string name="patientage_summary">Please select patient age to setup safety limits</string> <string name="patientage_summary">Please select patient age to setup safety limits</string>
<string name="patient_name">Patient name</string>
<string name="patient_name_summary">Please provide patient name or nickname to differentiate among multiple setups</string>
<string name="patient_name_default" comment="This is default patient display name, when user does not provide real one">User</string>
<string name="key_patient_name" translatable="false">patient_name</string>
<string name="key_i_understand" translatable="false">I_understand</string> <string name="key_i_understand" translatable="false">I_understand</string>
<string name="Glimp">Glimp</string> <string name="Glimp">Glimp</string>
<string name="needwhitelisting">%1$s needs battery optimalization whitelisting for proper performance</string> <string name="needwhitelisting">%1$s needs battery optimalization whitelisting for proper performance</string>
@ -863,8 +867,15 @@
<string name="bgsource_upload">BG upload settings</string> <string name="bgsource_upload">BG upload settings</string>
<string name="wear_detailed_delta_title">Show detailed delta</string> <string name="wear_detailed_delta_title">Show detailed delta</string>
<string name="wear_detailed_delta_summary">Show delta with one more decimal place</string> <string name="wear_detailed_delta_summary">Show delta with one more decimal place</string>
<string name="key_smbinterval" translatable="false">smbinterval</string>
<string name="smbinterval_summary">How frequently SMBs will be given in min</string>
<string name="smbmaxminutes">SMB max minutes</string> <string name="smbmaxminutes">SMB max minutes</string>
<string name="smbmaxminutes_summary">Max minutes of basal to limit SMB to</string> <string name="smbmaxminutes_summary">Max minutes of basal to limit SMB to</string>
<string name="uamsmbmaxminutes">UAM SMB max minutes</string>
<string name="uamsmbmaxminutes_summary">Max minutes of basal to limit SMB to for UAM</string>
<string name="key_carbsReqThreshold" translatable="false">carbsReqThreshold</string>
<string name="carbsReqThreshold">Carb suggestion threshold</string>
<string name="carbsReqThreshold_summary">When Carbs are suggested, how many carbs will prompt a notification</string>
<string name="unsupportedfirmware">Unsupported pump firmware</string> <string name="unsupportedfirmware">Unsupported pump firmware</string>
<string name="dexcomg5_xdripupload_title">Send BG data to xDrip+</string> <string name="dexcomg5_xdripupload_title">Send BG data to xDrip+</string>
<string name="key_dexcomg5_xdripupload" translatable="false">dexcomg5_xdripupload</string> <string name="key_dexcomg5_xdripupload" translatable="false">dexcomg5_xdripupload</string>
@ -1181,6 +1192,12 @@
<string name="high_temptarget_raises_sensitivity_summary"><![CDATA[Raise sensitivity for temptargets >= 100]]></string> <string name="high_temptarget_raises_sensitivity_summary"><![CDATA[Raise sensitivity for temptargets >= 100]]></string>
<string name="low_temptarget_lowers_sensitivity_title">Low temptarget lowers sensitivity</string> <string name="low_temptarget_lowers_sensitivity_title">Low temptarget lowers sensitivity</string>
<string name="low_temptarget_lowers_sensitivity_summary"><![CDATA[Lower sensitivity for temptargets < 100]]></string> <string name="low_temptarget_lowers_sensitivity_summary"><![CDATA[Lower sensitivity for temptargets < 100]]></string>
<string name="key_resistance_lowers_target" translatable="false">resistance_lowers_target</string>
<string name="resistance_lowers_target_title">Resistance lowers target</string>
<string name="resistance_lowers_target_summary">When resistance is detected, lower the target glucose</string>
<string name="key_sensitivity_raises_target" translatable="false">sensitivity_raises_target</string>
<string name="sensitivity_raises_target_title">Sensitivity raises target</string>
<string name="sensitivity_raises_target_summary">When sensitivity is detected, raise the target glucose</string>
<string name="combo_invalid_setup">Invalid pump setup, check the docs and verify that the Quick Info menu is named QUICK INFO using the 360 configuration software.</string> <string name="combo_invalid_setup">Invalid pump setup, check the docs and verify that the Quick Info menu is named QUICK INFO using the 360 configuration software.</string>
<string name="custom">Custom</string> <string name="custom">Custom</string>
<string name="largetimedifftitle">Large Time Difference</string> <string name="largetimedifftitle">Large Time Difference</string>
@ -1385,6 +1402,7 @@
<string name="tidepool_upload_bg">Upload BG tests</string> <string name="tidepool_upload_bg">Upload BG tests</string>
<string name="key_smbmaxminutes" translatable="false">smbmaxminutes</string> <string name="key_smbmaxminutes" translatable="false">smbmaxminutes</string>
<string name="key_uamsmbmaxminutes" translatable="false">uamsmbmaxminutes</string>
<string name="dst_plugin_name" translatable="false">Daylight Saving time</string> <string name="dst_plugin_name" translatable="false">Daylight Saving time</string>
<string name="dst_in_24h_warning">Daylight Saving time change in 24h or less</string> <string name="dst_in_24h_warning">Daylight Saving time change in 24h or less</string>
<string name="dst_loop_disabled_warning">Daylight Saving time change less than 3 hours ago - Closed loop disabled</string> <string name="dst_loop_disabled_warning">Daylight Saving time change less than 3 hours ago - Closed loop disabled</string>
@ -1760,19 +1778,15 @@
<!-- SMS Communicator & OTP Authenticator --> <!-- SMS Communicator & OTP Authenticator -->
<string name="key_smscommunicator_otp_enabled" translatable="false">smscommunicator_otp_enabled</string> <string name="key_smscommunicator_otp_enabled" translatable="false">smscommunicator_otp_enabled</string>
<string name="key_smscommunicator_otp_name" translatable="false">smscommunicator_otp_name</string>
<string name="key_smscommunicator_otp_password" translatable="false">smscommunicator_otp_password</string> <string name="key_smscommunicator_otp_password" translatable="false">smscommunicator_otp_password</string>
<string name="key_smscommunicator_otp_secret" translatable="false">smscommunicator_otp_secret</string> <string name="key_smscommunicator_otp_secret" translatable="false">smscommunicator_otp_secret</string>
<string name="smscommunicator_default_user_display_name" comment="This is default user display name, shown by Authenticators">User</string>
<string name="smscommunicator_code_from_authenticator_for" comment="This is continuation of sentence: To [ACTION] reply with code">from Authenticator app for: %1$s</string> <string name="smscommunicator_code_from_authenticator_for" comment="This is continuation of sentence: To [ACTION] reply with code">from Authenticator app for: %1$s</string>
<string name="smscommunicator_otp_enabled">Enable Authenticator</string> <string name="smscommunicator_otp_enabled">Enable Authenticator</string>
<string name="smscommunicator_otp_enabled_summary">Authenticate commands using One Time Passwords generated by Google Authenticator or similar 2FA apps.</string> <string name="smscommunicator_otp_enabled_summary">Authenticate commands using One Time Passwords generated by Google Authenticator or similar 2FA apps.</string>
<string name="smscommunicator_otp_pin">Additional PIN at token end</string> <string name="smscommunicator_otp_pin">Additional PIN at token end</string>
<string name="smscommunicator_otp_pin_summary">Additional digits that should be memorised and glued at end of each generated One Time Password</string> <string name="smscommunicator_otp_pin_summary">Additional digits that should be memorised and glued at end of each generated One Time Password</string>
<string name="smscommunicator_otp_name">User name for Authenticator</string>
<string name="smscommunicator_otp_name_summary">User name displayed along with generated OTP on Authenticator App</string>
<string name="smscomunicator_tab_otp_label">Authenticator setup</string> <string name="smscomunicator_tab_otp_label">Authenticator setup</string>

View file

@ -20,6 +20,14 @@
android:key="@string/key_language" android:key="@string/key_language"
android:title="@string/language" /> android:title="@string/language" />
<EditTextPreference
android:inputType="textPersonName"
android:key="@string/key_patient_name"
android:title="@string/patient_name"
android:summary="@string/patient_name_summary"
/>
<PreferenceCategory android:title="@string/protection"> <PreferenceCategory android:title="@string/protection">
<Preference <Preference

View file

@ -73,13 +73,41 @@
android:summary="@string/enablesmbaftercarbs_summary" android:summary="@string/enablesmbaftercarbs_summary"
android:title="@string/enablesmbaftercarbs" /> android:title="@string/enablesmbaftercarbs" />
<ListPreference <info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
android:defaultValue="3"
android:digits="0123456789"
android:inputType="number"
android:key="key_smbinterval"
android:maxLines="20"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/smbinterval_summary"
validate:maxNumber="10"
validate:minNumber="1"
validate:testType="numericRange" />
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
android:defaultValue="30" android:defaultValue="30"
android:entries="@array/smbMaxMinutes" android:entries="@array/smbMaxMinutes"
android:entryValues="@array/smbMaxMinutes" android:entryValues="@array/smbMaxMinutes"
android:key="@string/key_smbmaxminutes" android:key="@string/key_smbmaxminutes"
android:title="@string/smbmaxminutes_summary" /> android:title="@string/smbmaxminutes_summary" />
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
android:defaultValue="30"
android:dialogMessage="@string/uamsmbmaxminutes"
android:digits="0123456789"
android:inputType="number"
android:key="@string/key_uamsmbmaxminutes"
android:maxLines="20"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/uamsmbmaxminutes_summary"
validate:maxNumber="120"
validate:minNumber="15"
validate:testType="numericRange" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="@string/key_use_uam" android:key="@string/key_use_uam"
@ -87,6 +115,18 @@
android:title="@string/enableuam" /> android:title="@string/enableuam" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/key_sensitivity_raises_target"
android:summary="@string/sensitivity_raises_target_summary"
android:title="@string/sensitivity_raises_target_title"
/>
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_resistance_lowers_target"
android:summary="@string/resistance_lowers_target_summary"
android:title="@string/resistance_lowers_target_title"
/>
<!-- TODO AS-FIX --> <!-- TODO AS-FIX -->
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
@ -103,6 +143,20 @@
android:summary="@string/low_temptarget_lowers_sensitivity_summary" android:summary="@string/low_temptarget_lowers_sensitivity_summary"
android:title="@string/low_temptarget_lowers_sensitivity_title" /> android:title="@string/low_temptarget_lowers_sensitivity_title" />
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
android:key="@string/key_carbsReqThreshold"
android:title="@string/carbsReqThreshold"
android:defaultValue="1"
android:dialogMessage="@string/carbsReqThreshold_summary"
android:digits="0123456789"
android:inputType="number"
android:maxLines="20"
android:selectAllOnFocus="true"
android:singleLine="true"
validate:maxNumber="10"
validate:minNumber="1"
validate:testType="numericRange"/>
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen
android:key="absorption_smb_advanced" android:key="absorption_smb_advanced"
android:title="@string/advancedsettings_title"> android:title="@string/advancedsettings_title">

View file

@ -49,14 +49,6 @@
android:title="@string/smscommunicator_otp_pin" android:title="@string/smscommunicator_otp_pin"
validate:testType="pinStrength" /> validate:testType="pinStrength" />
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
android:dependency="@string/key_smscommunicator_remotecommandsallowed"
android:key="@string/key_smscommunicator_otp_name"
android:summary="@string/smscommunicator_otp_name_summary"
android:title="@string/smscommunicator_otp_name"
validate:testType="personName" />
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>