Skip to content
Permalink
Browse files

Merge pull request #2313 from nightscout/dev

0.9.2 (Grilled Cheese)
  • Loading branch information...
jasoncalabrese committed Dec 7, 2016
2 parents d407bab + ff10eb8 commit 209cf03b1d298c21b5c387a058ca5e7b6737b211
Showing with 277 additions and 75 deletions.
  1. +6 −0 README.md
  2. +1 −1 bower.json
  3. +2 −4 lib/api/alexa/index.js
  4. +9 −8 lib/language.js
  5. +4 −1 lib/plugins/alexa-plugin.md
  6. +114 −15 lib/plugins/cob.js
  7. +10 −8 lib/plugins/iob.js
  8. +2 −2 package.json
  9. +4 −4 static/index.html
  10. +3 −3 static/report/index.html
  11. +75 −0 tests/cob.test.js
  12. +47 −29 tests/iob.test.js
@@ -404,6 +404,12 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm
* `LOOP_URGENT` (`60`) - The number of minutes since the last loop that needs to be exceeded before an urgent alarm is triggered
* Add `loop` to `SHOW_FORECAST` to show forecasted BG.

##### `alexa` (Amazon Alexa)
Integration with Amazon Alexa, [detailed setup instructions](lib/plugins/alexa-plugin.md)

##### `cors` (CORS)
Enabled [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) so other websites can make request to your Nightscout site, uses these extended settings:
* `CORS_ALLOW_ORIGIN` (`*`) - The list of sites that are allow to make requests

#### Extended Settings
Some plugins support additional configuration using extra environment variables. These are prefixed with the name of the plugin and a `_`. For example setting `MYPLUGIN_EXAMPLE_VALUE=1234` would make `extendedSettings.exampleValue` available to the `MYPLUGIN` plugin.
@@ -1,6 +1,6 @@
{
"name": "nightscout",
"version": "0.9.1",
"version": "0.9.2",
"dependencies": {
"colorbrewer": "~1.0.0",
"jQuery-Storage-API": "~1.7.2",
@@ -86,7 +86,7 @@ function configure (app, wares, ctx, env) {
} else {
direction = records[0].direction;
}
var status = records[0].sgv + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)) + '.';
var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time)) + '.';
callback(null, {results: status, priority: -1});
});
// console.log('BG results called');
@@ -110,10 +110,8 @@ function configure (app, wares, ctx, env) {
direction = ' and rapidly dropping';
} else if (records[0].direction === 'DoubleUp') {
direction = ' and rapidly rising';
} else {
direction = records[0].direction;
}
var status = records[0].sgv + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time));
var status = sbx.scaleMgdl(records[0].sgv) + direction + ' as of ' + moment(records[0].date).from(moment(sbx.time));
callback('Current blood glucose', status);
});
}, 'metric', ['bg', 'blood glucose', 'number']);
@@ -3912,7 +3912,7 @@ function init() {
,pt: 'Insulina ativa'
,ru: 'Активный инсулин'
,sk: 'Aktívny inzulín (IOB)'
,nl: 'Actieve nnsuline (IOB)'
,nl: 'Actieve insuline (IOB)'
,ko: 'IOB'
}
,'Carbs-on-Board' : {
@@ -3946,7 +3946,7 @@ function init() {
,pt: 'Ajuda de bolus'
,ru: 'Предпросмотр мастера болюса'
,sk: 'Bolus Wizard'
,nl: 'Bolus wizzard'
,nl: 'Bolus Wizard Preview (BWP)'
,ko: 'Bolus 마법사 미리보기'
}
,'Value Loaded' : {
@@ -3977,9 +3977,9 @@ function init() {
,sv: 'Kanylålder (CAGE)'
,pl: 'Czas wkłucia (CAGE)'
,pt: 'Idade da Cânula (ICAT)'
,ru: 'Возраст сенсора'
,ru: 'Возраст канюли'
,sk: 'Zavedenie kanyly (CAGE)'
,nl: 'Canule leeftijd'
,nl: 'Canule leeftijd (CAGE)'
,ko: '캐뉼라 사용기간'
}
,'Basal Profile' : {
@@ -5117,7 +5117,7 @@ function init() {
,pl: 'Gdy sygnał jest zakłócony'
,ru: 'Когда есть шумовой фон'
,sk: 'Pri šume'
,nl: 'Waar ruis is'
,nl: 'Bij ruis'
,ko: '노이즈가 있을 때'
}
,'When enabled small white dots will be displayed for raw BG data' : {
@@ -5160,6 +5160,7 @@ function init() {
,ru: 'Произвольное наименование'
,sk: 'Vlastný názov stránky'
,ko: '사용자 정의 제목'
,nl: 'Eigen titel'
}
,'Theme' : {
cs: 'Téma'
@@ -7719,7 +7720,7 @@ function init() {
,bg: 'Възраст на инсулина (ВИ)'
,ro: 'Vechimea insulinei'
,ru: 'Возраст инсулина'
,nl: 'Insuline leeftijd'
,nl: 'Insuline leeftijd (IAGE)'
,ko: '인슐린 사용 기간'
,fi: 'Insuliinin ikä'
,pt: 'Idade da insulina'
@@ -8640,7 +8641,7 @@ function init() {
,bg: 'БП'
,ko: 'BWP'
,it: 'BWP'
,nl: 'Bolus Wizzard preview'
,nl: 'BWP'
}
,'Urgent' : {
cs:'Urgentní'
@@ -9092,7 +9093,7 @@ function init() {
,pt: 'COB'
,sk: 'SACH'
,it: 'COB'
,nl: 'Koolhydraten on board'
,nl: 'COB'
}
,'Last Carbs' : {
cs:'Poslední sacharidy'
@@ -7,9 +7,12 @@

Nightscout/Alexa
======================================

> Please note that your hosted montitor must support the Alexa plug-in which exposes an endpoint to the Alexa skill you will create below. Please see [updating my version here](https://github.com/nightscout/cgm-remote-monitor#updating-my-version) for steps
##Setup

### 1) Make sure to add `alexa` to the list of plugins in your `ENABLE` setting
### 1) Make sure to add `alexa` to the list of plugins in your `ENABLE` setting (note: environment variables are set in the configuration section for your monitor typically Azure, Heroku, etc.)
### 2) Sign in to https://developer.amazon.com/ and navigate to the "Alexa" tab. Select "Getting started" in "Alexa Skills Kit"
* Click on "Add a new skill". Fill in "Nightscout" as the name and "nightscout" as the invocation name (feel free to use other names as you see fit).
* This skill will not use the "Audio Player".
@@ -1,7 +1,8 @@
'use strict';

var _ = require('lodash')
, moment = require('moment');
, moment = require('moment')
, times = require('../times');

function init(ctx) {
var translate = ctx.language.translate;
@@ -13,6 +14,8 @@ function init(ctx) {
, pluginType: 'pill-minor'
};

cob.RECENCY_THRESHOLD = times.mins(30).msecs;

cob.setProperties = function setProperties(sbx) {
sbx.offerProperty('cob', function setCOB ( ) {
return cob.cobTotal(sbx.data.treatments, sbx.data.devicestatus, sbx.data.profile, sbx.time);
@@ -31,18 +34,111 @@ function init(ctx) {
return {};
}

// TODO: figure out the liverSensRatio that gives the most accurate purple line predictions
var liverSensRatio = 8;
var totalCOB = 0;
var lastCarbs = null;
if (typeof time === 'undefined') {
time = Date.now();
} else if (time && time.getTime) {
time = time.getTime();
}

var devicestatusCOB = cob.lastCOBDeviceStatus(devicestatus, time);

var treatmentCOB = (treatments !== undefined && treatments.length) ? cob.fromTreatments(treatments, devicestatus, profile, time, spec_profile) : {};

var result = devicestatusCOB;
if (_.isEmpty(result)) {
result = treatmentCOB;
result.source = 'Care Portal';
} else if (treatmentCOB.cob) {
result.treatmentCOB = treatmentCOB;
}

if (!treatments) {
return addDisplay(result);
};

function addDisplay(cob) {
if (_.isEmpty(cob) || cob.cob === undefined) {
return {};
}

if (typeof time === 'undefined') {
time = Date.now();
var display = Math.round(cob.cob * 10) / 10;
return _.merge(cob, {
display: display
, displayLine: 'COB: ' + display + 'g'
});
}

cob.lastCOBDeviceStatus = function lastCOBDeviceStatus (devicestatus, time) {

var futureMills = time + times.mins(5).msecs; //allow for clocks to be a little off
var recentMills = time - cob.RECENCY_THRESHOLD;

return _.chain(devicestatus)
.map(cob.fromDeviceStatus)
.reject(_.isEmpty)
.filter(function (cobStatus) {
return cobStatus.mills <= futureMills && cobStatus.mills >= recentMills;
})
.sortBy('mills')
.last()
.value();
};

cob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) {

var cobObj;
if (_.get(devicestatusEntry, 'openaps') !== undefined) {
var suggested = devicestatusEntry.openaps.suggested;
var enacted = devicestatusEntry.openaps.enacted;

var lastCOB = 0;
var lastMoment = null;

if (suggested && enacted) {
var suggestedMoment = moment(suggested.timestamp);
var enactedMoment = moment(enacted.timestamp);
if (enactedMoment.isAfter(suggestedMoment)) {
lastCOB = enacted.COB;
lastMoment = enactedMoment;
} else {
lastCOB = suggested.COB;
lastMoment = suggestedMoment;
}
} else if (enacted) {
lastCOB = enacted.COB;
lastMoment = moment(enacted.timestamp);
} else if (suggested) {
lastCOB = suggested.COB;
lastMoment = moment(suggested.timestamp);
}

if (lastCOB === 0 || !lastMoment) {
return {};
}

return {
cob: lastCOB
, source: 'OpenAPS'
, device: devicestatusEntry.device
, mills: lastMoment.valueOf( )
};
} else if (_.get(devicestatusEntry, 'loop.cob') !== undefined) {
cobObj = devicestatusEntry.loop.cob;
return {
cob: cobObj.cob
, source: 'Loop'
, device: devicestatusEntry.device
, mills: moment(cobObj.timestamp).valueOf( )
};
} else {
return {};
}
};

cob.fromTreatments = function fromTreatments (treatments, devicestatus, profile, time, spec_profile) {
// TODO: figure out the liverSensRatio that gives the most accurate purple line predictions
var liverSensRatio = 8;
var totalCOB = 0;
var lastCarbs = null;

var isDecaying = 0;
var lastDecayedBy = 0;
@@ -83,15 +179,13 @@ function init(ctx) {
});

var rawCarbImpact = isDecaying * profile.getSensitivity(time, spec_profile) / profile.getCarbRatio(time, spec_profile) * profile.getCarbAbsorptionRate(time, spec_profile) / 60;
var display = Math.round(totalCOB * 10) / 10;

return {
decayedBy: lastDecayedBy
, isDecaying: isDecaying
, carbs_hr: profile.getCarbAbsorptionRate(time, spec_profile)
, rawCarbImpact: rawCarbImpact
, cob: totalCOB
, display: display
, displayLine: 'COB: ' + display + 'g'
, lastCarbs: lastCarbs
};
};
@@ -159,10 +253,15 @@ function init(ctx) {
var displayCob = Math.round(prop.cob * 10) / 10;

var info = null;
if (prop.lastCarbs) {
var when = new Date(prop.lastCarbs.mills).toLocaleString();
var amount = prop.lastCarbs.carbs + 'g';
info = [{label: translate('Last Carbs'), value: amount + ' @ ' + when }];
if (prop.treatmentCOB !== undefined) {
info = [ ];
info.push({label: translate('Careportal COB'), value: Math.round(prop.treatmentCOB.cob * 10) / 10});

if (prop.treatmentCOB.lastCarbs) {
var when = new Date(prop.treatmentCOB.lastCarbs.mills).toLocaleString();
var amount = prop.treatmentCOB.lastCarbs.carbs + 'g';
info.push({label: translate('Last Carbs'), value: amount + ' @ ' + when});
}
}

sbx.pluginBase.updatePillText(sbx, {
@@ -57,21 +57,23 @@ function init(ctx) {
}
var futureMills = time + times.mins(5).msecs; //allow for clocks to be a little off
var recentMills = time - iob.RECENCY_THRESHOLD;

// All IOBs
var IOBs = _.chain(devicestatus)
var iobs = _.chain(devicestatus)
.map(iob.fromDeviceStatus)
.reject(_.isEmpty)
.filter(function (iobStatus) {
return iobStatus.mills <= futureMills && iobStatus.mills >= recentMills;
})
.sortBy('mills');

// Loop IOBs
var LoopIOBs = IOBs
.filter(function (iobStatus) {
return iobStatus.source === 'Loop';
});
var loopIOBs = iobs.filter(function (iobStatus) {
return iobStatus.source === 'Loop';
});

// Loop uploads both Loop IOB and pump-reported IOB, prioritize Loop IOB if available
return LoopIOBs.last().value() || IOBs.last().value();
return loopIOBs.last().value() || iobs.last().value();
};

iob.fromDeviceStatus = function fromDeviceStatus(devicestatusEntry) {
@@ -176,7 +178,7 @@ function init(ctx) {
} else if (minAgo < 180) {
var x2 = (minAgo - 75) / 5;
result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - 0.054233 * x2 + 0.55556);
result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak));
result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * 3 - peak));
}

}
@@ -233,7 +235,7 @@ function init(ctx) {

function getIob(sbx) {
if (sbx.properties.iob && sbx.properties.iob.iob !== 0) {
return sbx.properties.iob.iob + ' units of';
return utils.toFixed(sbx.properties.iob.iob) + ' units of';
}
return 'no';
}
@@ -1,6 +1,6 @@
{
"name": "Nightscout",
"version": "0.9.1",
"version": "0.9.2",
"description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.",
"license": "AGPL-3.0",
"author": "Nightscout Team",
@@ -63,7 +63,7 @@
"forever": "~0.13.0",
"git-rev": "git://github.com/bewest/git-rev.git",
"jquery": "^2.1.4",
"jsonwebtoken": "^7.1.7",
"jsonwebtoken": "7.1.9",
"lodash": "^4.0.0",
"long": "~2.2.3",
"mfb": "^0.12.0",
@@ -25,10 +25,10 @@
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#333333">

<link rel="stylesheet" type="text/css" href="/css/drawer.css?v=0.9.1" />
<link rel="stylesheet" type="text/css" href="/css/main.css?v=0.9.1" />
<link rel="stylesheet" type="text/css" href="/css/drawer.css?v=0.9.2" />
<link rel="stylesheet" type="text/css" href="/css/main.css?v=0.9.2" />
<link rel="stylesheet" type="text/css" href="/css/dropdown.css" />
<link rel="stylesheet" type="text/css" href="/css/sgv.css?v=0.9.1" />
<link rel="stylesheet" type="text/css" href="/css/sgv.css?v=0.9.2" />
<link rel="stylesheet" type="text/css" href="/bower_components/tipsy-jmalonzo/src/stylesheets/tipsy.css" />
<link rel="stylesheet" type="text/css" href="/bower_components/jquery-ui/themes/ui-darkness/jquery-ui.min.css">
</head>
@@ -590,7 +590,7 @@
<audio src="/audio/alarm2.mp3" preload="auto" loop="true" class="urgent alarm2 mp3" type="audio/mp3"></audio>
</div>

<script src="/public/js/bundle.js?v=0.9.1"></script>
<script src="/public/js/bundle.js?v=0.9.2"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="/bower_components/jQuery-Storage-API/jquery.storageapi.min.js"></script>
<script src="/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js"></script>

0 comments on commit 209cf03

Please sign in to comment.
You can’t perform that action at this time.