diff --git a/bin/clockset.sh b/bin/clockset.sh index 1da17db8c..327cf0ece 100755 --- a/bin/clockset.sh +++ b/bin/clockset.sh @@ -18,15 +18,33 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +CLOCK=${1-monitor/clock.json} +GLUCOSE=${2-monitor/glucose.json} + die() { echo "$@" ; exit 1; } +self=$(basename $0) +function usage ( ) { + +cat < fake-hwclock.data +( cat $CLOCK; echo ) | sed 's/"//g' | sed "s/$/`date +%z`/" | while read line; do date -u -d $line +"%F %R:%S"; done > fake-hwclock.data grep : fake-hwclock.data && sudo cp fake-hwclock.data /etc/fake-hwclock.data sudo fake-hwclock load -grep -q display_time glucose.json && grep display_time glucose.json | head -1 | awk '{print $2}' | sed "s/,//" | sed 's/"//g' | sed "s/$/`date +%z`/" | while read line; do date -u -d $line +"%F %R:%S"; done > fake-hwclock.data -grep -q dateString glucose.json && grep dateString glucose.json | head -1 | awk '{print $2}' | sed "s/,//" | sed 's/"//g' |while read line; do date -u -d $line +"%F %R:%S"; done > fake-hwclock.data +grep -q display_time $GLUCOSE && grep display_time $GLUCOSE | head -1 | awk '{print $2}' | sed "s/,//" | sed 's/"//g' | sed "s/$/`date +%z`/" | while read line; do date -u -d $line +"%F %R:%S"; done > fake-hwclock.data +grep -q dateString $GLUCOSE && grep dateString $GLUCOSE | head -1 | awk '{print $2}' | sed "s/,//" | sed 's/"//g' |while read line; do date -u -d $line +"%F %R:%S"; done > fake-hwclock.data grep : fake-hwclock.data && sudo cp fake-hwclock.data /etc/fake-hwclock.data sudo fake-hwclock load diff --git a/bin/mm-format-ns-glucose.sh b/bin/mm-format-ns-glucose.sh index c8d511b0f..b2c9134ae 100755 --- a/bin/mm-format-ns-glucose.sh +++ b/bin/mm-format-ns-glucose.sh @@ -8,6 +8,21 @@ NSONLY="" test "$1" = "--oref0" && NSONLY="this.glucose = this.sgv" && shift +self=$(basename $0) +function usage ( ) { + +cat < +$self - Format Medtronic glucose data into something acceptable to Nightscout. +EOT +} + +case "$1" in + --help|-h|help) + usage + exit 0 +esac + HISTORY=${1-glucosehistory.json} OUTPUT=${2-/dev/fd/1} #TZ=${3-$(date +%z)} diff --git a/bin/mm-format-ns-profile.sh b/bin/mm-format-ns-profile.sh index 8e0e2f53a..718224ce6 100755 --- a/bin/mm-format-ns-profile.sh +++ b/bin/mm-format-ns-profile.sh @@ -2,18 +2,32 @@ # Author: Ben West +self=$(basename $0) SETTINGS=${1-monitor/settings.json} CARBS=${2-monitor/carb-ratios.json} BASALRATES=${3-monitor/active-basal-profile.json} SENSITIVITIES=${4-monitor/insulin-sensitivities.json} -TARGETS=${4-monitor/bg-targets.json} -OUTPUT=${2-/dev/fd/1} +TARGETS=${5-monitor/bg-targets.json} +OUTPUT=${6-/dev/fd/1} # DIA # CARBRATIO #TZ=${3-$(date +%z)} function usage ( ) { cat < +$self - Format Medtronic pump-history data into something acceptable to Nightscout. +EOT +} + +case "$1" in + --help|-h|help) + usage + exit 0 +esac + + HISTORY=${1-pumphistory.json} OUTPUT=${2-/dev/fd/1} #TZ=${3-$(date +%z)} diff --git a/bin/mm-format-ns-treatments.sh b/bin/mm-format-ns-treatments.sh index 1c976759e..ebc884627 100755 --- a/bin/mm-format-ns-treatments.sh +++ b/bin/mm-format-ns-treatments.sh @@ -7,6 +7,20 @@ MODEL=${2-model.json} OUTPUT=${3-/dev/fd/1} #TZ=${3-$(date +%z)} self=$(basename $0) +function usage ( ) { + +cat < +$self - Format medtronic history data into Nightscout treatments data. +EOT +} + +case "$1" in + --help|-h|help) + usage + exit 0 +esac + # | json -e "this.type = 'mm://openaps/$self'" \ model=$(json -f $MODEL) diff --git a/bin/mm-format-oref0-glucose.sh b/bin/mm-format-oref0-glucose.sh index 099b93373..975a542a9 100755 --- a/bin/mm-format-oref0-glucose.sh +++ b/bin/mm-format-oref0-glucose.sh @@ -4,9 +4,23 @@ # Maintainer: @tghoward # Written for decocare v0.0.18. Will need updating the the decocare json format changes. +self=$(basename $0) HISTORY=${1-glucosehistory.json} OUTPUT=${2-/dev/fd/1} #TZ=${3-$(date +%z)} +function usage ( ) { + +cat < +$self - Format medtronic glucose data into oref0 format. +EOT +} + +case "$1" in + --help|-h|help) + usage + exit 0 +esac cat $HISTORY | \ json -e "this.medtronic = this._type;" | \ diff --git a/bin/mm-stick.sh b/bin/mm-stick.sh index b4053ea7e..afb6c9123 100755 --- a/bin/mm-stick.sh +++ b/bin/mm-stick.sh @@ -109,7 +109,7 @@ case $OPERATION in print_fail $* exit 1 ;; - *|help) + *|help|--help|-h) print_help ;; esac diff --git a/bin/nightscout.sh b/bin/nightscout.sh index 2b11237c0..666837c5f 100755 --- a/bin/nightscout.sh +++ b/bin/nightscout.sh @@ -6,27 +6,206 @@ NAME=${1-help} shift PROGRAM="ns-${NAME}" COMMAND=$(which $PROGRAM | head -n 1) +NIGHTSCOUT_DEBUG=${NIGHTSCOUT_DEBUG-0} function help_message ( ) { cat < +* latest-openaps-treatment +* cull-latest-openaps-treatments + +* get +* upload +* dedupe-treatments +* hash-api-secret +* status +* upload-entries +* autoconfigure-device-crud + EOF } +function setup_help ( ) { + +cat < + +sets up: +openaps use ns shell get entries.json 'count=10' +openaps use ns shell upload treatments.json recently/combined-treatments.json +EOF +} + +function ns_help ( ) { +cat < /dev/stderr && cat $FILE && exit 0 + exec ns-upload $NIGHTSCOUT_HOST $API_SECRET $TYPE $FILE + ;; + upload-non-empty-treatments) + test $(cat $1 | json -a | wc -l) -lt 1 && echo "Nothing to upload." > /dev/stderr && cat $1 && exit 0 + exec ns-upload $NIGHTSCOUT_HOST $API_SECRET treatments.json $1 + + ;; + upload) + exec ns-upload $NIGHTSCOUT_HOST $API_SECRET $* + ;; + *) + echo "Unknown request:" $OP + ns_help + exit 1; + ;; + esac + exit 0 + + ;; +hash-api-secret) + if [[ -z "$1" ]] ; then + echo "Missing plain Nightscout passphrase". + echo "Usage: $self hash-api-secret 'myverylongsecret'". + exit 1; + fi + API_SECRET=$(echo -n $1 | sha1sum | cut -d ' ' -f 1 | tr -d "\n") + echo $API_SECRET + ;; +autoconfigure-device-crud) + NIGHTSCOUT_HOST=$1 + PLAIN_NS_SECRET=$2 + API_SECRET=$($self hash-api-secret $2) + case $1 in + help|-h|--help) + setup_help + exit 0 + ;; + esac + # openaps device add ns-get host + test -z "$NIGHTSCOUT_HOST" && setup_help && exit 1; + test -z "$API_SECRET" && setup_help && exit 1; + openaps device add ns process --require "oper" nightscout ns "NIGHTSCOUT_HOST" "API_SECRET" + openaps device show ns --json | json \ + | json -e "this.extra.args = this.extra.args.replace(' NIGHTSCOUT_HOST ', ' $NIGHTSCOUT_HOST ')" \ + | json -e "this.extra.args = this.extra.args.replace(' API_SECRET', ' $API_SECRET')" \ + | openaps import + ;; cull-latest-openaps-treatments) INPUT=$1 MODEL=$2 LAST_TIME=$3 mm-format-ns-treatments $INPUT $MODEL | json -c "this.created_at > '$LAST_TIME'" ;; -help) +help|--help|-h) help_message + exit 0 ;; *) test -n "$COMMAND" && exec $COMMAND $* diff --git a/bin/ns-dedupe-treatments.sh b/bin/ns-dedupe-treatments.sh index 307513f5d..7785f39ea 100755 --- a/bin/ns-dedupe-treatments.sh +++ b/bin/ns-dedupe-treatments.sh @@ -11,7 +11,7 @@ EOF } function fetch ( ) { - curl -s $ENDPOINT.json + curl -s -g $ENDPOINT.json } function flatten ( ) { @@ -87,7 +87,7 @@ case "$1" in delete) main $2 delete_cmd ;; - *|help) + *|help|--help|-h) usage exit 1; ;; diff --git a/bin/ns-get.sh b/bin/ns-get.sh index d2d9366df..330662c2a 100755 --- a/bin/ns-get.sh +++ b/bin/ns-get.sh @@ -8,6 +8,11 @@ NIGHTSCOUT_HOST=${NIGHTSCOUT_HOST-${2-localhost:1337}} QUERY=${3} OUTPUT=${4-/dev/fd/1} +CURL_FLAGS="-g -s" +NIGHTSCOUT_FORMAT=${NIGHTSCOUT_FORMAT-json} +test "$NIGHTSCOUT_DEBUG" = "1" && CURL_FLAGS="${CURL_FLAGS} -iv" +test "$NIGHTSCOUT_DEBUG" = "1" && set -x + function usage ( ) { cat < [NIGHTSCOUT_HOST|localhost:1337] [QUERY] [stdout|-] @@ -40,6 +45,8 @@ EOF QUERY=${4} OUTPUT=${5-/dev/fd/1} REPORT_ENDPOINT=$NIGHTSCOUT_HOST/api/v1/${REPORT}'?'${QUERY} + test -z "$NIGHTSCOUT_HOST" && usage && exit 1; + curl ${CURL_FLAGS} $REPORT_ENDPOINT | $NIGHTSCOUT_FORMAT ;; type) @@ -49,12 +56,12 @@ EOF --noop) echo "curl -s $REPORT_ENDPOINT | json" ;; - help) + help|--help|-h) usage ;; *) test -z "$NIGHTSCOUT_HOST" && usage && exit 1; - curl -g -s $REPORT_ENDPOINT | json + curl ${CURL_FLAGS} $REPORT_ENDPOINT | $NIGHTSCOUT_FORMAT ;; esac diff --git a/bin/ns-status.js b/bin/ns-status.js index 638d9cdd0..fad7a8aa6 100755 --- a/bin/ns-status.js +++ b/bin/ns-status.js @@ -1,6 +1,12 @@ #!/usr/bin/env node +'use strict'; -var fs = require('fs'); +var os = require("os"); + +var requireUtils = require('../lib/require-utils') + , safeRequire = requireUtils.safeRequire + , requireWithTimestamp = requireUtils.requireWithTimestamp + ; /* Prepare Status info to for upload to Nightscout @@ -18,18 +24,22 @@ var fs = require('fs'); */ -function requireWithTimestamp (path) { - var resolved = require(path); - - if (resolved) { - resolved.timestamp = fs.statSync(path).mtime; - } - - return resolved; +function mmtuneStatus (status) { + if (mmtune_input) { + var mmtune = requireWithTimestamp(cwd + '/' + mmtune_input); + if (mmtune) { + if (mmtune.scanDetails && mmtune.scanDetails.length > 0) { + mmtune.scanDetails = mmtune.scanDetails.filter(function (d) { + return d[2] > -99; + }); + } + status.mmtune = mmtune; + } + } } if (!module.parent) { - + var clock_input = process.argv.slice(2, 3).pop(); var iob_input = process.argv.slice(3, 4).pop(); var suggested_input = process.argv.slice(4, 5).pop(); @@ -37,27 +47,42 @@ if (!module.parent) { var battery_input = process.argv.slice(6, 7).pop(); var reservoir_input = process.argv.slice(7, 8).pop(); var status_input = process.argv.slice(8, 9).pop(); + var mmtune_input = process.argv.slice(9, 10).pop(); if (!clock_input || !iob_input || !suggested_input || !enacted_input || !battery_input || !reservoir_input || !status_input) { - console.log('usage: ', process.argv.slice(0, 2), ' '); + console.log('usage: ', process.argv.slice(0, 2), ' [mmtune.json]'); process.exit(1); } - + var cwd = process.cwd(); - var status = { - openaps: { - iob: requireWithTimestamp(cwd + '/' + iob_input) - , suggested: requireWithTimestamp(cwd + '/' + suggested_input) - , enacted: requireWithTimestamp(cwd + '/' + enacted_input) - } - , pump: { - clock: require(cwd + '/' + clock_input) - , battery: require(cwd + '/' + battery_input) - , reservoir: require(cwd + '/' + reservoir_input) - , status: requireWithTimestamp(cwd + '/' + status_input) - } - }; + var hostname = 'unknown'; + try { + hostname = os.hostname(); + } catch (e) { + return console.error('Unable to get hostname to send with status', e); + } + + try { + var status = { + device: 'openaps://' + os.hostname() + , openaps: { + iob: requireWithTimestamp(cwd + '/' + iob_input) + , suggested: requireWithTimestamp(cwd + '/' + suggested_input) + , enacted: requireWithTimestamp(cwd + '/' + enacted_input) + } + , pump: { + clock: safeRequire(cwd + '/' + clock_input) + , battery: safeRequire(cwd + '/' + battery_input) + , reservoir: safeRequire(cwd + '/' + reservoir_input) + , status: requireWithTimestamp(cwd + '/' + status_input) + } + }; + + mmtuneStatus(status); + } catch (e) { + return console.error("Could not parse input data: ", e); + } console.log(JSON.stringify(status)); } diff --git a/bin/ns-upload-entries.sh b/bin/ns-upload-entries.sh index 0bb0f56c5..a60f8053f 100755 --- a/bin/ns-upload-entries.sh +++ b/bin/ns-upload-entries.sh @@ -3,15 +3,30 @@ # Author: Ben West # Maintainer: @cjo20, @scottleibrand +self=$(basename $0) ENTRIES=${1-entries.json} NIGHTSCOUT_HOST=${NIGHTSCOUT_HOST-localhost:1337} #TZ=${3-$(date +%z)} OUTPUT=${2} +function usage ( ) { +cat < +$self - Upload entries (glucose data) to NS. +EOF +} + +case "$1" in + -h|--help|help) + usage + exit 0 + ;; +esac + export ENTRIES API_SECRET NIGHTSCOUT_HOST # requires API_SECRET and NIGHTSCOUT_HOST to be set in calling environment (i.e. in crontab) ( -curl -s -X POST --data-binary @$ENTRIES \ +curl -m 30 -s -X POST --data-binary @$ENTRIES \ -H "API-SECRET: $API_SECRET" \ -H "content-type: application/json" \ $NIGHTSCOUT_HOST/api/v1/entries.json diff --git a/bin/ns-upload.sh b/bin/ns-upload.sh index ca45d303b..5df63d052 100755 --- a/bin/ns-upload.sh +++ b/bin/ns-upload.sh @@ -54,8 +54,14 @@ EOF fi # requires API_SECRET and NIGHTSCOUT_HOST to be set in calling environment # (i.e. in crontab) +if [[ "$ENTRIES" != "-" ]] ; then + if [[ ! -f $ENTRIES ]] ; then + echo "Input file $ENTRIES" does not exist. + exit 1; + fi +fi (test "$ENTRIES" != "-" && cat $ENTRIES || cat )| ( -curl -s -X POST --data-binary @- \ +curl -m 30 -s -X POST --data-binary @- \ -H "API-SECRET: $API_SECRET" \ -H "content-type: application/json" \ $REST_ENDPOINT diff --git a/bin/ns-uploader-setup.sh b/bin/ns-uploader-setup.sh index ebfd42afe..6a94bc8d8 100755 --- a/bin/ns-uploader-setup.sh +++ b/bin/ns-uploader-setup.sh @@ -20,6 +20,22 @@ die() { } self=$(basename $0) + +function usage ( ) { + +cat < +$self - setup uploading to NS? +EOF +} + +case "$1" in + --help|help|-h) + usage + exit 0 + ;; +esac + if [[ $# -lt 2 ]]; then openaps device show pump 2>/dev/null >/dev/null || die "Usage: $self " fi diff --git a/bin/oref0-calculate-iob.js b/bin/oref0-calculate-iob.js index b0636063a..06451a10a 100755 --- a/bin/oref0-calculate-iob.js +++ b/bin/oref0-calculate-iob.js @@ -19,14 +19,22 @@ */ var generate = require('oref0/lib/iob'); +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ' '); + +} if (!module.parent) { var pumphistory_input = process.argv.slice(2, 3).pop(); + if ([null, '--help', '-h', 'help'].indexOf(pumphistory_input) > 0) { + usage( ); + process.exit(0) + } var profile_input = process.argv.slice(3, 4).pop(); var clock_input = process.argv.slice(4, 5).pop(); if (!pumphistory_input || !profile_input) { - console.log('usage: ', process.argv.slice(0, 2), ' '); + usage( ); process.exit(1); } diff --git a/bin/oref0-copy-fresher b/bin/oref0-copy-fresher new file mode 100755 index 000000000..4d9f12081 --- /dev/null +++ b/bin/oref0-copy-fresher @@ -0,0 +1,37 @@ +#!/bin/bash +self=$(basename $0) +WITHIN=0 + +function usage ( ) { +cat <] [...] +cat files mentioned. +If --since seconds is specified, the file will only be cat'd if the files last +modification occurred within . +Default is 0, it will cat all files. +EOF +} +case $1 in + --help|-h|help) + usage + exit 0 + ;; + --since) + shift + WITHIN=$1 + shift + ;; +esac + + +for file in $* ; do + if [[ -f $file && ($WITHIN -eq 0 || $(expr $(date +%s) - $(date +%s -r $file)) -lt $WITHIN) ]] ; then + cat $file + fi +done + + + # 2783 test $(expr $(date +%s) - $(date +%s -r openaps.ini )) -gt 1000 && echo "OK" || echo "NOT" + # 2784 test $(expr $(date +%s) - $(date +%s -r openaps.ini )) -gt 1000000 && echo "OK" || echo "NOT" + # 2785 history | tail -n 3 > blah + diff --git a/bin/oref0-detect-sensitivity.js b/bin/oref0-detect-sensitivity.js new file mode 100755 index 000000000..bba62bf7f --- /dev/null +++ b/bin/oref0-detect-sensitivity.js @@ -0,0 +1,194 @@ +#!/usr/bin/env node + +/* + Determine Basal + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +var basal = require('oref0/lib/profile/basal'); +var get_iob = require('oref0/lib/iob'); + +if (!module.parent) { + var detectsensitivity = init(); + + var glucose_input = process.argv.slice(2, 3).pop(); + var pumphistory_input = process.argv.slice(3, 4).pop(); + var isf_input = process.argv.slice(4, 5).pop() + var basalprofile_input = process.argv.slice(5, 6).pop() + var profile_input = process.argv.slice(6, 7).pop(); + + if (!glucose_input || !pumphistory_input || !profile_input) { + console.error('usage: ', process.argv.slice(0, 2), ' '); + process.exit(1); + } + + var fs = require('fs'); + try { + var cwd = process.cwd(); + var glucose_data = require(cwd + '/' + glucose_input); + if (glucose_data.length < 72) { + console.log('Error: not enough glucose data to calculate autosens.'); + process.exit(2); + } + + var pumphistory_data = require(cwd + '/' + pumphistory_input); + var profile = require(cwd + '/' + profile_input); + //console.log(profile); + var glucose_status = detectsensitivity.getLastGlucose(glucose_data); + var isf_data = require(cwd + '/' + isf_input); + if (isf_data.units !== 'mg/dL') { + console.log('ISF is expected to be expressed in mg/dL.' + , 'Found', isf_data.units, 'in', isf_input, '.'); + process.exit(2); + } + var basalprofile = require(cwd + '/' + basalprofile_input); + + var iob_inputs = { + history: pumphistory_data + , profile: profile + //, clock: clock_data + }; + } catch (e) { + return console.error("Could not parse input data: ", e); + } + var avgDeltas = []; + var bgis = []; + var deviations = []; + var deviationSum = 0; + for (var i=0; i < glucose_data.length-3; ++i) { + //console.log(glucose_data[i]); + var bgTime; + if (glucose_data[i].display_time) { + bgTime = new Date(glucose_data[i].display_time.replace('T', ' ')); + } else if (glucose_data[i].dateString) { + bgTime = new Date(glucose_data[i].dateString); + } else { console.error("Could not determine last BG time"); } + //console.log(bgTime); + var bg = glucose_data[i].glucose; + if ( bg < 40 || glucose_data[i+3].glucose < 40) { + process.stderr.write("!"); + continue; + } + var avgDelta = (bg - glucose_data[i+3].glucose)/3; + avgDelta = avgDelta.toFixed(2); + iob_inputs.clock=bgTime; + iob_inputs.profile.current_basal = basal.basalLookup(basalprofile, bgTime); + //console.log(JSON.stringify(iob_inputs.profile)); + var iob = get_iob(iob_inputs); + //console.log(JSON.stringify(iob)); + + //var bgi = -iob.activity*profile.sens; + var bgi = Math.round(( -iob.activity * profile.sens * 5 )*100)/100; + bgi = bgi.toFixed(2); + deviation = avgDelta-bgi; + deviation = deviation.toFixed(2); + //if (deviation < 0 && deviation > -2) { + //console.log("BG: "+bg+", avgDelta: "+avgDelta+", BGI: "+bgi+", deviation: "+deviation); + //} + process.stderr.write("."); + + avgDeltas.push(avgDelta); + bgis.push(bgi); + deviations.push(deviation); + deviationSum += parseFloat(deviation); + + } + console.error(""); + //console.log(JSON.stringify(avgDeltas)); + //console.log(JSON.stringify(bgis)); + avgDeltas.sort(function(a, b){return a-b}); + bgis.sort(function(a, b){return a-b}); + deviations.sort(function(a, b){return a-b}); + for (var i=0.60; i > 0.25; i = i - 0.02) { + console.error("p="+i.toFixed(2)+": "+percentile(avgDeltas, i).toFixed(2)+", "+percentile(bgis, i).toFixed(2)+", "+percentile(deviations, i).toFixed(2)); + } + pSensitive = percentile(deviations, 0.50); + pResistant = percentile(deviations, 0.30); + //p30 = percentile(deviations, 0.3); + + average = deviationSum / deviations.length; + + console.error("Mean deviation: "+average.toFixed(2)); + var basalOff = 0; + + if(pSensitive < 0) { // sensitive + basalOff = pSensitive * (60/5) / profile.sens; + console.error("Excess insulin sensitivity detected"); + } else if (pResistant > 0) { // resistant + basalOff = pResistant * (60/5) / profile.sens; + console.error("Excess insulin resistance detected"); + } else { + console.error("Sensitivity within normal ranges"); + } + ratio = 1 + (basalOff / profile.max_daily_basal); + // don't adjust more than 2x + ratio = Math.max(ratio, 0.5); + ratio = Math.min(ratio, 2); + ratio = Math.round(ratio*100)/100; + newisf = profile.sens / ratio; + console.error("Basal adjustment "+basalOff.toFixed(2)+"U/hr"); + console.error("Ratio: "+ratio*100+"%: new ISF: "+newisf.toFixed(1)+"mg/dL/U"); + var sensAdj = { + "ratio": ratio + } + return console.log(JSON.stringify(sensAdj)); +} + +function init() { + + var detectsensitivity = { + name: 'detect-sensitivity' + , label: "OpenAPS Detect Sensitivity" + }; + + detectsensitivity.getLastGlucose = require('../lib/glucose-get-last'); + //detectsensitivity.detect_sensitivity = require('../lib/determine-basal/determine-basal'); + return detectsensitivity; + +} +module.exports = init; + +// From https://gist.github.com/IceCreamYou/6ffa1b18c4c8f6aeaad2 +// Returns the value at a given percentile in a sorted numeric array. +// "Linear interpolation between closest ranks" method +function percentile(arr, p) { + if (arr.length === 0) return 0; + if (typeof p !== 'number') throw new TypeError('p must be a number'); + if (p <= 0) return arr[0]; + if (p >= 1) return arr[arr.length - 1]; + + var index = arr.length * p, + lower = Math.floor(index), + upper = lower + 1, + weight = index % 1; + + if (upper >= arr.length) return arr[lower]; + return arr[lower] * (1 - weight) + arr[upper] * weight; +} + +// Returns the percentile of the given value in a sorted numeric array. +function percentRank(arr, v) { + if (typeof v !== 'number') throw new TypeError('v must be a number'); + for (var i = 0, l = arr.length; i < l; i++) { + if (v <= arr[i]) { + while (i < l && v === arr[i]) i++; + if (i === 0) return 0; + if (v !== arr[i-1]) { + i += (v - arr[i-1]) / (arr[i] - arr[i-1]); + } + return i / l; + } + } + return 1; +} + diff --git a/bin/oref0-determine-basal.js b/bin/oref0-determine-basal.js index 02eabe258..dcc7ee03f 100755 --- a/bin/oref0-determine-basal.js +++ b/bin/oref0-determine-basal.js @@ -14,28 +14,106 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + /* istanbul ignore next */ if (!module.parent) { var determinebasal = init(); - var iob_input = process.argv.slice(2, 3).pop(); - var currenttemp_input = process.argv.slice(3, 4).pop(); - var glucose_input = process.argv.slice(4, 5).pop(); - var profile_input = process.argv.slice(5, 6).pop(); - var offline = process.argv.slice(6, 7).pop(); + var argv = require('yargs') + .usage("$0 iob.json currenttemp.json glucose.json profile.json [[--auto-sens] autosens.json] [meal.json]") + .option('auto-sens', { + alias: 'a', + describe: "Auto-sensitivity configuration", + default: true + + }) + // error and show help if some other args given + .strict(true) + .help('help') + ; + function usage ( ) { + argv.showHelp( ); + } + + var params = argv.argv; + var errors = [ ]; + + var iob_input = params._.slice(0, 1).pop(); + if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) { + + usage( ); + process.exit(0) + } + var currenttemp_input = params._.slice(1, 2).pop(); + var glucose_input = params._.slice(2, 3).pop(); + var profile_input = params._.slice(3, 4).pop(); + var meal_input = params._.slice(4, 5).pop(); + var autosens_input = params.autoSens; + if (params._.length > 5) { + autosens_input = params.autoSens ? params._.slice(4, 5).pop() : false; + meal_input = params._.slice(5, 6).pop(); + } if (!iob_input || !currenttemp_input || !glucose_input || !profile_input) { - console.error('usage: ', process.argv.slice(0, 2), ' [Offline]'); + usage( ); process.exit(1); } - var cwd = process.cwd(); - var glucose_data = require(cwd + '/' + glucose_input); - var currenttemp = require(cwd + '/' + currenttemp_input); - var iob_data = require(cwd + '/' + iob_input); - var profile = require(cwd + '/' + profile_input); - var glucose_status = determinebasal.getLastGlucose(glucose_data); + var fs = require('fs'); + try { + var cwd = process.cwd(); + var glucose_data = require(cwd + '/' + glucose_input); + var currenttemp = require(cwd + '/' + currenttemp_input); + var iob_data = require(cwd + '/' + iob_input); + var profile = require(cwd + '/' + profile_input); + var glucose_status = determinebasal.getLastGlucose(glucose_data); + } catch (e) { + return console.error("Could not parse input data: ", e); + } + + //console.log(carbratio_data); + var meal_data = { }; + //console.error("meal_input",meal_input); + if (typeof meal_input != 'undefined') { + try { + meal_data = JSON.parse(fs.readFileSync(meal_input, 'utf8')); + console.error(JSON.stringify(meal_data)); + } catch (e) { + var msg = { + msg: "Optional feature Meal Assist enabled, but could not read required meal data." + , file: meal_input + , error: e + }; + console.error(msg.msg); + // console.log(JSON.stringify(msg)); + errors.push(msg); + // process.exit(1); + } + } + //if (meal_input) { meal_data = require(cwd + '/' + meal_input); } + //console.error(autosens_input); + var autosens_data = null; + if (autosens_input) { + // { "ratio":1 }; + autosens_data = { "ratio": 1 }; + if (autosens_input !== true && autosens_input.length) { + try { + autosens_data = JSON.parse(fs.readFileSync(autosens_input, 'utf8')); + console.error(JSON.stringify(autosens_data)); + } catch (e) { + var msg = { + msg: "Optional feature Auto Sensitivity enabled. Could not find specified auto-sens: " + autosens_input + , error: e + }; + console.error(msg.msg); + console.error(e); + // console.log(JSON.stringify(msg)); + errors.push(msg); + // process.exit(1); + } + } + } //if old reading from Dexcom do nothing var systemTime = new Date(); @@ -48,18 +126,24 @@ if (!module.parent) { var minAgo = (systemTime - bgTime) / 60 / 1000; if (minAgo > 10 || minAgo < -5) { // Dexcom data is too old, or way in the future - var reason = "BG data is too old, or clock set incorrectly "+bgTime; + var reason = "BG data is too old, or clock set incorrectly "+bgTime+" vs "+systemTime; console.error(reason); - return 1; + var msg = {msg: reason } + errors.push(msg); + /// return 1; + } + if (errors.length) { + console.log(JSON.stringify(errors)); + process.exit(1); } console.error(JSON.stringify(glucose_status)); console.error(JSON.stringify(currenttemp)); console.error(JSON.stringify(iob_data)); console.error(JSON.stringify(profile)); - var setTempBasal = require('../lib/basal-set-temp'); + var setTempBasal = require('oref0/lib/basal-set-temp'); - rT = determinebasal.determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, setTempBasal); + rT = determinebasal.determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, setTempBasal); if(typeof rT.error === 'undefined') { console.log(JSON.stringify(rT)); @@ -76,8 +160,8 @@ function init() { , label: "OpenAPS Determine Basal" }; - determinebasal.getLastGlucose = require('../lib/glucose-get-last'); - determinebasal.determine_basal = require('../lib/determine-basal/determine-basal'); + determinebasal.getLastGlucose = require('oref0/lib/glucose-get-last'); + determinebasal.determine_basal = require('oref0/lib/determine-basal/determine-basal'); return determinebasal; } diff --git a/bin/oref0-find-insulin-uses.js b/bin/oref0-find-insulin-uses.js index 813354c14..c8d4a33ad 100755 --- a/bin/oref0-find-insulin-uses.js +++ b/bin/oref0-find-insulin-uses.js @@ -19,14 +19,21 @@ */ var find_insulin = require('oref0/lib/iob/history'); +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ' '); +} if (!module.parent) { var iob_input = process.argv.slice(2, 3).pop() + if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) { + usage( ); + process.exit(0) + } var profile_input = process.argv.slice(3, 4).pop() var clock_input = process.argv.slice(4, 5).pop() if (!iob_input || !profile_input) { - console.log('usage: ', process.argv.slice(0, 2), ' '); + usage( ); process.exit(1); } diff --git a/bin/oref0-fix-git-corruption.sh b/bin/oref0-fix-git-corruption.sh new file mode 100755 index 000000000..715bf730c --- /dev/null +++ b/bin/oref0-fix-git-corruption.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# +# Author: Ben West +# +# +<&1 > /dev/null && return 1 || return 0 +} + +function ls_corrupt ( ) { + git status 2>&1 +} + +function find_last_branch_log ( ) { + # find last locally updated reflog, manually + find .git/logs/refs/heads/ -type f -printf "%T+ %p\n" | sort -r | head -n 1 | cut -f 2 -d' ' +} + +function find_last_valid_commit ( ) { + # last used branch + BRANCH=$(find_last_branch_log) + # Find second from last commit + if [ -z $BRANCH ]; then + exit 1 + fi + tail -n 2 $BRANCH | head -n 1 | cut -d' ' -f 2 +} + +if ! is_corrupt ; then + echo "Git repo does not appear to be corrupt." + exit 0 +fi + +# find previous commit before the corruption +VALID_COMMIT=$(find_last_valid_commit) + +if [ -z $VALID_COMMIT ]; then + echo "Error: Could not find last valid commit; aborting." + exit 1 +fi + +echo "Fixing, attempting to restore to $VALID_COMMIT" +while is_corrupt ; do + + # Each time the loop should run, remove any broken objects and attempt to + # restore the repo. + echo "Again" + # bunch of debugging + ls_corrupt + ls_corrupt > /tmp/corrupted + cat /tmp/corrupted | grep "error:.*file" + # Extract broken file objects from git error report. + cat /tmp/corrupted | grep "error:.*file" | sed -e "s/error:.*file \.git/\.git/g" | while read broken line ; do + echo $broken is corrupt + # Remove broken file. + rm -f $broken + # Attempt using git's repair tool. + git fsck --full + done + + # Attempt resetting HEAD to last known good commit. + git reset --hard $VALID_COMMIT + # If that works, quit the loop. + git status && break +done + +git status && echo "git repo repaired" + + diff --git a/bin/oref0-get-profile.js b/bin/oref0-get-profile.js index 8fedabcaf..e430ffc4c 100755 --- a/bin/oref0-get-profile.js +++ b/bin/oref0-get-profile.js @@ -17,17 +17,25 @@ */ var generate = require('oref0/lib/profile/'); +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ' [] []'); +} if (!module.parent) { var pumpsettings_input = process.argv.slice(2, 3).pop() + if ([null, '--help', '-h', 'help'].indexOf(pumpsettings_input) > 0) { + usage( ); + process.exit(0) + } var bgtargets_input = process.argv.slice(3, 4).pop() var isf_input = process.argv.slice(4, 5).pop() var basalprofile_input = process.argv.slice(5, 6).pop() - var maxiob_input = process.argv.slice(6, 7).pop() + var preferences_input = process.argv.slice(6, 7).pop() + var carbratio_input = process.argv.slice(7, 8).pop() if (!pumpsettings_input || !bgtargets_input || !isf_input || !basalprofile_input) { - console.log('usage: ', process.argv.slice(0, 2), ' []'); + usage( ); process.exit(1); } @@ -40,19 +48,57 @@ if (!module.parent) { process.exit(2); } var isf_data = require(cwd + '/' + isf_input); + if (isf_data.units !== 'mg/dL') { + console.log('ISF is expected to be expressed in mg/dL.' + , 'Found', isf_data.units, 'in', isf_input, '.'); + process.exit(2); + } var basalprofile_data = require(cwd + '/' + basalprofile_input); - var maxiob_data = { max_iob: 0 }; - if (typeof maxiob_input != 'undefined') { - maxiob_data = require(cwd + '/' + maxiob_input); + var preferences = {}; + if (typeof preferences_input != 'undefined') { + preferences = require(cwd + '/' + preferences_input); } + var fs = require('fs'); + var carbratio_data = { }; + //console.log("carbratio_input",carbratio_input); + if (typeof carbratio_input != 'undefined') { + try { + carbratio_data = JSON.parse(fs.readFileSync(carbratio_input, 'utf8')); + + } catch (e) { + var msg = { error: e, msg: "Could not parse carbratio_data. Feature Meal Assist enabled but cannot find required carb_ratios.", file: carbratio_input }; + console.error(msg.msg); + console.log(JSON.stringify(msg)); + process.exit(1); + } + var errors = [ ]; + + if (!(carbratio_data.schedule && carbratio_data.schedule[0].start && carbratio_data.schedule[0].ratio)) { + errors.push({msg: "Carb ratio data should have an array called schedule with a start and ratio fields.", file: carbratio_input, data: carbratio_data}); + } else { + } + if (carbratio_data.units != 'grams') { + errors.push({msg: "Carb ratio should have units field set to 'grams'.", file: carbratio_input, data: carbratio_data}); + } + if (errors.length) { + + errors.forEach(function (msg) { + console.error(msg.msg); + }); + console.log(JSON.stringify(errors)); + process.exit(1); + } + } + //console.log(carbratio_data); var inputs = { settings: pumpsettings_data , targets: bgtargets_data , basals: basalprofile_data , isf: isf_data - , max_iob: maxiob_data.max_iob || 0 - + , max_iob: preferences.max_iob || 0 + , skip_neutral_temps: preferences.skip_neutral_temps || false + , carbratio: carbratio_data }; var profile = generate(inputs); diff --git a/bin/oref0-html.js b/bin/oref0-html.js new file mode 100755 index 000000000..d7ef5ae53 --- /dev/null +++ b/bin/oref0-html.js @@ -0,0 +1,181 @@ +#!/usr/bin/env node + +/* + Update Pebble Watch information + + Copyright (c) 2015 OpenAPS Contributors + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +function getTime(minutes) { + var baseTime = new Date(); + baseTime.setHours('00'); + baseTime.setMinutes('00'); + baseTime.setSeconds('00'); + + return baseTime.getTime() + minutes * 60 * 1000; + +} + +/* Return basal rate(U / hr) at the provided timeOfDay */ + +function basalLookup() { + var now = new Date(); + basalRate = Math.round(basalprofile_data[basalprofile_data.length-1].rate*100)/100 + + for (var i = 0; i < basalprofile_data.length - 1; i++) { + if ((now >= getTime(basalprofile_data[i].minutes)) && (now < getTime(basalprofile_data[i + 1].minutes))) { + basalRate = basalprofile_data[i].rate.toFixed(2); + break; + } + } +} + + + +function fileHM(file) { + var filedate = new Date(fs.statSync(file).mtime); + var HMS = filedate.toLocaleTimeString().split(":") + return HMS[0].concat(":", HMS[1]); +} + +if (!module.parent) { + + var fs = require('fs'); + + var glucose_input = process.argv.slice(2, 3).pop() + var iob_input = process.argv.slice(3, 4).pop() + var basalprofile_input = process.argv.slice(4, 5).pop() + var currenttemp_input = process.argv.slice(5, 6).pop() + var requestedtemp_input = process.argv.slice(6, 7).pop() + var enactedtemp_input = process.argv.slice(7, 8).pop() + var meal_input = process.argv.slice(8, 9).pop() + + if (!glucose_input || !iob_input || !basalprofile_input || !currenttemp_input || !requestedtemp_input ) { + console.log('usage: ', process.argv.slice(0, 2), ' [meal.json]'); + process.exit(1); + } + + var cwd = process.cwd() + var file = cwd + '/' + glucose_input; + var glucose_data = require(file); + var bgTime = fileHM(file); + if (glucose_data[0].dateString) { + var bgDate = new Date(glucose_data[0].dateString); + var HMS = bgDate.toLocaleTimeString().split(":") + bgTime = HMS[0].concat(":", HMS[1]); + } + + var bgnow = glucose_data[0].glucose; + var delta = glucose_data[0].glucose - glucose_data[1].glucose; + var tick = delta; + if (delta >= 0) { tick = "+" + delta; } + var iob_data = require(cwd + '/' + iob_input); + iob = iob_data.iob.toFixed(1); + var basalprofile_data = require(cwd + '/' + basalprofile_input); + var basalRate; + basalLookup(); + file = cwd + '/' + currenttemp_input; + var temp = require(file); + var temp_time = fileHM(file); + var tempstring; + if (temp.duration < 1) { + tempstring = "No temp basal"; + } else { + tempstring = "Tmp: " + temp.duration + "m @ " + temp.rate.toFixed(1); + } + try { + var requestedtemp = require(cwd + '/' + requestedtemp_input); + } catch (e) { + return console.error("Could not parse requestedtemp: ", e); + } + var reqtempstring; + if (typeof requestedtemp.duration === 'undefined') { + reqtempstring = "None"; + } + else if (requestedtemp.duration < 1) { + reqtempstring = "Cancel"; + } else { + reqtempstring = requestedtemp.duration + "m@" + requestedtemp.rate.toFixed(1) + "U"; + } + //var enactedtemp = require(cwd + '/' + enactedtemp_input); + //if (enactedtemp.duration < 1) { + //enactedstring = "Cancel"; + //} else { + //enactedstring = enactedtemp.duration + "m@" + enactedtemp.rate.toFixed(1) + "U"; + //} + tz = new Date().toString().match(/([-\+][0-9]+)\s/)[1] + //enactedDate = new Date(enactedtemp.timestamp.concat(tz)); + //enactedHMS = enactedDate.toLocaleTimeString().split(":") + //enactedat = enactedHMS[0].concat(":", enactedHMS[1]); + + var mealCOB = "???"; + if (typeof meal_input != 'undefined') { + try { + meal_data = JSON.parse(fs.readFileSync(meal_input, 'utf8')); + //console.error(JSON.stringify(meal_data)); + if (typeof meal_data.mealCOB != 'undefined') { + mealCOB = meal_data.mealCOB; + } + } catch (e) { + //console.error("Optional feature Meal Assist not configured."); + } + } + +//console.log(""); + +console.log(""); + console.log(""); + console.log( bgnow + " " + tick + " at " + bgTime ); + console.log(""); + console.log(''); + console.log(''); +console.log(""); + +console.log(""); + + console.log("

"); + console.log( bgnow + " " + tick + " at " + bgTime ); + console.log("
"); + console.log( "IOB: " + iob + "U, eventually " + requestedtemp.eventualBG + "-" + requestedtemp.snoozeBG + " mg/dL" ); + console.log("
"); + //+ "Act: " + enactedstring + //+ " at " + enactedat + "\n" + console.log( tempstring ); + console.log( "U/hr at " + temp_time ); + console.log("

"); + console.log("
"); + console.log( "Req: " + reqtempstring ); + console.log("
"); + console.log( requestedtemp.reason ); + console.log("
"); + console.log( "Sched: " + basalRate + "U/hr" ); + console.log("
"); + console.log( "mealCOB: " + mealCOB + "g" ); + + +console.log(""); + + +} + diff --git a/bin/oref0-ifttt-notify b/bin/oref0-ifttt-notify index 17ea29b5f..a190932e3 100755 --- a/bin/oref0-ifttt-notify +++ b/bin/oref0-ifttt-notify @@ -25,7 +25,8 @@ if you wish. On the Maker channel there is a "how to trigger" link. Copy and paste the url for the example curl command, be sure to change the event name field. The URL, something like: -# https://maker.ifttt.com/trigger/{event}/with/key/MyKey + + https://maker.ifttt.com/trigger/{event}/with/key/MyKey You can pass the IFTTT_TRIGGER, which is the trigger URL as the first argument, or define it as an environment variable in your crontab. @@ -68,8 +69,9 @@ env) env exit ;; -help) +help|--help|-h) help_message + exit 0 ;; *) if [[ -z "$IFTTT_TRIGGER" || -z "$IFTTT_NOTIFY_USAGE" ]] ; then diff --git a/bin/oref0-meal.js b/bin/oref0-meal.js new file mode 100755 index 000000000..785e8a30b --- /dev/null +++ b/bin/oref0-meal.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node + +/* + oref0 meal data tool + + Collects meal data (carbs and boluses for last DIA hours) + for use in oref0 meal assist algorithm + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + +var generate = require('oref0/lib/meal'); +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ' [carbhistory.json]'); +} + +if (!module.parent) { + var pumphistory_input = process.argv.slice(2, 3).pop(); + if ([null, '--help', '-h', 'help'].indexOf(pumphistory_input) > 0) { + usage( ); + process.exit(0) + } + var profile_input = process.argv.slice(3, 4).pop(); + var clock_input = process.argv.slice(4, 5).pop(); + var carb_input = process.argv.slice(5, 6).pop(); + + if (!pumphistory_input || !profile_input) { + usage( ); + process.exit(1); + } + + var fs = require('fs'); + try { + var cwd = process.cwd(); + var all_data = require(cwd + '/' + pumphistory_input); + var profile_data = require(cwd + '/' + profile_input); + var clock_data = require(cwd + '/' + clock_input); + } catch (e) { + return console.error("Could not parse input data: ", e); + } + + //console.log(carbratio_data); + var carb_data = { }; + if (typeof carb_input != 'undefined') { + try { + carb_data = JSON.parse(fs.readFileSync(carb_input, 'utf8')); + //console.error(JSON.stringify(carb_data)); + } catch (e) { + //console.error("Warning: could not parse carb_input."); + } + } + + // all_data.sort(function (a, b) { return a.date > b.date }); + + var inputs = { + history: all_data + , profile: profile_data + , clock: clock_data + , carbs: carb_data + }; + + var dia_carbs = generate(inputs); + console.log(JSON.stringify(dia_carbs)); +} + diff --git a/bin/oref0-mint-max-iob.sh b/bin/oref0-mint-max-iob.sh index 8a1473cd7..3144308d8 100755 --- a/bin/oref0-mint-max-iob.sh +++ b/bin/oref0-mint-max-iob.sh @@ -8,11 +8,11 @@ shift function help_message ( ) { cat < [max_iob.json] +Usage: +$self [preferences.json] $self help - this message -Print a perfect max_iob.json. +Print a perfect preferences.json. Examples: @@ -23,14 +23,13 @@ $ $self 2 $ $self 2 foo.json max_iob 2 saved in foo.json -bewest@hither:~/src/openaps/oref0$ EOF } case $MAX_IOB in -""| help) +--help|-h|""| help) help_message ;; *) diff --git a/bin/oref0-normalize-temps.js b/bin/oref0-normalize-temps.js index b6b51cd86..c808d718e 100755 --- a/bin/oref0-normalize-temps.js +++ b/bin/oref0-normalize-temps.js @@ -17,17 +17,28 @@ var find_insulin = require('oref0/lib/temps'); var find_bolus = require('oref0/lib/bolus'); var describe_pump = require('oref0/lib/pump'); +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ''); +} if (!module.parent) { - var iob_input = process.argv.slice(2, 3).pop() + var iob_input = process.argv.slice(2, 3).pop(); + if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) { + usage( ); + process.exit(0) + } if (!iob_input) { - console.log('usage: ', process.argv.slice(0, 2), ''); + usage( ) process.exit(1); } var cwd = process.cwd() - var all_data = require(cwd + '/' + iob_input); + try { + var all_data = require(cwd + '/' + iob_input); + } catch (e) { + return console.error("Could not parse pumphistory: ", e); + } var inputs = { history: all_data diff --git a/bin/oref0-pebble.js b/bin/oref0-pebble.js index ffc70a81a..a58f9dd13 100755 --- a/bin/oref0-pebble.js +++ b/bin/oref0-pebble.js @@ -50,19 +50,28 @@ function fileHM(file) { return HMS[0].concat(":", HMS[1]); } +function usage ( ) { + console.log('usage: ', process.argv.slice(0, 2), ' [meal.json]'); +} + if (!module.parent) { var fs = require('fs'); var glucose_input = process.argv.slice(2, 3).pop() + if ([null, '--help', '-h', 'help'].indexOf(glucose_input) > 0) { + usage( ); + process.exit(0) + } var iob_input = process.argv.slice(3, 4).pop() var basalprofile_input = process.argv.slice(4, 5).pop() var currenttemp_input = process.argv.slice(5, 6).pop() var requestedtemp_input = process.argv.slice(6, 7).pop() var enactedtemp_input = process.argv.slice(7, 8).pop() + var meal_input = process.argv.slice(8, 9).pop() if (!glucose_input || !iob_input || !basalprofile_input || !currenttemp_input || !requestedtemp_input || !enactedtemp_input) { - console.log('usage: ', process.argv.slice(0, 2), ' '); + usage( ); process.exit(1); } @@ -91,7 +100,11 @@ if (!module.parent) { } else { tempstring = "Tmp: " + temp.duration + "m@" + temp.rate.toFixed(1); } - var requestedtemp = require(cwd + '/' + requestedtemp_input); + try { + var requestedtemp = require(cwd + '/' + requestedtemp_input); + } catch (e) { + return console.error("Could not parse requestedtemp: ", e); + } var reqtempstring; if (typeof requestedtemp.duration === 'undefined') { reqtempstring = "None"; @@ -112,17 +125,34 @@ if (!module.parent) { enactedHMS = enactedDate.toLocaleTimeString().split(":") enactedat = enactedHMS[0].concat(":", enactedHMS[1]); + var mealCOB = "???"; + if (typeof meal_input != 'undefined') { + try { + meal_data = JSON.parse(fs.readFileSync(meal_input, 'utf8')); + //console.error(JSON.stringify(meal_data)); + if (typeof meal_data.mealCOB != 'undefined') { + mealCOB = meal_data.mealCOB; + } + } catch (e) { + //console.error("Optional feature Meal Assist not configured."); + } + } + + var os = require("os"); + var host = os.hostname(); var pebble = { "content" : "" + bgnow + requestedtemp.tick + " " + bgTime + "\n" + iob + "U->" + requestedtemp.eventualBG + "-" + requestedtemp.snoozeBG + "\n" - + "Act: " + enactedstring - + " at " + enactedat + "\n" + //+ "Act: " + enactedstring + //+ " at " + enactedat + "\n" + tempstring + " at " + temp_time + "\n" + "Req: " + reqtempstring + "\n" + requestedtemp.reason + "\n" - + "Sched: " + basalRate + "U/hr\n", + + "Sched: " + basalRate + "U/hr\n" + + "mealCOB: " + mealCOB + "g\n" + + host + "\n", "refresh_frequency": 1 }; diff --git a/bin/oref0-raw.js b/bin/oref0-raw.js new file mode 100755 index 000000000..3bef28ec1 --- /dev/null +++ b/bin/oref0-raw.js @@ -0,0 +1,62 @@ +#!/usr/bin/env node +'use strict'; + +var fs = require('fs'); +var os = require("os"); + +var safeRequire = require('../lib/require-utils').safeRequire; +var getLastGlucose = require('../lib/glucose-get-last'); +var withRawGlucose = require('../lib/with-raw-glucose'); + +/* + Fills CGM data doesn't already contain an EVG, if we have unfiltered, filtered, and a cal + + Released under MIT license. See the accompanying LICENSE.txt file for + full terms and conditions + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + */ + +function usage ( ) { + console.error('usage: ', process.argv.slice(0, 2), ' [150]'); +} +if (!module.parent) { + var glucose_input = process.argv.slice(2, 3).pop(); + if ([null, '--help', '-h', 'help'].indexOf(glucose_input) > 0) { + usage( ); + process.exit(0) + } + var cal_input = process.argv.slice(3, 4).pop(); + + //limit to prevent high temping + var max_raw = process.argv.slice(4, 5).pop(); + + if (!glucose_input || !cal_input) { + usage( ); + process.exit(1); + } + + try { + var cwd = process.cwd(); + var glucose_data = safeRequire(cwd + '/' + glucose_input); + var cals = safeRequire(cwd + '/' + cal_input); + + + glucose_data = glucose_data.map(function each (entry) { + return withRawGlucose(entry, cals, max_raw); + }); + + console.log(JSON.stringify(glucose_data)); + + } catch (e) { + return console.error("Could not parse input data: ", e); + } + +} diff --git a/bin/oref0-reset-git.sh b/bin/oref0-reset-git.sh index aa8a33629..be2cb9a78 100755 --- a/bin/oref0-reset-git.sh +++ b/bin/oref0-reset-git.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Delete git lock / history if necessary to recover from corrupted .git objects # @@ -15,6 +15,30 @@ # must be run from within a git repo to do anything useful # remove old lockfile if still present +self=$(basename $0) +BACKUP_AREA=${1-${BACKUP_AREA-/var/cache/openaps-ruination}} +function usage ( ) { + +cat </dev/null +# first, try oref0-fix-git-corruption.sh to preserve git history up to last good commit +echo "Attempting to fix git corruption. Please wait 15s." +oref0-fix-git-corruption & +sleep 15 && killall oref0-fix-git-corruption # if git repository is too corrupt to do anything, mv it to /tmp and start over. -git status > /dev/null || ( mv .git /tmp/.git-`date +%s` && openaps init . ) + +git status > /dev/null || (echo "Saving backup to: $BACKUP" > /dev/stderr; mv .git $BACKUP; openaps init . ) diff --git a/bin/oref0-reset-usb.sh b/bin/oref0-reset-usb.sh index 82da00bd7..113731b35 100755 --- a/bin/oref0-reset-usb.sh +++ b/bin/oref0-reset-usb.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # Power-cycle the Raspberry Pi USB bus to reset attached USB devices # @@ -13,6 +13,22 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +self=$(basename $0) +function usage ( ) { + +cat < $FILE sleep 1 diff --git a/bin/oref0-template.js b/bin/oref0-template.js new file mode 100755 index 000000000..eaefd01bf --- /dev/null +++ b/bin/oref0-template.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node + + + +var argv = require('yargs') + .usage("$0 [args]") + /* + .option('', { + }) + */ + .command('mint ', 'generate template for import', require('oref0/lib/templates/')) + // .choices('type', ['devices', 'reports', 'alias', '*']) + /* + .command('devices', 'generate common device loops', function (yargs) { + return yargs.option('b', { + alias: 'bar' + }); + }) + */ + .help('help') + .alias('h', 'help') + .completion( ) + .strict( ) + .argv + ; + +// console.log('after', argv); diff --git a/bin/oref0.sh b/bin/oref0.sh index 44ffa5c9f..6d80925b5 100755 --- a/bin/oref0.sh +++ b/bin/oref0.sh @@ -12,29 +12,53 @@ function help_message ( ) { Usage: $self - ______ ______ ______ ______ 0 -/ | | \ | | | \ | | | | -| | | | | |__| | | |---- | |---- -\_|__|_/ |_| \_\ |_|____ |_| + ______ ______ ______ ______ 0 + / | | \ | | | \ | | | | + | | | | | |__| | | |---- | |---- + \_|__|_/ |_| \_\ |_|____ |_| Valid commands: + oref0 device-helper - : create/template a device from bash commands easily + oref0 alias-helper - : create/template a alias from bash commands easily oref0 env - print information about environment. oref0 pebble oref0 ifttt-notify oref0 get-profile oref0 calculate-iob + oref0 meal oref0 determine-basal + oref0 export-loop - Print a backup json representation of entire configuration. oref0 help - this message EOF } case $NAME in +device-helper) + name=$1 + shift + cat < <[your_webapi].azurewebsites.net>'); +} if (!module.parent) { var iob_input = process.argv.slice(2, 3).pop() + if ([null, '--help', '-h', 'help'].indexOf(iob_input) > 0) { + usage( ); + process.exit(0) + } var enacted_temps_input = process.argv.slice(3, 4).pop() var glucose_input = process.argv.slice(4, 5).pop() var webapi = process.argv.slice(5, 6).pop() if (!iob_input || !enacted_temps_input || !glucose_input || !webapi) { - console.log('usage: ', process.argv.slice(0, 2), ' <[your_webapi].azurewebsites.net>'); + usage( ); process.exit(1); } } diff --git a/lib/basal-set-temp.js b/lib/basal-set-temp.js index daf70e4d5..0e609f937 100644 --- a/lib/basal-set-temp.js +++ b/lib/basal-set-temp.js @@ -1,5 +1,5 @@ -var setTempBasal = function (rate, duration, profile, rT, offline) { - maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * profile.current_basal); +var setTempBasal = function (rate, duration, profile, rT, currenttemp) { + var maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * profile.current_basal); if (rate < 0) { rate = 0; @@ -8,16 +8,39 @@ var setTempBasal = function (rate, duration, profile, rT, offline) { rate = maxSafeBasal; } - // rather than canceling temps, if Offline mode is set, always set the current basal as a 30m temp - // so we can see on the pump that openaps is working - if (duration == 0 && offline == 'Offline') { - rate = profile.current_basal; - duration = 30; + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && typeof(currenttemp.rate) !== 'undefined' && currenttemp.duration > 20 && rate < currenttemp.rate + 0.1 && rate > currenttemp.rate - 0.1) { + rT.reason += ", but "+currenttemp.duration+"m left and " + currenttemp.rate + " ~ req " + rate + "U/hr: no action required"; + return rT; } - rT.duration = duration; - rT.rate = Math.round((Math.round(rate / 0.05) * 0.05)*100)/100; - return rT; + var suggestedRate = Math.round((Math.round(rate / 0.05) * 0.05)*100)/100; + if (suggestedRate === profile.current_basal) { + if (profile.skip_neutral_temps) { + if (typeof(currenttemp) !== 'undefined' && typeof(currenttemp.duration) !== 'undefined' && currenttemp.duration > 0) { + reason(rT, 'Suggested rate is same as profile rate, a temp basal is active, canceling current temp'); + rT.duration = 0; + rT.rate = 0; + return rT; + } else { + reason(rT, 'Suggested rate is same as profile rate, no temp basal is active, doing nothing'); + return rT; + } + } else { + reason(rT, 'Setting neutral temp basal of ' + profile.current_basal + 'U/hr'); + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } + } else { + rT.duration = duration; + rT.rate = suggestedRate; + return rT; + } }; -module.exports = setTempBasal; \ No newline at end of file +function reason(rT, msg) { + rT.reason = (rT.reason ? rT.reason + '. ' : '') + msg; + console.error(msg); +} + +module.exports = setTempBasal; diff --git a/lib/bolus.js b/lib/bolus.js index 7e14e995a..ab3bca773 100644 --- a/lib/bolus.js +++ b/lib/bolus.js @@ -113,7 +113,7 @@ function reduce (treatments) { // TODO: annotate prediction } } - if (state.carbs && state.insulin && state.bg) { + if (has_carbs && has_insulin) { state.eventType = 'Meal Bolus'; } else { if (has_carbs && !has_insulin) { diff --git a/lib/determine-basal/determine-basal.js b/lib/determine-basal/determine-basal.js index 52cb852fe..f58e77250 100644 --- a/lib/determine-basal/determine-basal.js +++ b/lib/determine-basal/determine-basal.js @@ -12,7 +12,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, offline, setTempBasal) { +var determine_basal = function determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, setTempBasal) { var rT = { //short for requestedTemp }; @@ -20,6 +20,14 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ rT.error ='Error: could not get current basal rate'; return rT; } + var basal = profile.current_basal; + if (typeof autosens_data !== 'undefined' ) { + basal = profile.current_basal * autosens_data.ratio; + basal = Math.round(basal*100)/100; + if (basal != profile.current_basal) { + console.error("Adjusting basal from "+profile.current_basal+" to "+basal); + } + } var bg = glucose_status.glucose; if (bg < 30) { //Dexcom is in ??? mode or calibrating, do nothing. Asked @benwest for raw data in iter_glucose @@ -31,6 +39,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ // if target_bg is set, great. otherwise, if min and max are set, then set target to their average var target_bg; + var min_bg; + if (typeof profile.min_bg !== 'undefined') { + min_bg = profile.min_bg; + } if (typeof profile.target_bg !== 'undefined') { target_bg = profile.target_bg; } else { @@ -63,28 +75,41 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ var minDelta = Math.min(glucose_status.delta, glucose_status.avgdelta); //var maxDelta = Math.max(glucose_status.delta, glucose_status.avgdelta); - + var sens = profile.sens; + if (typeof autosens_data !== 'undefined' ) { + sens = profile.sens / autosens_data.ratio; + sens = Math.round(sens*10)/10; + if (sens != profile.sens) { + console.error("Adjusting sens from "+profile.sens+" to "+sens); + } + } + //calculate BG impact: the amount BG "should" be rising or falling based on insulin activity alone - var bgi = Math.round(( -iob_data.activity * profile.sens * 5 )*100)/100; - // project deviation over next 15 minutes - var deviation = Math.round( 15 / 5 * ( glucose_status.avgdelta - bgi ) ); - //console.log("Avg.Delta: " + glucose_status.avgdelta.toFixed(1) + ", BGI: " + bgi.toFixed(1) + " 15m activity projection: " + deviation.toFixed(0)); + var bgi = Math.round(( -iob_data.activity * sens * 5 )*100)/100; + // project positive deviations for 15 minutes + var deviation = Math.round( 15 / 5 * ( minDelta - bgi ) ); + // project negative deviations for 30 minutes + if (deviation < 0) { + deviation = Math.round( 30 / 5 * ( glucose_status.avgdelta - bgi ) ); + } // calculate the naive (bolus calculator math) eventual BG based on net IOB and sensitivity - var naive_eventualBG = Math.round( bg - (iob_data.iob * profile.sens) ); + if (iob_data.iob > 0) { + var naive_eventualBG = Math.round( bg - (iob_data.iob * sens) ); + } else { // if IOB is negative, be more conservative and use the lower of sens, profile.sens + var naive_eventualBG = Math.round( bg - (iob_data.iob * Math.min(sens, profile.sens) ) ); + } // and adjust it for the deviation above var eventualBG = naive_eventualBG + deviation; - // calculate what portion of that is due to bolusiob - var bolusContrib = iob_data.bolusiob * profile.sens; + // 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 = Math.round( naive_eventualBG + 1.5 * bolusContrib ); // adjust that for deviation like we did eventualBG var snoozeBG = naive_snoozeBG + deviation; - //console.log("BG: " + bg +"(" + tick + ","+glucose_status.avgdelta.toFixed(1)+")"+ " -> " + eventualBG + "-" + snoozeBG + " (Unadjusted: " + naive_eventualBG + "-" + naive_snoozeBG + "), BGI: " + bgi); var expectedDelta = Math.round(( bgi + ( target_bg - eventualBG ) / ( profile.dia * 60 / 5 ) )*10)/10; - //console.log("expectedDelta: " + expectedDelta); if (typeof eventualBG === 'undefined' || isNaN(eventualBG)) { rT.error ='Error: could not calculate eventualBG'; @@ -92,7 +117,7 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ } // min_bg of 90 -> threshold of 70, 110 -> 80, and 130 -> 90 - var threshold = profile.min_bg - 0.5*(profile.min_bg-50); + var threshold = min_bg - 0.5*(min_bg-50); rT = { 'temp': 'absolute' @@ -102,167 +127,270 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_ , 'snoozeBG': snoozeBG }; + var basaliob; + if (iob_data.basaliob) { basaliob = iob_data.basaliob; } + else { basaliob = iob_data.iob - iob_data.bolussnooze; } + + // net amount of basal insulin delivered over the last DIA hours + var hightempinsulin = iob_data.hightempinsulin; + + var wtfAssist=0; + var mealAssist=0; + var mealAssistPct = 0; + //var wtfAssistPct = 0; + // if BG is high (more than DIA hours of basal above max_bg, i.e. above about 220mg/dL) and rising, wtf-assist + var high = profile.max_bg + ( basal * (profile.dia) * sens ); + if ( bg > high && minDelta > Math.max(0,bgi) ) { + wtfAssist=1; + } + // minDelta is > 12 and devation is > 50 wtf-assist and meal-assist + var wtfDeviation=50; + var wtfDelta=12; + if ( deviation > wtfDeviation && minDelta > wtfDelta ) { + wtfAssist=1; + mealAssist=1; + } else { + // phase in mealAssist, as a fraction + mealAssist = Math.max(0, Math.round( Math.min(deviation/wtfDeviation,minDelta/wtfDelta)*100)/100 ); + } + var remainingMealBolus = Math.round( (1.1 * meal_data.carbs/profile.carb_ratio - ( meal_data.boluses + Math.max(0,hightempinsulin) ) )*10)/10; + // if minDelta is >3 and >BGI, and there are uncovered carbs, meal-assist + if ( minDelta > Math.max(3, bgi) && meal_data.carbs > 0 && remainingMealBolus > 0 ) { + mealAssist=1; + } + // when rising with carbs or rising fast for no good reason, meal-assist (ignore bolus IOB) + if (typeof(meal_data.carbs) == 'undefined') { + var wtfAssist=0; + var mealAssist=0; + } + if (mealAssist > 0) { + // ignore all covered IOB, and just set eventualBG to the current bg + mAeventualBG = Math.max(bg,eventualBG) + deviation; + eventualBG = Math.round(mealAssist*mAeventualBG + (1-mealAssist)*eventualBG); + rT.eventualBG = eventualBG; + //console.error("eventualBG: "+eventualBG+", mAeventualBG: "+mAeventualBG+", rT.eventualBG: "+rT.eventualBG); + } + // lower target for meal-assist or wtf-assist (high and rising) + wtfAssist = Math.round( Math.max(wtfAssist, mealAssist) *100)/100; + if (wtfAssist > 0) { + min_bg = wtfAssist*80 + (1-wtfAssist)*min_bg; + target_bg = (min_bg + profile.max_bg) / 2; + expectedDelta = Math.round(( bgi + ( target_bg - eventualBG ) / ( profile.dia * 60 / 5 ) )*10)/10; + mealAssistPct = Math.round(mealAssist*100); + wtfAssistPct = Math.round(wtfAssist*100); + rT.mealAssist = "On: "+mealAssistPct+"%, "+wtfAssistPct+"%, Carbs: " + meal_data.carbs + " Boluses: " + meal_data.boluses + " ISF: " + sens + ", Target: " + Math.round(target_bg) + " Deviation: " + deviation + " BGI: " + bgi; + } else { + rT.mealAssist = "Off: Carbs: " + meal_data.carbs + " Boluses: " + meal_data.boluses + " ISF: " + sens + ", Target: " + Math.round(target_bg) + " Deviation: " + deviation + " BGI: " + bgi; + } + + rT.reason=""; if (bg < threshold) { // low glucose suspend mode: BG is < ~80 - rT.reason = "BG " + bg + "<" + threshold; - if ((glucose_status.delta <= 0 && glucose_status.avgdelta <= 0) || (glucose_status.delta < expectedDelta && glucose_status.avgdelta < expectedDelta)) { + rT.reason += "BG " + bg + "<" + threshold; + if ((glucose_status.delta <= 0 && minDelta <= 0) || (glucose_status.delta < expectedDelta && minDelta < expectedDelta) || bg < 60 ) { // BG is still falling / rising slower than predicted - return setTempBasal(0, 30, profile, rT, offline); + return setTempBasal(0, 30, profile, rT, currenttemp); } - if (glucose_status.delta > glucose_status.avgdelta) { + if (glucose_status.delta > minDelta) { rT.reason += ", delta " + glucose_status.delta + ">0"; } else { - rT.reason += ", avg delta " + glucose_status.avgdelta.toFixed(2) + ">0"; + rT.reason += ", avg delta " + minDelta.toFixed(2) + ">0"; + } + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); } - if (currenttemp.rate > profile.current_basal) { // if a high-temp is running + /* + if (currenttemp.rate > basal) { // if a high-temp is running rT.reason += ", cancel high temp"; - return setTempBasal(0, 0, profile, rT, offline); // cancel high temp + return setTempBasal(0, 0, profile, rT, currenttemp); // cancel high temp } else if (currenttemp.duration && eventualBG > profile.max_bg) { // if low-temped and predicted to go high from negative IOB rT.reason += ", cancel low temp"; - return setTempBasal(0, 0, profile, rT, offline); // cancel low temp + return setTempBasal(0, 0, profile, rT, currenttemp); // cancel low temp } rT.reason += "; no high-temp to cancel"; return rT; + */ } - if (eventualBG < profile.min_bg) { // if eventual BG is below target: - rT.reason = "Eventual BG " + eventualBG + "<" + profile.min_bg; - if (minDelta > expectedDelta) { - if (minDelta > 0) { // if 5m or 15m avg BG is rising faster than expected delta - if (glucose_status.delta > glucose_status.avgdelta) { + // if there are still carbs we haven't bolused or high-temped for, + // and they're enough to get snoozeBG above min_bg + //if (remainingMealBolus > 0 && snoozeBG + remainingMealBolus*sens > min_bg && minDelta > Math.max(0,expectedDelta)) { + if (remainingMealBolus > 0 && snoozeBG + remainingMealBolus*sens > min_bg && minDelta > expectedDelta) { + // simulate an extended bolus to deliver the remainder over DIA (so 30m is 0.5x remainder/dia) + + //var insulinReq = Math.round( (0.5 * remainingMealBolus / profile.dia)*100)/100; + var basalAdj = Math.round( (remainingMealBolus / profile.dia)*100)/100; + if (minDelta < 0 && minDelta > expectedDelta) { + var newbasalAdj = Math.round(( basalAdj * (1 - (minDelta / expectedDelta)) ) * 100)/100; + console.error("Reducing basalAdj from " + basalAdj + " to " + newbasalAdj); + basalAdj = newbasalAdj; + } + rT.reason += remainingMealBolus+"U meal bolus remaining, "; + // by rebasing everything off an adjusted basal rate + basal += basalAdj; + basal = Math.round( basal*100 )/100; + //rT.reason += ", setting " + rate + "U/hr"; + //var rate = basal + (2 * insulinReq); + //rate = Math.round( rate * 1000 ) / 1000; + //return setTempBasal(rate, 30, profile, rT, currenttemp); + //} else if (snoozeBG > min_bg) { // if adding back in the bolus contribution BG would be above min + } + if (eventualBG < min_bg) { // if eventual BG is below target: + if (mealAssist > 0) { + //if (mealAssist === true) { + rT.reason += "Meal assist: " + meal_data.carbs + "g, " + meal_data.boluses + "U"; + } else { + rT.reason += "Eventual BG " + eventualBG + "<" + min_bg; + // if 5m or 15m avg BG is rising faster than expected delta + if (minDelta > expectedDelta && minDelta > 0) { + if (glucose_status.delta > minDelta) { rT.reason += ", but Delta " + tick + " > Exp. Delta " + expectedDelta; } else { - rT.reason += ", but Avg. Delta " + glucose_status.avgdelta.toFixed(2) + " > Exp. Delta " + expectedDelta; + rT.reason += ", but Avg. Delta " + minDelta.toFixed(2) + " > Exp. Delta " + expectedDelta; } - if (currenttemp.duration > 0) { // if there is currently any temp basal running - rT.reason = rT.reason += "; cancel"; - return setTempBasal(0, 0, profile, rT, offline); // cancel temp + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; + return rT; } else { - rT.reason = rT.reason += "; no temp to cancel"; - return rT; + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); } } } - // if this is just due to boluses, we can snooze until the bolus IOB decays (at double speed) - if (snoozeBG > profile.min_bg) { // if adding back in the bolus contribution BG would be above min - // if BG is falling and high-temped, or rising and low-temped, cancel - // compare against zero here, not BGI, because BGI will be highly negative from boluses and no carbs - if (glucose_status.delta < 0 && currenttemp.duration > 0 && currenttemp.rate > profile.current_basal) { - rT.reason += tick + ", and temp " + currenttemp.rate + " > basal " + profile.current_basal; - return setTempBasal(0, 0, profile, rT, offline); // cancel temp - } else if (glucose_status.delta > 0 && currenttemp.duration > 0 && currenttemp.rate < profile.current_basal) { - rT.reason += tick + ", and temp " + currenttemp.rate + " < basal " + profile.current_basal; - return setTempBasal(0, 0, profile, rT, offline); // cancel temp - } - - rT.reason += "bolus snooze: eventual BG range " + eventualBG + "-" + snoozeBG; - return rT; - } else { - // calculate 30m low-temp required to get projected BG up to target - // use snoozeBG instead of eventualBG to more gradually ramp in any counteraction of the user's boluses - var insulinReq = Math.min(0, (snoozeBG - target_bg) / profile.sens); - if (minDelta < 0 && minDelta > expectedDelta) { - // if we're barely falling, newinsulinReq should be barely negative - var newinsulinReq = Math.round(( insulinReq * (minDelta / expectedDelta) ) * 100)/100; - //console.log("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); - insulinReq = newinsulinReq; - } - // rate required to deliver insulinReq less insulin over 30m: - var rate = profile.current_basal + (2 * insulinReq); - rate = Math.round( rate * 1000 ) / 1000; - // if required temp < existing temp basal - if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 0 && rate > currenttemp.rate - 0.1)) { - rT.reason += ", temp " + currenttemp.rate + " <~ req " + rate + "U/hr"; - return rT; + if (eventualBG < min_bg) { + // if we've bolused recently, we can snooze until the bolus IOB decays (at double speed) + if (snoozeBG > min_bg) { // if adding back in the bolus contribution BG would be above min + rT.reason += ", bolus snooze: eventual BG range " + eventualBG + "-" + snoozeBG; + //console.log(currenttemp, basal ); + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); + } } else { - rT.reason += ", no temp, setting " + rate + "U/hr"; - return setTempBasal(rate, 30, profile, rT, offline); + // calculate 30m low-temp required to get projected BG up to target + // use snoozeBG to more gradually ramp in any counteraction of the user's boluses + // multiply by 2 to low-temp faster for increased hypo safety + var insulinReq = 2 * Math.min(0, (snoozeBG - target_bg) / sens); + if (minDelta < 0 && minDelta > expectedDelta) { + // if we're barely falling, newinsulinReq should be barely negative + rT.reason += ", Snooze BG " + snoozeBG; + var newinsulinReq = Math.round(( insulinReq * (minDelta / expectedDelta) ) * 100)/100; + //console.log("Increasing insulinReq from " + insulinReq + " to " + newinsulinReq); + insulinReq = newinsulinReq; + } + // rate required to deliver insulinReq less insulin over 30m: + var rate = basal + (2 * insulinReq); + rate = Math.round( rate * 1000 ) / 1000; + // if required temp < existing temp basal + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + if (insulinScheduled < insulinReq - 0.2) { // if current temp would deliver >0.2U less than the required insulin, raise the rate + rT.reason += ", "+currenttemp.duration + "m@" + (currenttemp.rate - basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " < req " + insulinReq + "-0.2U"; + return setTempBasal(rate, 30, profile, rT, currenttemp); + } + if (typeof currenttemp.rate !== 'undefined' && (currenttemp.duration > 5 && rate > currenttemp.rate - 0.1)) { + rT.reason += ", temp " + currenttemp.rate + " ~< req " + rate + "U/hr"; + return rT; + } else { + rT.reason += ", setting " + rate + "U/hr"; + return setTempBasal(rate, 30, profile, rT, currenttemp); + } } } } // if eventual BG is above min but BG is falling faster than expected Delta if (minDelta < expectedDelta) { - if (glucose_status.delta < glucose_status.avgdelta) { - rT.reason = "Eventual BG " + eventualBG + ">" + profile.min_bg + " but Delta " + tick + " < Exp. Delta " + expectedDelta; + if (glucose_status.delta < minDelta) { + rT.reason += "Eventual BG " + eventualBG + ">" + min_bg + " but Delta " + tick + " < Exp. Delta " + expectedDelta; } else { - rT.reason = "Eventual BG " + eventualBG + ">" + profile.min_bg + " but Avg. Delta " + glucose_status.avgdelta.toFixed(2) + " < Exp. Delta " + expectedDelta; + rT.reason += "Eventual BG " + eventualBG + ">" + min_bg + " but Avg. Delta " + minDelta.toFixed(2) + " < Exp. Delta " + expectedDelta; } - if (currenttemp.duration > 0) { // if there is currently any temp basal running - rT.reason = rT.reason += "; cancel"; - return setTempBasal(0, 0, profile, rT, offline); // cancel temp - } else { - rT.reason = rT.reason += "; no temp to cancel"; + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); } } - if (eventualBG < profile.max_bg) { - rT.reason = eventualBG + " is in range. No temp required"; - if (currenttemp.duration > 0) { // if there is currently any temp basal running - rT.reason = rT.reason += "; cancel"; - return setTempBasal(0, 0, profile, rT, offline); // cancel temp - } - if (offline == 'Offline') { - // if no temp is running or required, set the current basal as a temp, so you can see on the pump that the loop is working - if ((!currenttemp.duration || (currenttemp.rate == profile.current_basal)) && !rT.duration) { - rT.reason = rT.reason + "; setting current basal of " + profile.current_basal + " as temp"; - return setTempBasal(profile.current_basal, 30, profile, rT, offline); - } + if (eventualBG < profile.max_bg || snoozeBG < profile.max_bg) { + rT.reason += eventualBG+"-"+snoozeBG+" in range: no temp required"; + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); } - return rT; } - // eventual BG is above target: + // eventual BG is at/above target: // if iob is over max, just cancel any temps - var basal_iob = Math.round(( iob_data.iob - iob_data.bolusiob )*1000)/1000; - rT.reason = "Eventual BG " + eventualBG + ">" + profile.max_bg + ", "; - if (basal_iob > max_iob) { - rT.reason = "basal_iob " + basal_iob + " > max_iob " + max_iob; - return setTempBasal(0, 0, profile, rT, offline); + var basaliob; + if (iob_data.basaliob) { basaliob = iob_data.basaliob; } + else { basaliob = iob_data.iob - iob_data.bolussnooze; } + rT.reason += "Eventual BG " + eventualBG + ">=" + profile.max_bg + ", "; + if (basaliob > max_iob) { + rT.reason += "basaliob " + basaliob + " > max_iob " + max_iob; + if (currenttemp.duration > 15 && basal < currenttemp.rate + 0.1 && basal > currenttemp.rate - 0.1) { + rT.reason += ", temp " + currenttemp.rate + " ~ req " + basal + "U/hr"; + return rT; + } else { + rT.reason += "; setting current basal of " + basal + " as temp"; + return setTempBasal(basal, 30, profile, rT, currenttemp); + } } else { // otherwise, calculate 30m high-temp required to get projected BG down to target // insulinReq is the additional insulin required to get down to max bg: - var insulinReq = (eventualBG - target_bg) / profile.sens; + // if in meal assist mode, check if snoozeBG is lower, as eventualBG is not dependent on IOB + var insulinReq = (Math.min(snoozeBG,eventualBG) - target_bg) / sens; if (minDelta < 0 && minDelta > expectedDelta) { var newinsulinReq = Math.round(( insulinReq * (1 - (minDelta / expectedDelta)) ) * 100)/100; //console.log("Reducing insulinReq from " + insulinReq + " to " + newinsulinReq); insulinReq = newinsulinReq; } // if that would put us over max_iob, then reduce accordingly - if (insulinReq > max_iob-basal_iob) { - rT.reason = "max_iob " + max_iob + ", "; - insulinReq = max_iob-basal_iob; + if (insulinReq > max_iob-basaliob) { + rT.reason += "max_iob " + max_iob + ", "; + insulinReq = max_iob-basaliob; } // rate required to deliver insulinReq more insulin over 30m: - var rate = profile.current_basal + (2 * insulinReq); + var rate = basal + (2 * insulinReq); rate = Math.round( rate * 1000 ) / 1000; - var maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * profile.current_basal); + var maxSafeBasal = Math.min(profile.max_basal, 3 * profile.max_daily_basal, 4 * basal); if (rate > maxSafeBasal) { rT.reason += "adj. req. rate:"+rate.toFixed(1) +" to maxSafeBasal:"+maxSafeBasal.toFixed(1)+", "; - rate = maxSafeBasal; + rate = maxSafeBasal.toFixed(1); } - var insulinScheduled = currenttemp.duration * (currenttemp.rate - profile.current_basal) / 60; - if (insulinScheduled > insulinReq + 0.3) { // if current temp would deliver >0.3U more than the required insulin, lower the rate - rT.reason = currenttemp.duration + "mins @" + (currenttemp.rate - profile.current_basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " > req " + insulinReq + "+0.3 U"; - return setTempBasal(rate, 30, profile, rT, offline); + var insulinScheduled = currenttemp.duration * (currenttemp.rate - basal) / 60; + if (insulinScheduled > insulinReq + 0.1) { // if current temp would deliver >0.1U more than the required insulin, lower the rate + rT.reason += currenttemp.duration + "m@" + (currenttemp.rate - basal).toFixed(3) + " = " + insulinScheduled.toFixed(3) + " > req " + insulinReq + "+0.1U"; + return setTempBasal(rate, 30, profile, rT, currenttemp); } if (typeof currenttemp.duration == 'undefined' || currenttemp.duration == 0) { // no temp is set rT.reason += "no temp, setting " + rate + "U/hr"; - return setTempBasal(rate, 30, profile, rT, offline); + return setTempBasal(rate, 30, profile, rT, currenttemp); } - if (currenttemp.duration > 0 && rate < currenttemp.rate + 0.1) { // if required temp <~ existing temp basal + if (currenttemp.duration > 5 && rate < currenttemp.rate + 0.1) { // if required temp <~ existing temp basal rT.reason += "temp " + currenttemp.rate + " >~ req " + rate + "U/hr"; return rT; } // required temp > existing temp basal rT.reason += "temp " + currenttemp.rate + "<" + rate + "U/hr"; - return setTempBasal(rate, 30, profile, rT, offline); + return setTempBasal(rate, 30, profile, rT, currenttemp); } }; diff --git a/lib/glucose-get-last.js b/lib/glucose-get-last.js index 7e6b5b0b4..3c819b57b 100644 --- a/lib/glucose-get-last.js +++ b/lib/glucose-get-last.js @@ -28,8 +28,8 @@ var getLastGlucose = function (data) { return { delta: now.glucose - last.glucose , glucose: now.glucose - , avgdelta: avg + , avgdelta: Math.round( avg * 1000 ) / 1000 }; }; -module.exports = getLastGlucose; \ No newline at end of file +module.exports = getLastGlucose; diff --git a/lib/iob/total.js b/lib/iob/total.js index 8b2736651..eb9022a46 100644 --- a/lib/iob/total.js +++ b/lib/iob/total.js @@ -1,11 +1,13 @@ - function iobTotal(opts, time) { var iobCalc = opts.calculate; var treatments = opts.treatments; var profile_data = opts.profile; var iob = 0; - var bolusiob = 0; + var bolussnooze = 0; + var basaliob = 0; var activity = 0; + var netbasalinsulin = 0; + var hightempinsulin = 0; if (!treatments) return {}; //if (typeof time === 'undefined') { //var time = new Date(); @@ -17,21 +19,37 @@ function iobTotal(opts, time) { var tIOB = iobCalc(treatment, time, dia); if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - // keep track of bolus IOB separately for snoozes, but decay it three times as fast + // keep track of bolus IOB separately for snoozes, but decay it twice as fast if (treatment.insulin >= 0.2 && treatment.started_at) { //use half the dia for double speed bolus snooze var bIOB = iobCalc(treatment, time, dia / 2); //console.log(treatment); //console.log(bIOB); - if (bIOB && bIOB.iobContrib) bolusiob += bIOB.iobContrib; + if (bIOB && bIOB.iobContrib) bolussnooze += bIOB.iobContrib; + } else { + var aIOB = iobCalc(treatment, time, dia); + if (aIOB && aIOB.iobContrib) basaliob += aIOB.iobContrib; + if (treatment.insulin) { + now = time.getTime(); + var dia_ago = now - profile_data.dia*60*60*1000; + if(treatment.date > dia_ago && treatment.date <= now) { + netbasalinsulin += treatment.insulin; + if (treatment.insulin > 0) { + hightempinsulin += treatment.insulin; + } + } + } } } }); return { - iob: iob, - activity: activity, - bolusiob: bolusiob + iob: Math.round( iob * 1000 ) / 1000, + activity: Math.round( activity * 10000 ) / 10000, + bolussnooze: Math.round( bolussnooze * 1000 ) / 1000, + basaliob: Math.round( basaliob * 1000 ) / 1000, + netbasalinsulin: Math.round( netbasalinsulin * 1000 ) / 1000, + hightempinsulin: Math.round( hightempinsulin * 1000 ) / 1000, }; } diff --git a/lib/meal/history.js b/lib/meal/history.js new file mode 100644 index 000000000..c66cbf265 --- /dev/null +++ b/lib/meal/history.js @@ -0,0 +1,37 @@ + +var tz = require('timezone'); + +function findMealInputs (inputs) { + var pumpHistory = inputs.history; + var carbHistory = inputs.carbs; + var profile_data = inputs.profile; + var mealInputs = []; + for (var i=0; i < carbHistory.length; i++) { + var current = carbHistory[i]; + if (current.carbs && current.created_at) { + var temp = {}; + temp.timestamp = current.created_at; + temp.carbs = current.carbs; + mealInputs.push(temp); + } + } + for (var i=0; i < pumpHistory.length; i++) { + var current = pumpHistory[i]; + if (pumpHistory[i]._type == "Bolus") { + //console.log(pumpHistory[i]); + var temp = {}; + temp.timestamp = current.timestamp; + temp.bolus = current.amount; + mealInputs.push(temp); + } + if (pumpHistory[i]._type == "BolusWizard") { + //console.log(pumpHistory[i]); + var temp = {}; + temp.timestamp = current.timestamp; + temp.carbs = current.carb_input; + mealInputs.push(temp); + } + } + return mealInputs; +} +exports = module.exports = findMealInputs; diff --git a/lib/meal/index.js b/lib/meal/index.js new file mode 100644 index 000000000..4af9d3e1f --- /dev/null +++ b/lib/meal/index.js @@ -0,0 +1,21 @@ + +var tz = require('timezone'); +var find_meals = require('./history'); +var sum = require('./total'); + +function generate (inputs) { + + var treatments = find_meals(inputs); + + var opts = { + treatments: treatments + , profile: inputs.profile + }; + + var clock = new Date(tz(inputs.clock)); + + var meal_data = sum(opts, clock); + return meal_data; +} + +exports = module.exports = generate; diff --git a/lib/meal/total.js b/lib/meal/total.js new file mode 100644 index 000000000..9923941e6 --- /dev/null +++ b/lib/meal/total.js @@ -0,0 +1,48 @@ +var tz = require('timezone'); + +function diaCarbs(opts, time) { + var treatments = opts.treatments; + var profile_data = opts.profile; + var carbs = 0; + var boluses = 0; + var carbDelay = 20 * 60 * 1000; + //TODO: make this configurable + var carbs_hr = 30; + var firstCarbTime = time.getTime(); + if (!treatments) return {}; + + treatments.forEach(function(treatment) { + now = time.getTime(); + var dia_ago = now - profile_data.dia*60*60*1000; + t = new Date(tz(treatment.timestamp)).getTime(); + if(t > dia_ago && t <= now) { + if (treatment.carbs >= 1) { + if (t < firstCarbTime) { + //firstCarbTime = treatment.timestamp; + firstCarbTime = t; + //console.error(firstCarbTime); + } + carbs += parseFloat(treatment.carbs); + } + if (treatment.bolus >= 0.1) { + boluses += parseFloat(treatment.bolus); + } + } + }); + now = new Date().getTime(); + hours = (now-firstCarbTime-carbDelay)/(60*60*1000); + decayed = carbs_hr*hours; + //console.error(hours, decayed); + var mealCOB = Math.max(0, carbs - (carbs_hr*hours)); + //console.error(mealCOB); + + return { + carbs: Math.round( carbs * 1000 ) / 1000, + boluses: Math.round( boluses * 1000 ) / 1000, + // for display only: not usable in calculations + mealCOB: Math.round( mealCOB ) + }; +} + +exports = module.exports = diaCarbs; + diff --git a/lib/profile/basal.js b/lib/profile/basal.js index 6cbda113f..d94162df2 100644 --- a/lib/profile/basal.js +++ b/lib/profile/basal.js @@ -2,9 +2,11 @@ var getTime = require('../medtronic-clock'); /* Return basal rate(U / hr) at the provided timeOfDay */ -function basalLookup (schedules) { +function basalLookup (schedules, now) { var basalprofile_data = schedules; - var now = new Date(); + if (typeof(now) === 'undefined') { + var now = new Date(); + } var basalRate = basalprofile_data[basalprofile_data.length-1].rate for (var i = 0; i < basalprofile_data.length - 1; i++) { diff --git a/lib/profile/carbs.js b/lib/profile/carbs.js index 74cc503f4..940cbeb62 100644 --- a/lib/profile/carbs.js +++ b/lib/profile/carbs.js @@ -3,18 +3,20 @@ var getTime = require('../medtronic-clock'); function carbRatioLookup (inputs) { var now = new Date(); - var carbratio_data = inputs.carbs; - //carbratio_data.schedule.sort(function (a, b) { return a.offset > b.offset }); - var carbRatio = carbratio_data.schedule[carbratio_data.schedule.length - 1] - - for (var i = 0; i < carbratio_data.schedule.length - 1; i++) { - if ((now >= getTime(carbratio_data.schedule[i].offset)) && (now < getTime(carbratio_data.schedule[i + 1].offset))) { - carbRatio = carbratio_data.schedule[i]; - break; + var carbratio_data = inputs.carbratio; + if (typeof(carbratio_data) != "undefined" && typeof(carbratio_data.schedule) != "undefined") { + //carbratio_data.schedule.sort(function (a, b) { return a.offset > b.offset }); + var carbRatio = carbratio_data.schedule[carbratio_data.schedule.length - 1] + + for (var i = 0; i < carbratio_data.schedule.length - 1; i++) { + if ((now >= getTime(carbratio_data.schedule[i].offset)) && (now < getTime(carbratio_data.schedule[i + 1].offset))) { + carbRatio = carbratio_data.schedule[i]; + break; + } } - } return carbRatio.ratio; - profile.carbratio = carbRatio.ratio; + //profile.carbratio = carbRatio.ratio; + } else { return; } } carbRatioLookup.carbRatioLookup = carbRatioLookup; diff --git a/lib/profile/index.js b/lib/profile/index.js index c256729e8..b05a1b432 100644 --- a/lib/profile/index.js +++ b/lib/profile/index.js @@ -2,10 +2,11 @@ var basal = require('./basal'); var targets = require('./targets'); var isf = require('./isf'); +var carb_ratios = require('./carbs'); function defaults ( ) { var profile = { - max_iob: 0 // if max_iob.json is not profided, never give more insulin than the pump would have + max_iob: 0 // if max_iob is not provided, never give more insulin than the pump would have // , dia: pumpsettings_data.insulin_action_curve , type: "current" }; @@ -16,22 +17,37 @@ function generate (inputs, opts) { var profile = opts && opts.type ? opts : defaults( ); var pumpsettings_data = inputs.settings; - if (inputs.settings.insulin_action_curve) { + if (inputs.settings.insulin_action_curve > 1) { profile.dia = pumpsettings_data.insulin_action_curve; + } else { + console.error("DIA of",profile.dia,"is not supported"); + return -1; } if (inputs.max_iob) { profile.max_iob = inputs.max_iob; } + profile.skip_neutral_temps = inputs.skip_neutral_temps; profile.current_basal = basal.basalLookup(inputs.basals); profile.max_daily_basal = basal.maxDailyBasal(inputs); profile.max_basal = basal.maxBasalLookup(inputs); + if (profile.basal < 0.1) { + console.error("max_basal of",profile.max_basal,"is not supported"); + return -1; + } var range = targets.bgTargetsLookup(inputs); profile.min_bg = range.min_bg; profile.max_bg = range.max_bg; profile.sens = isf.isfLookup(inputs); + if (profile.sens < 5) { + console.error("ISF of",profile.sens,"is not supported"); + return -1; + } + if (typeof(inputs.carbratio) != "undefined") { + profile.carb_ratio = carb_ratios.carbRatioLookup(inputs); + } return profile; } diff --git a/lib/profile/isf.js b/lib/profile/isf.js index e97504f88..71b91a548 100644 --- a/lib/profile/isf.js +++ b/lib/profile/isf.js @@ -7,6 +7,9 @@ function isfLookup (inputs) { //isf_data.sensitivities.sort(function (a, b) { return a.offset > b.offset }); var isfSchedule = isf_data.sensitivities[isf_data.sensitivities.length - 1] + if (isf_data.sensitivities[0].offset != 0 || isf_data.sensitivities[0].i != 0 || isf_data.sensitivities[0].x != 0 || isf_data.sensitivities[0].start != "00:00:00") { + return -1; + } for (var i = 0; i < isf_data.sensitivities.length - 1; i++) { if ((now >= getTime(isf_data.sensitivities[i].offset)) && (now < getTime(isf_data.sensitivities[i + 1].offset))) { isfSchedule = isf_data.sensitivities[i]; diff --git a/lib/profile/targets.js b/lib/profile/targets.js index b3e7a2a4b..2a696b020 100644 --- a/lib/profile/targets.js +++ b/lib/profile/targets.js @@ -25,7 +25,7 @@ function lookup (inputs) { function bound_target_range (target) { // hard-code lower bounds for min_bg and max_bg in case pump is set too low, or units are wrong - target.max_bg = Math.max(100, target.high); + target.max_bg = Math.max(90, target.high); target.min_bg = Math.max(90, target.low); // hard-code upper bound for min_bg in case pump is set too high target.min_bg = Math.min(200, target.min_bg); diff --git a/lib/pump.js b/lib/pump.js index 3e02e5102..65b7539c4 100644 --- a/lib/pump.js +++ b/lib/pump.js @@ -7,10 +7,17 @@ function translate (treatments) { var invalid = false; switch (current._type) { case 'CalBGForPH': - current.eventType = ''; + current.eventType = 'BG Check'; current.glucose = current.amount; current.glucoseType = 'Finger'; - current.notes = "Pump received finger stick."; + break; + case 'BasalProfileStart': + case 'ResultDailyTotal': + case 'BGReceived': + case 'Sara6E': + case 'Model522ResultTotals': + case 'Model722ResultTotals': + invalid = true; break; default: break; diff --git a/lib/require-utils.js b/lib/require-utils.js new file mode 100644 index 000000000..6081e7e28 --- /dev/null +++ b/lib/require-utils.js @@ -0,0 +1,31 @@ +'use strict'; + +var fs = require('fs'); + +function safeRequire (path) { + var resolved; + + try { + resolved = require(path); + } catch (e) { + console.error("Could not require: " + path, e); + } + + return resolved; +} + +function requireWithTimestamp (path) { + var resolved = safeRequire(path); + + if (resolved) { + resolved.timestamp = fs.statSync(path).mtime; + } + + return resolved; +} + + +module.exports = { + safeRequire: safeRequire + , requireWithTimestamp: requireWithTimestamp +}; \ No newline at end of file diff --git a/lib/templates/exported-loop.json b/lib/templates/exported-loop.json new file mode 100644 index 000000000..6092fd1b8 --- /dev/null +++ b/lib/templates/exported-loop.json @@ -0,0 +1,679 @@ +[ + { + "type": "alias", + "name": "rm-warmup", + "rm-warmup": { + "command": "! bash -c \"rm -f model.json monitor/clock.json > /dev/null\"" + } + }, + { + "type": "alias", + "name": "warmup", + "warmup": { + "command": "report invoke model.json raw-pump/clock-raw.json monitor/clock.json" + } + }, + { + "fail-warmup": { + "command": "! bash -c \"echo PREFLIGHT FAIL; exit 1\"" + }, + "type": "alias", + "name": "fail-warmup" + }, + { + "type": "alias", + "preflight": { + "command": "! bash -c \"(openaps rm-warmup; echo PREFLIGHT ) && openaps warmup 2>&1 >/dev/null && grep -q T monitor/clock.json && echo PREFLIGHT OK || openaps fail-warmup\"" + }, + "name": "preflight" + }, + { + "type": "alias", + "name": "monitor-cgm", + "monitor-cgm": { + "command": "report invoke monitor/glucose-raw.json monitor/glucose.json" + } + }, + { + "type": "alias", + "name": "monitor-pump-history", + "monitor-pump-history": { + "command": "report invoke monitor/pump-history-raw.json monitor/pump-history.json" + } + }, + { + "type": "alias", + "name": "get-basal-status", + "get-basal-status": { + "command": "report invoke monitor/temp-basal-status.json" + } + }, + { + "type": "alias", + "name": "get-pump-details", + "get-pump-details": { + "command": "report invoke monitor/reservoir.json monitor/status.json monitor/battery.json" + } + }, + { + "type": "alias", + "name": "get-settings", + "get-settings": { + "command": "report invoke settings/bg-targets-raw.json settings/bg-targets.json settings/insulin-sensitivities-raw.json settings/insulin-sensitivities.json settings/selected-basal-profile.json settings/settings.json" + } + }, + { + "type": "alias", + "name": "gather-pump-data", + "gather-pump-data": { + "command": "! bash -c \"openaps get-basal-status; openaps get-pump-details; openaps monitor-pump-history; openaps get-settings\"" + } + }, + { + "type": "alias", + "name": "gather-clean-data", + "gather-clean-data": { + "command": "! bash -c \"openaps monitor-cgm && openaps gather-pump-data\"" + } + }, + { + "type": "alias", + "name": "do-oref0", + "do-oref0": { + "command": "report invoke oref0-monitor/profile.json oref0-monitor/iob.json oref0-predict/oref0.json" + } + }, + { + "type": "alias", + "name": "enact-oref0", + "enact-oref0": { + "command": "report invoke oref0-enacted/enacted-temp-basal.json" + } + }, + { + "do-everything": { + "command": "! bash -c \"openaps preflight && openaps gather-clean-data && openaps do-oref0 && openaps enact-oref0\"" + }, + "type": "alias", + "name": "do-everything" + }, + { + "type": "vendor", + "name": "mmeowlink.vendors.mmeowlink", + "mmeowlink.vendors.mmeowlink": { + "path": ".", + "module": "mmeowlink.vendors.mmeowlink" + } + }, + { + "type": "vendor", + "name": "openxshareble", + "openxshareble": { + "path": ".", + "module": "openxshareble" + } + }, + { + "openapscontrib.timezones": { + "path": ".", + "module": "openapscontrib.timezones" + }, + "type": "vendor", + "name": "openapscontrib.timezones" + }, + { + "openapscontrib.mmhistorytools": { + "path": ".", + "module": "openapscontrib.mmhistorytools" + }, + "type": "vendor", + "name": "openapscontrib.mmhistorytools" + }, + { + "pancreabble": { + "path": ".", + "module": "pancreabble" + }, + "type": "vendor", + "name": "pancreabble" + }, + { + "main": { + "phases": "", + "rrule": "RRULE:FREQ=MINUTELY;INTERVAL=1" + }, + "type": "schedule", + "name": "main" + }, + { + "do-everything": { + "phases": "", + "rrule": "RRULE:FREQ=MINUTELY;INTERVAL=1" + }, + "type": "schedule", + "name": "do-everything" + }, + { + "pump": { + "vendor": "mmeowlink.vendors.mmeowlink", + "extra": "pump.ini" + }, + "type": "device", + "name": "pump", + "extra": { + "serial": "000000", + "radio_type": "subg_rfspy", + "port": "/dev/serial/by-id/usb-Nightscout_subg_rfspy_000002-if00" + } + }, + { + "extra": { + "fields": "", + "cmd": "oref0", + "args": "" + }, + "type": "device", + "name": "oref0", + "oref0": { + "vendor": "openaps.vendors.process", + "extra": "oref0.ini" + } + }, + { + "extra": { + "fields": "glucose pumphistory isf basal_profile profile", + "cmd": "oref0", + "args": "detect-sensitivity" + }, + "type": "device", + "name": "detect-sensitivity", + "detect-sensitivity": { + "vendor": "openaps.vendors.process", + "extra": "detect-sensitivity.ini" + } + }, + { + "extra": { + "fields": "settings bg-targets insulin-sensitivities basal-profile max-iob", + "cmd": "oref0", + "args": "get-profile" + }, + "type": "device", + "name": "get-profile", + "get-profile": { + "vendor": "openaps.vendors.process", + "extra": "get-profile.ini" + } + }, + { + "type": "device", + "calculate-iob": { + "vendor": "openaps.vendors.process", + "extra": "calculate-iob.ini" + }, + "name": "calculate-iob", + "extra": { + "fields": "pump-history oref0-profile clock", + "cmd": "oref0", + "args": "calculate-iob" + } + }, + { + "determine-basal": { + "vendor": "openaps.vendors.process", + "extra": "determine-basal.ini" + }, + "type": "device", + "name": "determine-basal", + "extra": { + "fields": "oref0-iob temp-basal glucose oref0-profile", + "cmd": "oref0", + "args": "determine-basal" + } + }, + { + "type": "device", + "tz": { + "vendor": "openapscontrib.timezones", + "extra": "tz.ini" + }, + "name": "tz", + "extra": {} + }, + { + "units": { + "vendor": "openaps.vendors.units", + "extra": "units.ini" + }, + "type": "device", + "name": "units", + "extra": {} + }, + { + "extra": {}, + "type": "device", + "name": "mmhistorytools", + "mmhistorytools": { + "vendor": "openapscontrib.mmhistorytools", + "extra": "mmhistorytools.ini" + } + }, + { + "type": "device", + "cgm": { + "vendor": "openxshareble", + "extra": "cgm.ini" + }, + "name": "cgm", + "extra": { + "serial": "SM12345678" + } + }, + { + "pong": { + "vendor": "openaps.vendors.process", + "extra": "pong.ini" + }, + "type": "device", + "name": "pong", + "extra": { + "fields": "thing", + "cmd": "echo", + "args": "" + } + }, + { + "ns": { + "vendor": "openaps.vendors.process", + "extra": "ns.ini" + }, + "type": "device", + "name": "ns", + "extra": { + "fields": "oper", + "cmd": "nightscout", + "args": "ns myhost.com 143505794bdd283f7daed50f42d201b6926892c8" + } + }, + { + "pebble": { + "vendor": "pancreabble", + "extra": "pebble.ini" + }, + "type": "device", + "name": "pebble", + "extra": {} + }, + { + "DoPing": { + "then": "" + }, + "type": "trigger", + "name": "DoPing" + }, + { + "type": "trigger", + "ping": { + "then": "pong" + }, + "name": "ping" + }, + { + "pong": { + "then": "" + }, + "type": "trigger", + "name": "pong" + }, + { + "settings/settings.json": { + "device": "pump", + "use": "read_settings", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/settings.json" + }, + { + "type": "report", + "name": "settings/bg-targets-raw.json", + "settings/bg-targets-raw.json": { + "device": "pump", + "use": "read_bg_targets", + "reporter": "JSON" + } + }, + { + "settings/bg-targets.json": { + "device": "units", + "to": "mg/dL", + "use": "bg_targets", + "input": "raw-pump/bg-targets-raw.json", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/bg-targets.json" + }, + { + "settings/insulin-sensitivities-raw.json": { + "device": "pump", + "use": "read_insulin_sensitivities", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/insulin-sensitivities-raw.json" + }, + { + "type": "report", + "name": "settings/insulin-sensitivities.json", + "settings/insulin-sensitivities.json": { + "device": "units", + "to": "mg/dL", + "use": "insulin_sensitivities", + "input": "raw-pump/insulin-sensitivities-raw.json", + "reporter": "JSON" + } + }, + { + "type": "report", + "name": "settings/selected-basal-profile.json", + "settings/selected-basal-profile.json": { + "device": "pump", + "use": "read_selected_basal_profile", + "reporter": "JSON" + } + }, + { + "monitor/clock-raw.json": { + "device": "pump", + "use": "read_clock", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/clock-raw.json" + }, + { + "monitor/clock.json": { + "use": "clock", + "reporter": "JSON", + "astimezone": "False", + "date": "None", + "adjust": "missing", + "timezone": "PST", + "device": "tz", + "input": "raw-pump/clock-raw.json" + }, + "type": "report", + "name": "monitor/clock.json" + }, + { + "monitor/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/temp-basal-status.json" + }, + { + "monitor/pump-history-raw.json": { + "hours": "8.0", + "device": "pump", + "use": "iter_pump_hours", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/pump-history-raw.json" + }, + { + "monitor/pump-history.json": { + "use": "rezone", + "reporter": "JSON", + "astimezone": "False", + "date": "timestamp dateString start_at end_at created_at", + "adjust": "missing", + "timezone": "PST", + "device": "tz", + "input": "raw-pump/pump-history-raw.json" + }, + "type": "report", + "name": "monitor/pump-history.json" + }, + { + "type": "report", + "name": "model.json", + "model.json": { + "device": "pump", + "use": "model", + "reporter": "JSON" + } + }, + { + "monitor/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/reservoir.json" + }, + { + "type": "report", + "name": "monitor/status.json", + "monitor/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "monitor/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/battery.json" + }, + { + "type": "report", + "name": "oref0-monitor/profile.json", + "oref0-monitor/profile.json": { + "insulin-sensitivities": "settings/insulin-sensitivities.json", + "use": "shell", + "settings": "settings/settings.json", + "reporter": "text", + "json_default": "True", + "device": "get-profile", + "bg-targets": "settings/bg-targets.json", + "basal-profile": "settings/selected-basal-profile.json", + "max-iob": "max-iob.json", + "remainder": "" + } + }, + { + "type": "report", + "name": "oref0-monitor/iob.json", + "oref0-monitor/iob.json": { + "use": "shell", + "clock": "monitor/clock.json", + "reporter": "text", + "json_default": "True", + "pump-history": "monitor/pump-history.json", + "oref0-profile": "oref0-monitor/profile.json", + "device": "calculate-iob", + "remainder": "" + } + }, + { + "type": "report", + "name": "oref0-predict/oref0.json", + "oref0-predict/oref0.json": { + "use": "shell", + "oref0-iob": "oref0-monitor/iob.json", + "temp-basal": "monitor/temp-basal-status.json", + "reporter": "text", + "json_default": "True", + "oref0-profile": "oref0-monitor/profile.json", + "device": "determine-basal", + "remainder": "", + "glucose": "monitor/glucose.json" + } + }, + { + "type": "report", + "name": "oref0-enacted/enacted-temp-basal.json", + "oref0-enacted/enacted-temp-basal.json": { + "device": "pump", + "input": "oref0-predict/oref0.json", + "use": "set_temp_basal", + "reporter": "JSON" + } + }, + { + "monitor/glucose-raw.json": { + "count": "20", + "device": "cgm", + "use": "iter_glucose", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/glucose-raw.json" + }, + { + "type": "report", + "name": "monitor/glucose.json", + "monitor/glucose.json": { + "use": "rezone", + "reporter": "JSON", + "astimezone": "False", + "date": "timestamp dateString start_at end_at created_at", + "adjust": "missing", + "timezone": "PST", + "device": "tz", + "input": "raw-cgm/glucose-raw.json" + } + }, + { + "cgm-vendor.json": { + "device": "cgm", + "use": "GetFirmwareHeader", + "reporter": "JSON" + }, + "type": "report", + "name": "cgm-vendor.json" + }, + { + "blah.txt": { + "thing": "foo", + "use": "shell", + "reporter": "text", + "device": "pong", + "remainder": "bar", + "json_default": "False" + }, + "type": "report", + "name": "blah.txt" + }, + { + "raw-pump/settings.json": { + "device": "pump", + "use": "read_settings", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/settings.json" + }, + { + "type": "report", + "name": "raw-pump/bg-targets-raw.json", + "raw-pump/bg-targets-raw.json": { + "device": "pump", + "use": "read_bg_targets", + "reporter": "JSON" + } + }, + { + "raw-pump/insulin-sensitivities-raw.json": { + "device": "pump", + "use": "read_insulin_sensitivities", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/insulin-sensitivities-raw.json" + }, + { + "type": "report", + "name": "raw-pump/selected-basal-profile.json", + "raw-pump/selected-basal-profile.json": { + "device": "pump", + "use": "read_selected_basal_profile", + "reporter": "JSON" + } + }, + { + "raw-pump/clock-raw.json": { + "device": "pump", + "use": "read_clock", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/clock-raw.json" + }, + { + "raw-pump/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/temp-basal-status.json" + }, + { + "raw-pump/pump-history-raw.json": { + "hours": "8.0", + "device": "pump", + "use": "iter_pump_hours", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/pump-history-raw.json" + }, + { + "raw-pump/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/reservoir.json" + }, + { + "type": "report", + "name": "raw-pump/status.json", + "raw-pump/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "raw-pump/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/battery.json" + }, + { + "type": "report", + "name": "raw-cgm/glucose-raw.json", + "raw-cgm/glucose-raw.json": { + "count": "20", + "device": "cgm", + "use": "iter_glucose", + "reporter": "JSON" + } + } +] diff --git a/lib/templates/index.js b/lib/templates/index.js new file mode 100644 index 000000000..16009ad65 --- /dev/null +++ b/lib/templates/index.js @@ -0,0 +1,115 @@ + + +exports.xyzbuilder = { + foo: { + default: 'ok' + }, + baz: { + default: 'optional' + } + +}; + +var reference = require('./exported-loop.json'); + + +function oref0_reports (argv) { + var reference = require('./some-reports'); + var devs = [ ]; + if (argv.oref0) { + reference.forEach(function (item) { + if (item.type == 'report') { + // if (item[item.name].device == 'pump') { + if ([null, 'get-profile', 'calculate-iob', 'determine-basal'].indexOf(item[item.name].device) > 0) { + devs.push(item); + } + } + }); + + } + console.log(JSON.stringify(devs, ' ')); +} + +function oref0_devices (argv) { + var devs = [ ]; + if (argv.oref0) { + reference.forEach(function (item) { + if (item.type == 'device') { + if (item.extra.cmd == 'oref0') { + devs.push(item); + } + } + }); + + } + console.log(JSON.stringify(devs, ' ')); +} + +function per_type (yargs) { + return yargs + .command('oref0', 'generate oref0 devices', {oref0: {default: true}}, oref0_devices) + ; +} + +function per_shape (yargs) { + return yargs + .command('oref0-inputs', 'generate reports for oref0', {oref0: {default: true}}, oref0_reports) + .command('medtronic-pump', 'organize output from medtronic pump', {name: {default: 'pump'}}, medtronic_pump_reports) + // .command('glucose', '', { }, oref0_reports) + ; +} + +function medtronic_pump_reports (argv) { + var reference = require('./medtronic-pump-reports'); + var out = [ ]; + reference.forEach(function (item) { + if (item[item.name].device == 'pump') { + if (argv.name != 'pump') { + item[item.name].device = argv.name; + } + } + out.push(item); + }); + console.log(JSON.stringify(out)); + return out; +} + +function per_alias (yargs) { + return yargs + .command('common', 'generate common aliases', {common: {default: true}}, print_aliases) +} + +function print_aliases (argv) { + var devs = [ ]; + if (argv.common) { + reference.forEach(function (item) { + if (item.type == 'alias') { + devs.push(item); + } + }); + } + console.log(JSON.stringify(devs, ' ')); +} + + +function run ( ) { +} + +exports.builder = function (yargs) { + return yargs + .command('device ', 'generate devices', per_type, run) + .command('reports ', 'generate reports', per_shape, run) + .command('alias ', 'generate aliases', per_alias, run) + .strict( ) + // .usage('$0 mint ') + // .demand('type', 1) + // .choices('type', ['devices', 'reports', 'alias', '*']) + // .options('bazbaz', { default: 'blah' }) + ; +} + +exports.handler = function (argv) { + // console.log('args', argv); + // return argv.command( + +} diff --git a/lib/templates/medtronic-pump-reports.json b/lib/templates/medtronic-pump-reports.json new file mode 100644 index 000000000..f232030ee --- /dev/null +++ b/lib/templates/medtronic-pump-reports.json @@ -0,0 +1,233 @@ +[ + { + "type": "report", + "name": "raw-pump/bg-targets-raw.json", + "raw-pump/bg-targets-raw.json": { + "device": "pump", + "use": "read_bg_targets", + "reporter": "JSON" + } + }, + { + "settings/bg-targets.json": { + "device": "units", + "to": "mg/dL", + "use": "bg_targets", + "input": "raw-pump/bg-targets-raw.json", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/bg-targets.json" + }, + { + "raw-pump/insulin-sensitivities-raw.json": { + "device": "pump", + "use": "read_insulin_sensitivities", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/insulin-sensitivities-raw.json" + }, + { + "type": "report", + "name": "settings/insulin-sensitivities.json", + "settings/insulin-sensitivities.json": { + "device": "units", + "to": "mg/dL", + "use": "insulin_sensitivities", + "input": "raw-pump/insulin-sensitivities-raw.json", + "reporter": "JSON" + } + }, + { + "raw-pump/clock-raw.json": { + "device": "pump", + "use": "read_clock", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/clock-raw.json" + }, + { + "monitor/clock.json": { + "use": "clock", + "reporter": "JSON", + "astimezone": "False", + "date": "None", + "adjust": "missing", + "device": "tz", + "input": "raw-pump/clock-raw.json" + }, + "type": "report", + "name": "monitor/clock.json" + }, + { + "monitor/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/temp-basal-status.json" + }, + { + "type": "report", + "name": "oref0-predict/oref0.json", + "oref0-predict/oref0.json": { + "use": "shell", + "oref0-iob": "oref0-monitor/iob.json", + "temp-basal": "monitor/temp-basal-status.json", + "reporter": "text", + "json_default": "True", + "oref0-profile": "oref0-monitor/profile.json", + "device": "determine-basal", + "remainder": "", + "glucose": "monitor/glucose.json" + } + }, + { + "raw-pump/pump-history-raw.json": { + "hours": "8.0", + "device": "pump", + "use": "iter_pump_hours", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/pump-history-raw.json" + }, + { + "monitor/pump-history.json": { + "use": "rezone", + "reporter": "JSON", + "astimezone": "False", + "date": "timestamp dateString start_at end_at created_at", + "adjust": "missing", + "device": "tz", + "input": "raw-pump/pump-history-raw.json" + }, + "type": "report", + "name": "monitor/pump-history.json" + }, + { + "type": "report", + "name": "model.json", + "model.json": { + "device": "pump", + "use": "model", + "reporter": "JSON" + } + }, + { + "monitor/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/reservoir.json" + }, + { + "type": "report", + "name": "monitor/status.json", + "monitor/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "monitor/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/battery.json" + }, + { + "type": "report", + "name": "oref0-enacted/enacted-temp-basal.json", + "oref0-enacted/enacted-temp-basal.json": { + "device": "pump", + "input": "oref0-predict/oref0.json", + "use": "set_temp_basal", + "reporter": "JSON" + } + }, + { + "settings/settings.json": { + "device": "oref0", + "remainder": "copy-fresher raw-pump/settings.json", + "use": "shell", + "json_default": "True", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/settings.json" + }, + { + "raw-pump/settings.json": { + "device": "pump", + "use": "read_settings", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/settings.json" + }, + { + "type": "report", + "name": "settings/selected-basal-profile.json", + "settings/selected-basal-profile.json": { + "device": "oref0", + "remainder": "copy-fresher raw-pump/selected-basal-profile.json", + "use": "shell", + "json_default": "True", + "reporter": "JSON" + } + }, + { + "type": "report", + "name": "raw-pump/selected-basal-profile.json", + "raw-pump/selected-basal-profile.json": { + "device": "pump", + "use": "read_selected_basal_profile", + "reporter": "JSON" + } + }, + { + "raw-pump/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/temp-basal-status.json" + }, + { + "raw-pump/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/reservoir.json" + }, + { + "type": "report", + "name": "raw-pump/status.json", + "raw-pump/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "raw-pump/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/battery.json" + } +] diff --git a/lib/templates/oref0-devices.json b/lib/templates/oref0-devices.json new file mode 100644 index 000000000..ec63d730d --- /dev/null +++ b/lib/templates/oref0-devices.json @@ -0,0 +1,54 @@ +[ + { + "extra": { + "fields": "", + "cmd": "oref0", + "args": "" + }, + "type": "device", + "name": "oref0", + "oref0": { + "vendor": "openaps.vendors.process", + "extra": "oref0.ini" + } + }, + { + "extra": { + "fields": "settings bg-targets insulin-sensitivities basal-profile max-iob", + "cmd": "oref0", + "args": "get-profile" + }, + "type": "device", + "name": "get-profile", + "get-profile": { + "vendor": "openaps.vendors.process", + "extra": "get-profile.ini" + } + }, + { + "type": "device", + "calculate-iob": { + "vendor": "openaps.vendors.process", + "extra": "calculate-iob.ini" + }, + "name": "calculate-iob", + "extra": { + "fields": "pump-history oref0-profile clock", + "cmd": "oref0", + "args": "calculate-iob" + } + }, + { + "determine-basal": { + "vendor": "openaps.vendors.process", + "extra": "determine-basal.ini" + }, + "type": "device", + "name": "determine-basal", + "extra": { + "fields": "oref0-iob temp-basal glucose oref0-profile", + "cmd": "oref0", + "args": "determine-basal" + } + } +] diff --git a/lib/templates/some-reports.json b/lib/templates/some-reports.json new file mode 100644 index 000000000..ab1172a59 --- /dev/null +++ b/lib/templates/some-reports.json @@ -0,0 +1,320 @@ +[ + { + "settings/settings.json": { + "device": "oref0", + "remainder": "copy-fresher raw-pump/settings.json", + "use": "shell", + "json_default": "True", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/settings.json" + }, + { + "type": "report", + "name": "raw-pump/bg-targets-raw.json", + "raw-pump/bg-targets-raw.json": { + "device": "pump", + "use": "read_bg_targets", + "reporter": "JSON" + } + }, + { + "settings/bg-targets.json": { + "device": "units", + "to": "mg/dL", + "use": "bg_targets", + "input": "raw-pump/bg-targets-raw.json", + "reporter": "JSON" + }, + "type": "report", + "name": "settings/bg-targets.json" + }, + { + "raw-pump/insulin-sensitivities-raw.json": { + "device": "pump", + "use": "read_insulin_sensitivities", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/insulin-sensitivities-raw.json" + }, + { + "type": "report", + "name": "settings/insulin-sensitivities.json", + "settings/insulin-sensitivities.json": { + "device": "units", + "to": "mg/dL", + "use": "insulin_sensitivities", + "input": "raw-pump/insulin-sensitivities-raw.json", + "reporter": "JSON" + } + }, + { + "type": "report", + "name": "settings/selected-basal-profile.json", + "settings/selected-basal-profile.json": { + "device": "oref0", + "remainder": "copy-fresher raw-pump/selected-basal-profile.json", + "use": "shell", + "json_default": "True", + "reporter": "JSON" + } + }, + { + "raw-pump/clock-raw.json": { + "device": "pump", + "use": "read_clock", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/clock-raw.json" + }, + { + "monitor/clock.json": { + "use": "clock", + "reporter": "JSON", + "astimezone": "False", + "date": "None", + "adjust": "missing", + "timezone": "PDT", + "device": "tz", + "input": "raw-pump/clock-raw.json" + }, + "type": "report", + "name": "monitor/clock.json" + }, + { + "monitor/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/temp-basal-status.json" + }, + { + "raw-pump/pump-history-raw.json": { + "hours": "8.0", + "device": "pump", + "use": "iter_pump_hours", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/pump-history-raw.json" + }, + { + "monitor/pump-history.json": { + "use": "rezone", + "reporter": "JSON", + "astimezone": "False", + "date": "timestamp dateString start_at end_at created_at", + "adjust": "missing", + "timezone": "PDT", + "device": "tz", + "input": "raw-pump/pump-history-raw.json" + }, + "type": "report", + "name": "monitor/pump-history.json" + }, + { + "type": "report", + "name": "model.json", + "model.json": { + "device": "pump", + "use": "model", + "reporter": "JSON" + } + }, + { + "monitor/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/reservoir.json" + }, + { + "type": "report", + "name": "monitor/status.json", + "monitor/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "monitor/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/battery.json" + }, + { + "type": "report", + "name": "oref0-monitor/profile.json", + "oref0-monitor/profile.json": { + "insulin-sensitivities": "settings/insulin-sensitivities.json", + "use": "shell", + "settings": "settings/settings.json", + "reporter": "text", + "json_default": "True", + "device": "get-profile", + "bg-targets": "settings/bg-targets.json", + "basal-profile": "settings/selected-basal-profile.json", + "max-iob": "max-iob.json", + "remainder": "" + } + }, + { + "type": "report", + "name": "oref0-monitor/iob.json", + "oref0-monitor/iob.json": { + "use": "shell", + "clock": "monitor/clock.json", + "reporter": "text", + "json_default": "True", + "pump-history": "monitor/pump-history.json", + "oref0-profile": "oref0-monitor/profile.json", + "device": "calculate-iob", + "remainder": "" + } + }, + { + "type": "report", + "name": "oref0-predict/oref0.json", + "oref0-predict/oref0.json": { + "use": "shell", + "oref0-iob": "oref0-monitor/iob.json", + "temp-basal": "monitor/temp-basal-status.json", + "reporter": "text", + "json_default": "True", + "oref0-profile": "oref0-monitor/profile.json", + "device": "determine-basal", + "remainder": "", + "glucose": "monitor/glucose.json" + } + }, + { + "type": "report", + "name": "oref0-enacted/enacted-temp-basal.json", + "oref0-enacted/enacted-temp-basal.json": { + "device": "pump", + "input": "oref0-predict/oref0.json", + "use": "set_temp_basal", + "reporter": "JSON" + } + }, + { + "monitor/glucose-raw.json": { + "count": "20", + "device": "cgm", + "use": "iter_glucose", + "reporter": "JSON" + }, + "type": "report", + "name": "monitor/glucose-raw.json" + }, + { + "type": "report", + "name": "monitor/glucose.json", + "monitor/glucose.json": { + "use": "rezone", + "reporter": "JSON", + "astimezone": "False", + "date": "timestamp dateString start_at end_at created_at", + "adjust": "missing", + "timezone": "PDT", + "device": "tz", + "input": "raw-cgm/glucose-raw.json" + } + }, + { + "cgm-vendor.json": { + "device": "cgm", + "use": "GetFirmwareHeader", + "reporter": "JSON" + }, + "type": "report", + "name": "cgm-vendor.json" + }, + { + "blah.txt": { + "thing": "foo", + "use": "shell", + "reporter": "text", + "device": "pong", + "remainder": "bar", + "json_default": "False" + }, + "type": "report", + "name": "blah.txt" + }, + { + "raw-pump/settings.json": { + "device": "pump", + "use": "read_settings", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/settings.json" + }, + { + "type": "report", + "name": "raw-pump/selected-basal-profile.json", + "raw-pump/selected-basal-profile.json": { + "device": "pump", + "use": "read_selected_basal_profile", + "reporter": "JSON" + } + }, + { + "raw-pump/temp-basal-status.json": { + "device": "pump", + "use": "read_temp_basal", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/temp-basal-status.json" + }, + { + "raw-pump/reservoir.json": { + "device": "pump", + "use": "reservoir", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/reservoir.json" + }, + { + "type": "report", + "name": "raw-pump/status.json", + "raw-pump/status.json": { + "device": "pump", + "use": "read_status", + "reporter": "JSON" + } + }, + { + "raw-pump/battery.json": { + "device": "pump", + "use": "read_battery_status", + "reporter": "JSON" + }, + "type": "report", + "name": "raw-pump/battery.json" + }, + { + "type": "report", + "name": "raw-cgm/glucose-raw.json", + "raw-cgm/glucose-raw.json": { + "count": "20", + "device": "cgm", + "use": "iter_glucose", + "reporter": "JSON" + } + } +] diff --git a/lib/with-raw-glucose.js b/lib/with-raw-glucose.js new file mode 100644 index 000000000..0a85592c0 --- /dev/null +++ b/lib/with-raw-glucose.js @@ -0,0 +1,42 @@ +'use strict'; + +function cleanCal (cal) { + var clean = { + scale: parseFloat(cal.scale) || 0 + , intercept: parseFloat(cal.intercept) || 0 + , slope: parseFloat(cal.slope) || 0 + }; + + clean.valid = ! (clean.slope === 0 || clean.unfiltered === 0 || clean.scale === 0); + + return clean; +} + +module.exports = function withRawGlucose (entry, cals, maxRaw) { + maxRaw = maxRaw || 150; + + var egv = entry.glucose || entry.sgv || 0; + + entry.unfiltered = parseInt(entry.unfiltered) || 0; + entry.filtered = parseInt(entry.filtered) || 0; + + //TODO: add time check, but how recent should it be? + //TODO: currently assuming the first is the best (and that there is probably just 1 cal) + var cal = cals && cals.length > 0 && cleanCal(cals[0]); + + if (cal && cal.valid) { + if (cal.filtered === 0 || egv < 40) { + entry.raw = Math.round(cal.scale * (entry.unfiltered - cal.intercept) / cal.slope); + } else { + var ratio = cal.scale * (entry.filtered - cal.intercept) / cal.slope / egv; + entry.raw = Math.round(cal.scale * (entry.unfiltered - cal.intercept) / cal.slope / ratio); + } + + if (entry.raw && egv < 40 && entry.raw < maxRaw) { + entry.glucose = entry.raw; + entry.fromRaw = true; + } + } + + return entry; +}; \ No newline at end of file diff --git a/package.json b/package.json index a9786ae4e..99d938949 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oref0", - "version": "0.1.2", + "version": "0.1.4", "description": "openaps oref0 reference implementation of the reference design", "scripts": { "test": "make test" @@ -20,12 +20,16 @@ "bin": { "oref0-calculate-iob": "./bin/oref0-calculate-iob.js", "oref0-find-insulin-uses": "./bin/oref0-find-insulin-uses.js", + "oref0-detect-sensitivity": "./bin/oref0-detect-sensitivity.js", "oref0-determine-basal": "./bin/oref0-determine-basal.js", + "oref0-meal": "./bin/oref0-meal.js", "oref0-normalize-temps": "./bin/oref0-normalize-temps.js", "send-tempbasal-Azure": "./bin/send-tempbasal-Azure.js", + "oref0-fix-git-corruption": "bin/oref0-fix-git-corruption.sh", "oref0-get-profile": "./bin/oref0-get-profile.js", "oref0-mint-max-iob": "./bin/oref0-mint-max-iob.sh", "oref0-ifttt-notify": "./bin/oref0-ifttt-notify", + "oref0-raw": "./bin/oref0-raw.js", "oref0-reset-usb": "bin/oref0-reset-usb.sh", "oref0-reset-git": "bin/oref0-reset-git.sh", "mm-format-ns-glucose": "./bin/mm-format-ns-glucose.sh", @@ -40,12 +44,16 @@ "ns-status": "./bin/ns-status.js", "nightscout": "./bin/nightscout.sh", "ns-dedupe-treatments": "./bin/ns-dedupe-treatments.sh", + "oref0-html": "./bin/oref0-html.js", + "oref0-template": "./bin/oref0-template.js", + "oref0-copy-fresher": "./bin/oref0-copy-fresher", "oref0-pebble": "./bin/oref0-pebble.js" }, "homepage": "https://github.com/openaps/oref0", "dependencies": { "share2nightscout-bridge": "^0.1.5", - "timezone": "0.0.47" + "timezone": "0.0.47", + "yargs": "~4.3.2" }, "devDependencies": { "mocha": "^2.3.3", diff --git a/tests/determine-basal.test.js b/tests/determine-basal.test.js index f050882db..a1707ccd3 100644 --- a/tests/determine-basal.test.js +++ b/tests/determine-basal.test.js @@ -1,6 +1,6 @@ 'use strict'; -require('should'); +var should = require('should'); @@ -13,70 +13,68 @@ describe('determine-basal', function ( ) { // standard initial conditions for all determine-basal test cases unless overridden var glucose_status = {"delta":0,"glucose":115,"avgdelta":0}; var currenttemp = {"duration":0,"rate":0,"temp":"absolute"}; - var iob_data = {"iob":0,"activity":0,"bolusiob":0}; - var profile = {"max_iob":1.5,"dia":3,"type":"current","current_basal":0.9,"max_daily_basal":1.3,"max_basal":3.5,"max_bg":120,"min_bg":110,"sens":40, "target_bg":110}; + var iob_data = {"iob":0,"activity":0,"bolussnooze":0}; + var profile = {"max_iob":2.5,"dia":3,"type":"current","current_basal":0.9,"max_daily_basal":1.3,"max_basal":3.5,"max_bg":120,"min_bg":110,"sens":40,"target_bg":110,"carb_ratio":10}; + var meal_data = {}; - it('should do nothing when in range w/o IOB', function () { - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - (typeof output.rate).should.equal('undefined'); - (typeof output.duration).should.equal('undefined'); - output.reason.should.match(/in range/); - }); - - it('should set current temp when in range w/o IOB with Offline set', function () { - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, 'Offline',setTempBasal); - output.rate.should.equal(0.9); - output.duration.should.equal(30); - output.reason.should.match(/in range.*setting current basal/); - }); - it('should cancel any temp when in range w/o IOB', function () { var currenttemp = {"duration":30,"rate":0,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/in range.*cancel/); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/in range.*/); }); // low glucose suspend test cases it('should temp to 0 when low w/o IOB', function () { var glucose_status = {"delta":-5,"glucose":75,"avgdelta":-5}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 75<80/); }); - it('should do nothing when low and rising w/o IOB', function () { - var glucose_status = {"delta":5,"glucose":75,"avgdelta":5}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + it('should not extend temp to 0 when >20m left', function () { + var currenttemp = {"duration":27,"rate":0,"temp":"absolute"}; + var glucose_status = {"delta":-5,"glucose":75,"avgdelta":-5}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); (typeof output.rate).should.equal('undefined'); (typeof output.duration).should.equal('undefined'); - output.reason.should.match(/75<80.*no high-temp/); + }); + + it('should do nothing when low and rising w/o IOB', function () { + var glucose_status = {"delta":5,"glucose":75,"avgdelta":5}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/75<80.*setting current basal/); }); it('should do nothing when low and rising w/ negative IOB', function () { var glucose_status = {"delta":5,"glucose":75,"avgdelta":5}; - var iob_data = {"iob":-1,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - (typeof output.rate).should.equal('undefined'); - (typeof output.duration).should.equal('undefined'); - output.reason.should.match(/75<80.*no high-temp/); + var iob_data = {"iob":-1,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/75<80.*setting current basal/); }); it('should do nothing on large uptick even if avgdelta is still negative', function () { var glucose_status = {"delta":2,"glucose":75,"avgdelta":-2}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - (typeof output.rate).should.equal('undefined'); - (typeof output.duration).should.equal('undefined'); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); output.reason.should.match(/BG 75<80/); }); it('should temp to 0 when rising slower than BGI', function () { var glucose_status = {"delta":1,"glucose":75,"avgdelta":1}; - var iob_data = {"iob":-1,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":-1,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 75<80/); @@ -84,8 +82,8 @@ describe('determine-basal', function ( ) { it('should temp to 0 when low and falling, regardless of BGI', function () { var glucose_status = {"delta":-1,"glucose":75,"avgdelta":-1}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0.5}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0.5}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 75<80/); @@ -94,27 +92,30 @@ describe('determine-basal', function ( ) { it('should cancel high-temp when low and rising faster than BGI', function () { var currenttemp = {"duration":20,"rate":2,"temp":"absolute"}; var glucose_status = {"delta":5,"glucose":75,"avgdelta":5}; - var iob_data = {"iob":-1,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/BG 75<80, avg delta .*, cancel high temp/); + var iob_data = {"iob":-1,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/BG 75<80, avg delta .*/); }); - it('should cancel low-temp eventualBG is higher then max_bg', function () { - var currenttemp = {"duration":20,"rate":0.9,"temp":"absolute"}; + it('should cancel low-temp when eventualBG is higher then max_bg', function () { + var currenttemp = {"duration":20,"rate":0,"temp":"absolute"}; var glucose_status = {"delta":5,"glucose":75,"avgdelta":5}; - var iob_data = {"iob":-1,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/BG 75<80, avg delta .*, cancel low temp/); + var iob_data = {"iob":-1,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/BG 75<80, avg delta .*/); }); it('should high-temp when > 80-ish and rising w/ lots of negative IOB', function () { var glucose_status = {"delta":5,"glucose":85,"avgdelta":5}; - var iob_data = {"iob":-1,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":-1,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(1); output.duration.should.equal(30); output.reason.should.match(/no temp, setting/); @@ -122,26 +123,25 @@ describe('determine-basal', function ( ) { it('should high-temp when > 180-ish and rising but not more then maxSafeBasal', function () { var glucose_status = {"delta":5,"glucose":185,"avgdelta":5}; - var iob_data = {"iob":0,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - - output.reason.should.match(/max_iob .*, adj. req. rate:.* to maxSafeBasal:.*, no temp, setting/); + var iob_data = {"iob":0,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.reason.should.match(/.*, adj. req. rate:.* to maxSafeBasal:.*, no temp, setting/); }); it('should reduce high-temp when schedule would be above max', function () { var glucose_status = {"delta":5,"glucose":145,"avgdelta":5}; var currenttemp = {"duration":160,"rate":1.9,"temp":"absolute"}; - var iob_data = {"iob":0,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.duration.should.equal(30); - output.reason.should.match(/.*mins .* = .* > req .*/); + output.reason.should.match(/.*m.* = .* > req .*/); }); it('should continue high-temp when required ~= temp running', function () { var glucose_status = {"delta":5,"glucose":145,"avgdelta":5}; var currenttemp = {"duration":30,"rate":3.1,"temp":"absolute"}; - var iob_data = {"iob":0,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); (typeof output.rate).should.equal('undefined'); (typeof output.duration).should.equal('undefined'); output.reason.should.match(/Eventual BG .*>.*, temp .* >~ req /); @@ -150,25 +150,25 @@ describe('determine-basal', function ( ) { it('should set high-temp when required running temp is low', function () { var glucose_status = {"delta":5,"glucose":145,"avgdelta":5}; var currenttemp = {"duration":30,"rate":1.1,"temp":"absolute"}; - var iob_data = {"iob":0,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.duration.should.equal(30); output.reason.should.match(/Eventual BG .*>.*, temp/); }); it('should stop high-temp when iob is near max_iob.', function () { var glucose_status = {"delta":5,"glucose":485,"avgdelta":5}; - var iob_data = {"iob":3.5,"activity":0.05,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/basal_iob .* > max_iob .*/); + var iob_data = {"iob":3.5,"activity":0.05,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/basaliob .* > max_iob .*/); }); it('should temp to 0 when LOW w/ positive IOB', function () { var glucose_status = {"delta":0,"glucose":39,"avgdelta":0}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0.5}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0.5}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 39<80/); @@ -176,8 +176,8 @@ describe('determine-basal', function ( ) { it('should temp to 0 when LOW w/ negative IOB', function () { var glucose_status = {"delta":0,"glucose":39,"avgdelta":0}; - var iob_data = {"iob":-2.5,"activity":-0.03,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":-2.5,"activity":-0.03,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 39<80/); @@ -185,8 +185,8 @@ describe('determine-basal', function ( ) { it('should temp to 0 when LOW w/ no IOB', function () { var glucose_status = {"delta":0,"glucose":39,"avgdelta":0}; - var iob_data = {"iob":0,"activity":0,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0,"activity":0,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.equal(0); output.duration.should.equal(30); output.reason.should.match(/BG 39<80/); @@ -198,47 +198,47 @@ describe('determine-basal', function ( ) { it('should low-temp when eventualBG < min_bg', function () { var glucose_status = {"delta":-3,"glucose":110,"avgdelta":-1}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.below(0.8); output.duration.should.equal(30); - output.reason.should.match(/Eventual BG .*<110, no temp, setting .*/); + output.reason.should.match(/Eventual BG .*<110.*setting .*/); }); it('should low-temp when eventualBG < min_bg with delta > exp. delta', function () { var glucose_status = {"delta":-5,"glucose":115,"avgdelta":-6}; - var iob_data = {"iob":2,"activity":0.05,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":2,"activity":0.05,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); //console.log(output); output.rate.should.be.below(0.2); output.duration.should.equal(30); - output.reason.should.match(/Eventual BG .*<110, no temp, setting .*/); + output.reason.should.match(/Eventual BG .*<110.*setting .*/); }); it('should low-temp when eventualBG < min_bg with delta > exp. delta', function () { var glucose_status = {"delta":-2,"glucose":156,"avgdelta":-1.33}; - var iob_data = {"iob":3.51,"activity":0.06,"bolusiob":0.08}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - console.log(output); + var iob_data = {"iob":3.51,"activity":0.06,"bolussnooze":0.08}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.below(0.8); output.duration.should.equal(30); - output.reason.should.match(/Eventual BG .*<110, no temp, setting .*/); + output.reason.should.match(/Eventual BG .*<110.*setting .*/); }); it('should low-temp much less when eventualBG < min_bg with delta barely negative', function () { var glucose_status = {"delta":-1,"glucose":115,"avgdelta":-1}; - var iob_data = {"iob":2,"activity":0.05,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - //console.log(output); - output.rate.should.be.above(0.5); + var iob_data = {"iob":2,"activity":0.05,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.be.above(0.3); output.rate.should.be.below(0.8); output.duration.should.equal(30); - output.reason.should.match(/Eventual BG .*<110, no temp, setting .*/); + output.reason.should.match(/Eventual BG .*<110.*setting .*/); }); - it('should do nothing when eventualBG < min_bg but low temp in progress', function () { - var glucose_status = {"delta":-3,"glucose":110,"avgdelta":-1}; - var currenttemp = {"duration":20,"rate":0.0,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + it('should do nothing when eventualBG < min_bg but appropriate low temp in progress', function () { + var glucose_status = {"delta":-1,"glucose":110,"avgdelta":-1}; + var currenttemp = {"duration":20,"rate":0.25,"temp":"absolute"}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); (typeof output.rate).should.equal('undefined'); (typeof output.duration).should.equal('undefined'); output.reason.should.match(/Eventual BG .*<110, temp .*/); @@ -247,85 +247,95 @@ describe('determine-basal', function ( ) { it('should cancel low-temp when lowish and avg.delta rising faster than BGI', function () { var currenttemp = {"duration":20,"rate":0.5,"temp":"absolute"}; var glucose_status = {"delta":3,"glucose":85,"avgdelta":3}; - var iob_data = {"iob":-0.5,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/Eventual BG.*<.*but Avg. Delta.*> Exp.*; cancel/); + var iob_data = {"iob":-0.7,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + //output.reason.should.match(/.*; cancel/); }); it('should cancel low-temp when lowish and delta rising faster than BGI', function () { var currenttemp = {"duration":20,"rate":0.5,"temp":"absolute"}; var glucose_status = {"delta":3,"glucose":85,"avgdelta":3}; - var iob_data = {"iob":-0.5,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/Eventual BG.*<.*but.*Delta.*> Exp.*; cancel/); + var iob_data = {"iob":-0.7,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); }); - it('should do nothing when lowish and delta rising faster than BGI', function () { + it('should set current basal as temp when lowish and delta rising faster than BGI', function () { var currenttemp = {"duration":0,"rate":0.5,"temp":"absolute"}; var glucose_status = {"delta":3,"glucose":85,"avgdelta":3}; - var iob_data = {"iob":-0.5,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.reason.should.match(/Eventual BG.*<.*but.*Delta.*> Exp.*; no temp to cancel/); + var iob_data = {"iob":-0.7,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //(typeof output.rate).should.equal('undefined'); + //(typeof output.duration).should.equal('undefined'); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/in range.*setting current basal/); }); + it('should low-temp when low and rising slower than BGI', function () { var glucose_status = {"delta":1,"glucose":85,"avgdelta":1}; - var iob_data = {"iob":-0.5,"activity":-0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - //console.log(output); + var iob_data = {"iob":-0.5,"activity":-0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.below(0.8); output.duration.should.equal(30); - output.reason.should.match(/no temp, setting/); + output.reason.should.match(/setting/); }); // high eventualBG it('should high-temp when eventualBG > max_bg', function () { var glucose_status = {"delta":+3,"glucose":120,"avgdelta":+1}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(1); output.duration.should.equal(30); - output.reason.should.match(/Eventual BG .*>120/); + output.reason.should.match(/Eventual BG .*>=120/); }); it('should cancel high-temp when high and avg. delta falling faster than BGI', function () { var currenttemp = {"duration":20,"rate":2,"temp":"absolute"}; var glucose_status = {"delta":-5,"glucose":175,"avgdelta":-5}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/Eventual BG.*>.*but Avg. Delta.*< Exp.*; cancel/); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + //output.reason.should.match(/.*; cancel/); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + output.reason.should.match(/Eventual BG.*>.*but Avg. Delta.*< Exp.*/); }); it('should cancel high-temp when high and delta falling faster than BGI', function () { var currenttemp = {"duration":20,"rate":2,"temp":"absolute"}; var glucose_status = {"delta":-5,"glucose":175,"avgdelta":-4}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); - output.reason.should.match(/Eventual BG.*>.*but.*Delta.*< Exp.*; cancel/); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/Eventual BG.*>.*but.*Delta.*< Exp.*/); }); - it('should do nothing when not temp and high and delta falling faster than BGI', function () { + it('should do nothing when no temp and high and delta falling faster than BGI', function () { var currenttemp = {"duration":0,"rate":0,"temp":"absolute"}; var glucose_status = {"delta":-5,"glucose":175,"avgdelta":-4}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - (typeof output.rate).should.equal('undefined'); - (typeof output.duration).should.equal('undefined'); - output.reason.should.match(/Eventual BG.*>.*but.*Delta.*< Exp.*; no temp to cancel/); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //(typeof output.rate).should.equal('undefined'); + //(typeof output.duration).should.equal('undefined'); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + output.reason.should.match(/Eventual BG.*>.*but.*Delta.*< Exp.*/); }); it('should high-temp when high and falling slower than BGI', function () { var glucose_status = {"delta":-1,"glucose":175,"avgdelta":-1}; - var iob_data = {"iob":1,"activity":0.01,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":1,"activity":0.01,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(1); output.duration.should.equal(30); output.reason.should.match(/no temp, setting/); @@ -333,8 +343,8 @@ describe('determine-basal', function ( ) { it('should high-temp when high and falling slowly with low insulin activity', function () { var glucose_status = {"delta":-1,"glucose":300,"avgdelta":-1}; - var iob_data = {"iob":0.5,"activity":0.005,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0.5,"activity":0.005,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(2.5); output.duration.should.equal(30); output.reason.should.match(/no temp, setting/); @@ -342,9 +352,8 @@ describe('determine-basal', function ( ) { it('should set lower high-temp when high and falling almost fast enough with low insulin activity', function () { var glucose_status = {"delta":-5,"glucose":300,"avgdelta":-5}; - var iob_data = {"iob":0.5,"activity":0.005,"bolusiob":0}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - //console.log(output); + var iob_data = {"iob":0.5,"activity":0.005,"bolussnooze":0}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(1); output.rate.should.be.below(2); output.duration.should.equal(30); @@ -353,9 +362,9 @@ describe('determine-basal', function ( ) { it('should reduce high-temp when high and falling almost fast enough with low insulin activity', function () { var glucose_status = {"delta":-5,"glucose":300,"avgdelta":-5}; - var iob_data = {"iob":0.5,"activity":0.005,"bolusiob":0}; + var iob_data = {"iob":0.5,"activity":0.005,"bolussnooze":0}; var currenttemp = {"duration":30,"rate":2.5,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(1); output.rate.should.be.below(2); output.duration.should.equal(30); @@ -368,67 +377,86 @@ describe('determine-basal', function ( ) { }); it('should bg be < 30 (Dexcom is in ???) return error', function () { - var result = determine_basal({glucose:18},undefined, undefined, {"current_basal":0.0}, undefined,setTempBasal); + var result = determine_basal({glucose:18},undefined, undefined, {"current_basal":0.0}, undefined, meal_data, setTempBasal); result.error.should.equal('CGM is calibrating or in ??? state'); }); it('profile should contain min_bg,max_bg or target_bg', function () { - var result = determine_basal({glucose:100},undefined, undefined, {"current_basal":0.0}, undefined,setTempBasal); + var result = determine_basal({glucose:100},undefined, undefined, {"current_basal":0.0}, undefined, meal_data, setTempBasal); result.error.should.equal('Error: could not determine target_bg'); }); it('iob_data should not be undefined', function () { - var result = determine_basal({glucose:100},undefined, undefined, {"current_basal":0.0, "target_bg":100}, undefined,setTempBasal); + var result = determine_basal({glucose:100},undefined, undefined, {"current_basal":0.0, "target_bg":100}, undefined, meal_data, setTempBasal); result.error.should.equal('Error: iob_data undefined'); }); - it('iob_data should contain activity, iob, bolusiob', function () { - var result = determine_basal({glucose:100}, undefined,{"activity":0}, {"current_basal":0.0, "target_bg":100}, undefined,setTempBasal); + it('iob_data should contain activity, iob, bolussnooze', function () { + var result = determine_basal({glucose:100}, undefined,{"activity":0}, {"current_basal":0.0, "target_bg":100}, undefined, meal_data, setTempBasal); result.error.should.equal('Error: iob_data missing some property'); }); /* it('should return error eventualBG if something went wrong', function () { - var result = determine_basal({glucose:100}, undefined,{"activity":0, "iob":0,"bolusiob":0}, {"current_basal":0.0, "target_bg":100, "sens":NaN}, undefined,setTempBasal); + var result = determine_basal({glucose:100}, undefined,{"activity":0, "iob":0,"bolussnooze":0}, {"current_basal":0.0, "target_bg":100, "sens":NaN}, undefined, meal_data, setTempBasal); result.error.should.equal('Error: could not calculate eventualBG'); }); */ // meal assist / bolus snooze // right after 20g 1U meal bolus - it('should do nothing when low and rising after meal bolus', function () { + it('should set current basal as temp when low and rising after meal bolus', function () { var glucose_status = {"delta":1,"glucose":80,"avgdelta":1}; - var iob_data = {"iob":0.5,"activity":-0.01,"bolusiob":1}; - var meal_data = {"dia_carbs":20,"dia_bolused":1}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0.5,"activity":-0.01,"bolussnooze":1,"basaliob":-0.5}; + var meal_data = {"carbs":20,"boluses":1}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + }); + + it('should do nothing when requested temp already running with >15m left', function () { + var glucose_status = {"delta":-2,"glucose":121,"avgdelta":-1.333}; + var iob_data = {"iob":3.983,"activity":0.0255,"bolussnooze":2.58,"basaliob":0.384,"netbasalinsulin":0.3,"hightempinsulin":0.7}; + var meal_data = {"carbs":65,"boluses":4}; + var currenttemp = {"duration":29,"rate":1.3,"temp":"absolute"}; + var profile = {"max_iob":3,"type":"current","dia":3,"current_basal":1.3,"max_daily_basal":1.3,"max_basal":3.5,"min_bg":105,"max_bg ":105,"sens":40,"carb_ratio":10} + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); (typeof output.rate).should.equal('undefined'); (typeof output.duration).should.equal('undefined'); }); - + it('should cancel high temp when low and dropping after meal bolus', function () { var glucose_status = {"delta":-1,"glucose":80,"avgdelta":1}; - var iob_data = {"iob":0.5,"activity":-0.01,"bolusiob":1}; + var iob_data = {"iob":0.5,"activity":-0.01,"bolussnooze":1,"basaliob":-0.5}; var currenttemp = {"duration":20,"rate":2,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + output.rate.should.be.below(1.0); + output.duration.should.equal(30); }); it('should cancel low temp when low and rising after meal bolus', function () { var glucose_status = {"delta":1,"glucose":80,"avgdelta":1}; - var iob_data = {"iob":0.5,"activity":-0.01,"bolusiob":1}; + var iob_data = {"iob":0.5,"activity":-0.01,"bolussnooze":1,"basaliob":-0.5}; var currenttemp = {"duration":20,"rate":0,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); - output.rate.should.equal(0); - output.duration.should.equal(0); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + //output.rate.should.equal(0); + //output.duration.should.equal(0); }); // 40m after 20g 1U meal bolus it('should high-temp aggressively when 120 and rising after meal bolus', function () { var glucose_status = {"delta":10,"glucose":120,"avgdelta":10}; - var iob_data = {"iob":0.4,"activity":0,"bolusiob":0.7}; - var meal_data = {"dia_carbs":20,"dia_bolused":1}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var iob_data = {"iob":0.4,"activity":0,"bolussnooze":0.7,"basaliob":-0.3}; + var meal_data = {"carbs":20,"boluses":1}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.above(1.8); output.duration.should.equal(30); }); @@ -436,10 +464,10 @@ describe('determine-basal', function ( ) { // 60m after 20g 1U meal bolus it('should high-temp aggressively when 150 and rising after meal bolus', function () { var glucose_status = {"delta":3,"glucose":150,"avgdelta":5}; - var iob_data = {"iob":0.5,"activity":0.01,"bolusiob":0.6}; - var meal_data = {"dia_carbs":20,"dia_bolused":1}; + var iob_data = {"iob":0.5,"activity":0.01,"bolussnooze":0.6,"basaliob":-0.1}; + var meal_data = {"carbs":20,"boluses":1}; var currenttemp = {"duration":10,"rate":2,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.above(2.2); output.duration.should.equal(30); }); @@ -447,57 +475,107 @@ describe('determine-basal', function ( ) { // 75m after 20g 1U meal bolus it('should reduce high-temp when 160 and dropping slowly after meal bolus', function () { var glucose_status = {"delta":-3,"glucose":160,"avgdelta":0}; - var iob_data = {"iob":0.9,"activity":0.02,"bolusiob":0.5}; - var meal_data = {"dia_carbs":20,"dia_bolused":1}; + var iob_data = {"iob":0.9,"activity":0.02,"bolussnooze":0.5,"basaliob":0.4}; + var meal_data = {"carbs":20,"boluses":1}; var currenttemp = {"duration":30,"rate":2.5,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); output.rate.should.be.below(1.5); }); -/* // right after 120g 6U meal bolus it('should high-temp when 120 and rising after meal bolus', function () { - var glucose_status = {"delta":1,"glucose":120,"avgdelta":1}; - var iob_data = {"iob":6,"activity":0,"bolusiob":6}; - var meal_data = {"dia_carbs":120,"dia_bolused":6}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var glucose_status = {"delta":4,"glucose":120,"avgdelta":4}; + var iob_data = {"iob":6,"activity":0,"bolussnooze":6,"basaliob":0,"hightempinsulin":0}; + var meal_data = {"carbs":120,"boluses":6}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.above(1); output.duration.should.equal(30); }); // after 120g 6U meal bolus it('should high-temp when 140 and rising after meal bolus', function () { - var glucose_status = {"delta":1,"glucose":140,"avgdelta":1}; - //TODO: figure out how to track basal_iob vs. net_iob - var iob_data = {"iob":6.5,"activity":0.01,"bolusiob":5.5}; - var meal_data = {"dia_carbs":120,"dia_bolused":6}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var glucose_status = {"delta":4,"glucose":140,"avgdelta":4}; + var iob_data = {"iob":6.5,"activity":0.01,"bolussnooze":5.5,"basaliob":1,"hightempinsulin":1}; + var meal_data = {"carbs":120,"boluses":6}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.above(1); output.duration.should.equal(30); }); // after 120g 6U meal bolus it('should high-temp when 160 and rising after meal bolus', function () { - var glucose_status = {"delta":1,"glucose":160,"avgdelta":1}; - //TODO: figure out how to track basal_iob vs. net_iob - var iob_data = {"iob":7.0,"activity":0.02,"bolusiob":5}; - var meal_data = {"dia_carbs":120,"dia_bolused":6}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var glucose_status = {"delta":4,"glucose":160,"avgdelta":4}; + var iob_data = {"iob":7.0,"activity":0.02,"bolussnooze":5.0,"basaliob":2,"hightempinsulin":2}; + var meal_data = {"carbs":120,"boluses":6}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); output.rate.should.be.above(1); output.duration.should.equal(30); }); -*/ + + // after 120g 6U meal bolus + it('should not high-temp when 160 and rising slowly after meal bolus', function () { + var glucose_status = {"delta":1,"glucose":160,"avgdelta":1}; + var iob_data = {"iob":7.0,"activity":0.02,"bolussnooze":5.0,"basaliob":2}; + var meal_data = {"carbs":120,"boluses":6}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + //should.not.exist(output.rate); + //should.not.exist(output.duration); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + }); // after 120g 6U meal bolus it('should cancel temp when 160 and falling after meal bolus', function () { var glucose_status = {"delta":-1,"glucose":160,"avgdelta":-1}; - //TODO: figure out how to track basal_iob vs. net_iob - var iob_data = {"iob":7.0,"activity":0.03,"bolusiob":5}; - var meal_data = {"dia_carbs":120,"dia_bolused":6}; + var iob_data = {"iob":7.0,"activity":0.03,"bolussnooze":5.0,"basaliob":2}; + var meal_data = {"carbs":120,"boluses":6}; var currenttemp = {"duration":15,"rate":2.5,"temp":"absolute"}; - var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined,setTempBasal); + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + //output.rate.should.equal(0); + //output.duration.should.equal(0); + }); + + it('should not set temp when boluses + basal IOB cover meal carbs', function () { + var glucose_status = {"delta":1,"glucose":160,"avgdelta":1}; + var iob_data = {"iob":7.0,"activity":0.02,"bolussnooze":4.0,"basaliob":3}; + var meal_data = {"carbs":120,"boluses":11}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + output.rate.should.equal(0.9); + output.duration.should.equal(30); + //(typeof output.rate).should.equal('undefined'); + //(typeof output.duration).should.equal('undefined'); + }); + + it('should not set temp when boluses + basal IOB cover meal carbs', function () { + var meal_data = {"carbs":15,"boluses":2} + var glucose_status = {"delta":3,"glucose":200,"avgdelta":8.667} + var currenttemp = {"duration":3,"rate":3.5,"temp":"absolute"} + var iob_data = {"iob":2.701,"activity":0.0107,"bolussnooze":0.866,"basaliob":1.013,"netbasalinsulin":1.1,"hightempinsulin":1.8} + var profile_data = {"max_iob":3,"type":"current","dia":3,"current_basal":0.9,"max_daily_basal":1.3,"max_basal":3.5,"min_bg":105,"max_bg":105,"sens":40,"carb_ratio":10} + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, undefined, meal_data, setTempBasal); + //console.log(output); + //output.rate.should.equal(0.9); + //output.duration.should.equal(30); + //(typeof output.rate).should.equal('undefined'); + //(typeof output.duration).should.equal('undefined'); + }); + + + it('should temp to zero with double sensitivity adjustment', function () { + //var glucose_status = {"delta":1,"glucose":160,"avgdelta":1}; + var iob_data = {"iob":0.5,"activity":0.001,"bolussnooze":0.0,"basaliob":0.5}; + var autosens_data = {"ratio":0.5}; + var output = determine_basal(glucose_status, currenttemp, iob_data, profile, autosens_data, meal_data, setTempBasal); + //console.log(output); output.rate.should.equal(0); - output.duration.should.equal(0); + output.duration.should.equal(30); }); }); diff --git a/tests/iob.test.js b/tests/iob.test.js index c5644c7ca..d678b2e51 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -22,13 +22,13 @@ describe('IOB', function ( ) { var rightAfterBolus = require('../lib/iob')(inputs); rightAfterBolus.iob.should.equal(1); - rightAfterBolus.bolusiob.should.equal(1); + rightAfterBolus.bolussnooze.should.equal(1); var hourLaterInputs = inputs; hourLaterInputs.clock = new Date(now + (60 * 60 * 1000)).toISOString(); var hourLater = require('../lib/iob')(hourLaterInputs); hourLater.iob.should.be.lessThan(1); - hourLater.bolusiob.should.be.lessThan(.5); + hourLater.bolussnooze.should.be.lessThan(.5); hourLater.iob.should.be.greaterThan(0); var afterDIAInputs = inputs; @@ -36,7 +36,7 @@ describe('IOB', function ( ) { var afterDIA = require('../lib/iob')(afterDIAInputs); afterDIA.iob.should.equal(0); - afterDIA.bolusiob.should.equal(0); + afterDIA.bolussnooze.should.equal(0); }); @@ -58,13 +58,13 @@ describe('IOB', function ( ) { var rightAfterBolus = require('../lib/iob')(inputs); rightAfterBolus.iob.should.equal(1); - rightAfterBolus.bolusiob.should.equal(1); + rightAfterBolus.bolussnooze.should.equal(1); var hourLaterInputs = inputs; hourLaterInputs.clock = new Date(now + (60 * 60 * 1000)).toISOString(); var hourLater = require('../lib/iob')(hourLaterInputs); hourLater.iob.should.be.lessThan(1); - hourLater.bolusiob.should.be.lessThan(.5); + hourLater.bolussnooze.should.be.lessThan(.5); hourLater.iob.should.be.greaterThan(0); var after3hInputs = inputs; diff --git a/tests/set-temp-basal.test.js b/tests/set-temp-basal.test.js index 3f8cd7413..b5aec788e 100644 --- a/tests/set-temp-basal.test.js +++ b/tests/set-temp-basal.test.js @@ -34,12 +34,6 @@ describe('setTempBasal', function ( ) { requestedTemp.duration.should.equal(30); }); - it('should set current_basal as temp on requestedTemp if offline', function () { - var requestedTemp = setTempBasal(0, 0, profile, rt, "Offline"); - requestedTemp.rate.should.equal(0.8); - requestedTemp.duration.should.equal(30); - }); - it('should limit high temp to 3 * max_daily_basal', function () { var profile = { "current_basal":1.0,"max_daily_basal":1.3,"max_basal":10.0 }; var requestedTemp = setTempBasal(6, 30, profile, rt); @@ -60,4 +54,4 @@ describe('setTempBasal', function ( ) { requestedTemp.duration.should.equal(30); }); -}); \ No newline at end of file +}); diff --git a/tests/with-raw-glucose.test.js b/tests/with-raw-glucose.test.js new file mode 100644 index 000000000..68db98ba3 --- /dev/null +++ b/tests/with-raw-glucose.test.js @@ -0,0 +1,54 @@ +'use strict'; + +var should = require('should'); +var withRawGlucose = require('../lib/with-raw-glucose'); + +var cals = [{ + scale: 1 + , intercept: 25717.82377004309 + , slope: 766.895601715918 +}]; + +describe('IOB', function ( ) { + it('should add raw glucose and not mess with real glucose', function ( ) { + var entry = {unfiltered: 113680, filtered: 111232, glucose: 110, noise: 1}; + withRawGlucose(entry, cals); + + entry.glucose.should.equal(110); + entry.raw.should.equal(113); + }); + + it('should add raw glucose and not mess with sgv from NS', function ( ) { + var entry = {unfiltered: 113680, filtered: 111232, sgv: 110, noise: 1}; + withRawGlucose(entry, cals); + + should.not.exist(entry.glucose); + entry.sgv.should.equal(110); + entry.raw.should.equal(113); + }); + + it('should add raw glucose and set missing glucose', function ( ) { + var entry = {unfiltered: 113680, filtered: 111232, noise: 1}; + withRawGlucose(entry, cals); + + entry.glucose.should.equal(115); + entry.raw.should.equal(115); + }); + + it('should add raw glucose, but not set missing glucose above maxRaw', function ( ) { + var entry = {unfiltered: 143680, filtered: 141232, noise: 1}; + withRawGlucose(entry, cals, 150); + + should.not.exist(entry.glucose); + entry.raw.should.equal(154); + }); + + it('should add raw glucose, and set missing glucose when maxRaw is higher', function ( ) { + var entry = {unfiltered: 143680, filtered: 141232, noise: 1}; + withRawGlucose(entry, cals, 250); + + entry.glucose.should.equal(154); + entry.raw.should.equal(154); + }); + +}); \ No newline at end of file