From 8c98c1d782dc326ad0bc36b049c91160a35ac6e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 13 Nov 2014 23:45:34 -0800 Subject: [PATCH 001/661] simple pushover based alarms, ported from old pebble code --- lib/entries.js | 156 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index c8f8eb7a64b..e864ac98619 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -3,7 +3,11 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); -var TEN_MINS = 10 * 60 * 1000; +// declare local constants for time differences +var TIME_10_MINS = 10 * 60 * 1000, + TIME_15_MINS = 15 * 60 * 1000, + TIME_30_MINS = TIME_15_MINS * 2; + /**********\ * Entries @@ -102,6 +106,7 @@ function storage(name, storage, pushover) { var firstErr = null, totalCreated = 0; + console.info("docs: " + JSON.stringify(docs)); docs.forEach(function(doc) { collection.update(doc, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; @@ -113,31 +118,134 @@ function storage(name, storage, pushover) { }); } - //currently the Android upload will send the last MBG over and over, make sure we get a single notification - var lastMBG = 0; - function sendPushover(doc) { - if (doc.type && doc.mbg && doc.type == 'mbg' && doc.date && doc.date != lastMBG && pushover) { - var offset = new Date().getTime() - doc.date; - if (offset > TEN_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); - } else { - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + doc.mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(doc.date), - priority: 0, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); - } - lastMBG = doc.date; + if (doc.type && doc.date && pushover) { + if (doc.type == 'mbg') { + sendMBGPushover(doc); + } else if (doc.type == 'sgv') { + sendSGVPushover(doc); + } else { + console.info("Not an MBG or SGV: " + JSON.stringify(doc)); } + } else { + console.info("What are you: " + JSON.stringify(doc)); + } + } + + //currently the Android upload will send the last MBG over and over, make sure we get a single notification + var lastMBGDate = 0; + + function sendMBGPushover(doc) { + + if (doc.mbg && doc.type == 'mbg' && doc.date != lastMBGDate) { + var offset = new Date().getTime() - doc.date; + if (offset > TIME_10_MINS) { + console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); + } else { + var msg = { + expire: 14400, // 4 hours + message: '\nMeter BG: ' + doc.mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(doc.date), + priority: 0, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); + } + lastMBGDate = doc.date; + } + } + + // global variable for last alert time + var lastAlert = 0; + var lastSGVDate = 0; + + function sendSGVPushover(doc) { + + if (!doc.sgv || doc.type != 'sgv') { + console.info("Not an SGV: " + JSON.stringify(doc)); + return; + } + + var now = new Date().getTime(), + offset = new Date().getTime() - doc.date; + + if (offset > TIME_10_MINS || doc.date == lastSGVDate) { + console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); + return; + } + + // initialize message data + var sinceLastAlert = now - lastAlert, + priority = 0, + sound = "bike", + readingtime = doc.date, + readago = now - readingtime; + + console.info("now: " + now); + console.info("doc.sgv: " + doc.sgv); + console.info("doc.direction: " + doc.direction); + console.info("doc.date: " + doc.date); + console.info("readingtime: " + readingtime); + console.info("readago: " + readago); + + // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high + if (doc.sgv < 39) { + if (sinceLastAlert > TIME_10_MINS) { + priority = 2; + sound = "siren"; + } + } else if (doc.sgv < 55) { + priority = 2; + sound = "spacealarm"; + } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + priority = 1; + sound = "falling"; + } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { + priority = 1; + sound = "falling"; + } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + priority = 0; + sound = "cashregister"; + } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + priority = 1; + sound = "intermission"; + } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + priority = 1; + sound = "tugboat"; + } else if (doc.sgv > 300 && sinceLastAlert > TIME_15_MINS) { + priority = 2; + sound = "climb"; + } + + if (priority === 0 && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { + priority = 1; + sound = "cosmic"; + } + + if (priority > 0) { + lastAlert = now; + } + + var msg = { + expire: 14400, // 4 hours + message: '\BG NOW: ' + doc.sgv, + title: 'CGM Alert', + sound: sound, + timestamp: new Date(doc.date), + priority: priority, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); + + lastSGVDate = doc.date; } function getEntry(fn, id) { From e89714f8a48c5b5e047f3ddf338b3a96aff48a22 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 13 Nov 2014 23:49:39 -0800 Subject: [PATCH 002/661] removed some extra info's --- lib/entries.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index e864ac98619..f05a3e0f886 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -106,7 +106,6 @@ function storage(name, storage, pushover) { var firstErr = null, totalCreated = 0; - console.info("docs: " + JSON.stringify(docs)); docs.forEach(function(doc) { collection.update(doc, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; @@ -124,11 +123,7 @@ function storage(name, storage, pushover) { sendMBGPushover(doc); } else if (doc.type == 'sgv') { sendSGVPushover(doc); - } else { - console.info("Not an MBG or SGV: " + JSON.stringify(doc)); } - } else { - console.info("What are you: " + JSON.stringify(doc)); } } @@ -167,7 +162,6 @@ function storage(name, storage, pushover) { function sendSGVPushover(doc) { if (!doc.sgv || doc.type != 'sgv') { - console.info("Not an SGV: " + JSON.stringify(doc)); return; } From 6682d274b01920aceaa00cd12e26281638847bb7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 14 Nov 2014 00:06:21 -0800 Subject: [PATCH 003/661] fixed bug that cause notification to be sent too often --- lib/entries.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index f05a3e0f886..79d6f36ee39 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -176,7 +176,7 @@ function storage(name, storage, pushover) { // initialize message data var sinceLastAlert = now - lastAlert, priority = 0, - sound = "bike", + sound = null, readingtime = doc.date, readago = now - readingtime; @@ -216,28 +216,29 @@ function storage(name, storage, pushover) { sound = "climb"; } - if (priority === 0 && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { + if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { priority = 1; sound = "cosmic"; } - if (priority > 0) { + if (sound != null) { lastAlert = now; + + var msg = { + expire: 14400, // 4 hours + message: '\BG NOW: ' + doc.sgv, + title: 'CGM Alert', + sound: sound, + timestamp: new Date(doc.date), + priority: priority, + retry: 30 + }; + + pushover.send(msg, function (err, result) { + console.log(result); + }); } - var msg = { - expire: 14400, // 4 hours - message: '\BG NOW: ' + doc.sgv, - title: 'CGM Alert', - sound: sound, - timestamp: new Date(doc.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); lastSGVDate = doc.date; } From 82fb83b23077bb1cd74011b317e17baffd2affb2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Nov 2014 18:12:55 -0800 Subject: [PATCH 004/661] really basic template to post sgvs --- bin/post-sgv.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100755 bin/post-sgv.sh diff --git a/bin/post-sgv.sh b/bin/post-sgv.sh new file mode 100755 index 00000000000..9b48e5dfcc9 --- /dev/null +++ b/bin/post-sgv.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# "date": "1413782506964" + +curl -H "Content-Type: application/json" -H "api-secret: $API_SECRET" -XPOST 'http://localhost:1337/api/v1/entries/' -d '{ + "sgv": 100, + "type": "sgv", + "direction": "Flat", + "date": "1415950912800" +}' From 79c1e5ebd87949ed0aa68f872e6a5ff45d6aafa9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Nov 2014 07:13:58 -0700 Subject: [PATCH 005/661] minor push notification changes --- lib/entries.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 79d6f36ee39..acd5d43a3dd 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -189,11 +189,11 @@ function storage(name, storage, pushover) { // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (doc.sgv < 39) { - if (sinceLastAlert > TIME_10_MINS) { - priority = 2; + if (sinceLastAlert > TIME_15_MINS) { + priority = 1; sound = "siren"; } - } else if (doc.sgv < 55) { + } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { priority = 2; sound = "spacealarm"; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { @@ -211,8 +211,8 @@ function storage(name, storage, pushover) { } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { priority = 1; sound = "tugboat"; - } else if (doc.sgv > 300 && sinceLastAlert > TIME_15_MINS) { - priority = 2; + } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { + priority = 1; sound = "climb"; } From c3ce249c7da36a0ab5b29dfdf415e3e6926b5c92 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 1 Dec 2014 17:34:52 -0800 Subject: [PATCH 006/661] try some new sounds --- lib/entries.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index acd5d43a3dd..7256720eb22 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -195,7 +195,7 @@ function storage(name, storage, pushover) { } } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { priority = 2; - sound = "spacealarm"; + sound = "persistent"; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { priority = 1; sound = "falling"; @@ -210,10 +210,10 @@ function storage(name, storage, pushover) { sound = "intermission"; } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { priority = 1; - sound = "tugboat"; + sound = "climb"; } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { priority = 1; - sound = "climb"; + sound = "updown"; } if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { From be30be173aebef97fca7b28b7dc34ad3a7f03669 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Dec 2014 11:48:08 -0800 Subject: [PATCH 007/661] adjust push message title for extra information --- lib/entries.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 7256720eb22..60cb15db393 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -175,50 +175,54 @@ function storage(name, storage, pushover) { // initialize message data var sinceLastAlert = now - lastAlert, + title = 'CGM Alert', priority = 0, sound = null, readingtime = doc.date, readago = now - readingtime; - console.info("now: " + now); - console.info("doc.sgv: " + doc.sgv); - console.info("doc.direction: " + doc.direction); - console.info("doc.date: " + doc.date); - console.info("readingtime: " + readingtime); - console.info("readago: " + readago); + console.info('now: ' + now); + console.info('doc.sgv: ' + doc.sgv); + console.info('doc.direction: ' + doc.direction); + console.info('doc.date: ' + doc.date); + console.info('readingtime: ' + readingtime); + console.info('readago: ' + readago); // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (doc.sgv < 39) { - if (sinceLastAlert > TIME_15_MINS) { + if (sinceLastAlert > TIME_30_MINS) { + title = 'CGM Error'; priority = 1; - sound = "siren"; + sound = 'persistent'; } } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; priority = 2; - sound = "persistent"; + sound = 'persistent'; } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; priority = 1; - sound = "falling"; + sound = 'falling'; } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { + title = 'Falling'; priority = 1; - sound = "falling"; + sound = 'falling'; } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + title = 'Perfect'; priority = 0; - sound = "cashregister"; + sound = 'cashregister'; } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + title = 'Rising'; priority = 1; - sound = "intermission"; + sound = 'intermission'; } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + title = 'High'; priority = 1; - sound = "climb"; + sound = 'climb'; } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { + title = 'High'; priority = 1; - sound = "updown"; - } - - if (sound == null && readago > TIME_15_MINS && sinceLastAlert > TIME_30_MINS) { - priority = 1; - sound = "cosmic"; + sound = 'updown'; } if (sound != null) { @@ -226,8 +230,8 @@ function storage(name, storage, pushover) { var msg = { expire: 14400, // 4 hours - message: '\BG NOW: ' + doc.sgv, - title: 'CGM Alert', + message: 'BG NOW: ' + doc.sgv, + title: title, sound: sound, timestamp: new Date(doc.date), priority: priority, From fcfc0176cd3d39b342ac46352cac2a7c8e16bf57 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Dec 2014 09:01:19 -0800 Subject: [PATCH 008/661] use new BG thresholds instead of hardcoded BGs --- lib/entries.js | 18 +++++++++--------- server.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 60cb15db393..80bddbae1e5 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -14,7 +14,7 @@ var TIME_10_MINS = 10 * 60 * 1000, * Encapsulate persistent storage of sgv entries. \**********/ -function storage(name, storage, pushover) { +function storage(name, storage, pushover, env) { // TODO: Code is a little redundant. @@ -195,16 +195,16 @@ function storage(name, storage, pushover) { priority = 1; sound = 'persistent'; } - } else if (doc.sgv < 55 && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; + } else if (doc.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { + title = 'Urgent Low'; priority = 2; sound = 'persistent'; - } else if (doc.sgv < 70 && sinceLastAlert > TIME_15_MINS) { + } else if (doc.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { title = 'Low'; priority = 1; sound = 'falling'; } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { - title = 'Falling'; + title = 'Double Down'; priority = 1; sound = 'falling'; } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) @@ -212,15 +212,15 @@ function storage(name, storage, pushover) { priority = 0; sound = 'cashregister'; } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Rising'; + title = 'Double Up'; priority = 1; sound = 'intermission'; - } else if (doc.sgv > 250 && sinceLastAlert > TIME_30_MINS) { + } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { title = 'High'; priority = 1; sound = 'climb'; - } else if (doc.sgv > 300 && sinceLastAlert > TIME_30_MINS) { - title = 'High'; + } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + title = 'Urgent High'; priority = 1; sound = 'updown'; } diff --git a/server.js b/server.js index 660540c3747..ad9d559611f 100644 --- a/server.js +++ b/server.js @@ -47,7 +47,7 @@ var express = require('express'); /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// -var entriesStorage = entries.storage(env.mongo_collection, store, pushover); +var entriesStorage = entries.storage(env.mongo_collection, store, pushover, env); var settings = require('./lib/settings')(env.settings_collection, store); var treatmentsStorage = treatments.storage(env.treatments_collection, store, pushover); var devicestatusStorage = devicestatus.storage(env.devicestatus_collection, store); From 0d44f0a81e197856d9eb820310077ec5951eaa80 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 12 Jan 2015 10:17:33 -0800 Subject: [PATCH 009/661] adjusted alarms sounds for double up and urgent high --- lib/entries.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 80bddbae1e5..e899a526b98 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -214,7 +214,7 @@ function storage(name, storage, pushover, env) { } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { title = 'Double Up'; priority = 1; - sound = 'intermission'; + sound = 'climb'; } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { title = 'High'; priority = 1; @@ -222,7 +222,7 @@ function storage(name, storage, pushover, env) { } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { title = 'Urgent High'; priority = 1; - sound = 'updown'; + sound = 'persistent'; } if (sound != null) { From 4a0fc8ffef13ae957fb0570f5d58328a801addd5 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 24 Mar 2015 18:15:05 -0700 Subject: [PATCH 010/661] update API to support searching for all types Should enable searching for all data types. --- app.js | 2 +- lib/api/devicestatus/index.js | 6 ++- lib/api/entries/index.js | 87 ++++++++++++++++++++++++++++++----- lib/api/index.js | 12 ++--- lib/devicestatus.js | 5 +- lib/storage.js | 6 +++ lib/treatments.js | 2 +- tests/api.entries.test.js | 9 ++-- 8 files changed, 103 insertions(+), 26 deletions(-) diff --git a/app.js b/app.js index 272735bcb77..cab4dbe1655 100644 --- a/app.js +++ b/app.js @@ -5,7 +5,7 @@ function create (env, ctx) { /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// - var api = require('./lib/api/')(env, ctx.entries, ctx.settings, ctx.treatments, ctx.profiles, ctx.devicestatus); + var api = require('./lib/api/')(env, ctx); var pebble = ctx.pebble; var app = express(); diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index af67dc7b64e..40d3a3246d3 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -17,7 +17,11 @@ function configure (app, wares, devicestatus) { // List settings available api.get('/devicestatus/', function(req, res) { - devicestatus.list(function (err, profiles) { + var q = req.query; + if (!q.count) { + q.count = 10; + } + devicestatus.list(q, function (err, profiles) { return res.json(profiles); }); }); diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 79642cb0f77..6c779fa115c 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -7,7 +7,8 @@ var sgvdata = require('sgvdata'); /**********\ * Entries \**********/ -function configure (app, wares, entries) { +function configure (app, wares, core) { + var entries = core.entries; var express = require('express'), api = express.Router( ) ; @@ -26,8 +27,24 @@ function configure (app, wares, entries) { // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); + function force_typed_data (opts) { + function sync (data, next) { + if (data.type != opts.type) { + console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); + data.type = opts.type; + } + next(null, data); + } + return es.map(sync); + } + // Middleware to format any response involving entries. function format_entries (req, res, next) { + var type_params = { + type: (req.query && req.query.find && req.query.find.type + && req.query.find.type != req.params.model) + ? req.query.find.type : req.params.model + }; var output = es.readArray(res.entries || [ ]); if (res.entries_err) { return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', res.entries_err); @@ -37,7 +54,7 @@ function configure (app, wares, entries) { es.pipeline(output, sgvdata.format( ), res); }, json: function ( ) { - es.pipeline(output, entries.map( ), es.writeArray(function (err, out) { + es.pipeline(output, force_typed_data(type_params), entries.map( ), es.writeArray(function (err, out) { res.json(out); })); } @@ -106,24 +123,70 @@ function configure (app, wares, entries) { es.pipeline(inputs( ), persist(done)); } - api.get('/entries', function(req, res, next) { - // If "?count=" is present, use that number to decided how many to return. - var query = req.query; - if (!query.count) { query.count = 10 }; - entries.list(query, function(err, entries) { - res.entries = entries; + api.param('model', function (req, res, next, model) { + var find = { }; + switch (model) { + case 'treatments': + case 'devicestatus': + case 'settings': + case 'profile': + req.model = core[model]; + // find.type = model; + break; + + case 'meter': + case 'mbg': + find.type = 'mbg'; + req.model = core.entries; + break; + case 'cal': + find.type = 'cal'; + req.model = core.entries; + break; + case 'sensor': + find.type = 'sensor'; + req.model = core.entries; + break; + case 'sgv': + find.type = 'sgv'; + req.model = core.entries; + break; + default: + req.model = core.entries; + break; + } + if (!req.query.find) { + req.query.find = find; + } else { + req.query.find.type = find.type; + } + next( ); + }); + api.get('/entries/current', function(req, res, next) { + entries.list({count: 1}, function(err, records) { + res.entries = records; res.entries_err = err; return next( ); }); }, format_entries); - api.get('/entries/current', function(req, res, next) { - entries.list({count: 1}, function(err, records) { - res.entries = records; + api.get('/entries/:model', query_models, format_entries); + api.get('/entries', query_models, format_entries); + + + function query_models (req, res, next) { + // If "?count=" is present, use that number to decided how many to return. + if (!req.model) { + req.model = core.entries; + } + var query = req.query; + if (!query.count) { query.count = 10 }; + req.model.list(query, function(err, entries) { + res.entries = entries; res.entries_err = err; return next( ); }); - }, format_entries); + } // Allow previewing your post content, just echos everything you diff --git a/lib/api/index.js b/lib/api/index.js index e666a71b6e0..04280c607ea 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,6 +1,6 @@ 'use strict'; -function create (env, entries, settings, treatments, profile, devicestatus) { +function create (env, core) { var express = require('express'), app = express( ) ; @@ -46,11 +46,11 @@ function create (env, entries, settings, treatments, profile, devicestatus) { } // Entries and settings - app.use('/', require('./entries/')(app, wares, entries)); - app.use('/', require('./settings/')(app, wares, settings)); - app.use('/', require('./treatments/')(app, wares, treatments)); - app.use('/', require('./profile/')(app, wares, profile)); - app.use('/', require('./devicestatus/')(app, wares, devicestatus)); + app.use('/', require('./entries/')(app, wares, core)); + app.use('/', require('./settings/')(app, wares, core.settings)); + app.use('/', require('./treatments/')(app, wares, core.treatments)); + app.use('/', require('./profile/')(app, wares, core.profile)); + app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 2c1ec79d87e..00d9ce2d9f1 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -27,8 +27,9 @@ function storage (collection, storage) { }); } - function list(fn) { - return api().find({}).sort({created_at: -1}).toArray(fn); + function list(opts, fn) { + var q = opts && opts.find ? opts.find : { }; + return storage.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); } function api() { diff --git a/lib/storage.js b/lib/storage.js index d5b128a0592..0f8c50ba3fa 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -49,6 +49,12 @@ function init (env, cb) { }; }; + mongo.limit = function limit (opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); + } + return this; + }; mongo.ensureIndexes = function(collection, fields) { fields.forEach(function (field) { console.info("ensuring index for: " + field); diff --git a/lib/treatments.js b/lib/treatments.js index cbf16d890e1..2db959b12f4 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -116,7 +116,7 @@ function storage (collection, storage, pushover) { return q; } - return api( ).find(find()).sort({created_at: -1}).toArray(fn); + return storage.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); } function api ( ) { diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index f3e368da7a6..5c906ae7cdf 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -10,13 +10,16 @@ describe('Entries REST api', function ( ) { this.wares = require('../lib/middleware/')(env); var store = require('../lib/storage')(env); this.archive = require('../lib/entries').storage(env.mongo_collection, store); + var bootevent = require('../lib/bootevent'); this.app = require('express')( ); this.app.enable('api'); var self = this; - store(function ( ) { - self.app.use('/', entries(self.app, self.wares, self.archive)); - self.archive.create(load('json'), done); + bootevent(env).boot(function booted (ctx) { + env.store = ctx.store; + self.app.use('/', entries(self.app, self.wares, ctx)); + self.archive.create(load('json'), done); }); + // store(function ( ) { }); }); after(function (done) { From 036ede01b8fb4dc41d55ae9f2940a731a905f8a1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 12:33:58 +0300 Subject: [PATCH 011/661] First pass at visualisation plugin architecture, using node modules with Browserify --- bundle/bundle.source.js | 27 ++++++++++++- bundle/index.js | 3 +- lib/boluswizardpreview.js | 58 ++++++++++++++++++++++++++++ lib/pluginbase.js | 17 ++++++++ lib/profilefunctions.js | 81 +++++++++++++++++++++++++++++++++++++++ package.json | 1 + static/js/client.js | 50 ++++++++++++++++++++++++ 7 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 lib/boluswizardpreview.js create mode 100644 lib/pluginbase.js create mode 100644 lib/profilefunctions.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index e9c8f6e8dca..28b28f3177d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,10 +1,33 @@ (function () { - + window.Nightscout = window.Nightscout || {}; window.Nightscout = { - iob: require('../lib/iob')() + iob: require('../lib/iob')(), + profile: require('../lib/profilefunctions')() + }; + + // Plugins + + var inherits = require("inherits"); + var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class + + window.NightscoutPlugins = window.NightscoutPlugins || {}; + + window.NightscoutPlugins = { + bwp: require('../lib/boluswizardpreview')(PluginBase) }; + // class inheritance to the plugins from the base + map functions over + + for (var p in window.NightscoutPlugins) { + var plugin = window.NightscoutPlugins[p]; + inherits(plugin, PluginBase); + + for (var n in PluginBase.prototype) { + var item = PluginBase.prototype[n]; + plugin[n] = item; + } + } console.info("Nightscout bundle ready", window.Nightscout); diff --git a/bundle/index.js b/bundle/index.js index 5ea7ff424da..60f9bcf79ac 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,8 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: [__dirname + '../lib/', __dirname + '/bundle.source.js'], + watch: __dirname + '/../lib/', + // watch: [__dirname + '/../lib/', __dirname + '/bundle.source.js'], mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js new file mode 100644 index 00000000000..5be1d1b70db --- /dev/null +++ b/lib/boluswizardpreview.js @@ -0,0 +1,58 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + var iob = this.env.iob; + + var pill = this.currentDetails.find('span.pill.bat'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var bat = 0.0; + + sgv = Number(sgv)/18; + iob = Number(iob); + + // Above target -> calculate insulin dose against target_high + + if (sgv > this.profile.target_high) + { + var delta = sgv - this.profile.target_high; + bat = (delta / this.profile.sens) - iob; + } + + // between targets + + if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && iob > 0) + { + // ... + } + + // Above target -> calculate insulin dose against target_low + + if (sgv < this.profile.target_low) + { + var delta = this.profile.target_low - sgv; + bat = 0-(delta / this.profile.sens) - iob; + } + + bat = Math.round(bat * 100) / 100; + pill.find('em').text(bat + 'U'); + +}; + + +function BWP(pluginBase) { + pluginBase.call(this); + + return { + updateVisualisation: updateVisualisation + }; +} + +module.exports = BWP; \ No newline at end of file diff --git a/lib/pluginbase.js b/lib/pluginbase.js new file mode 100644 index 00000000000..0bcb05f77b0 --- /dev/null +++ b/lib/pluginbase.js @@ -0,0 +1,17 @@ +'use strict'; + +function setEnv(env) { + this.profile = env.profile; + this.currentDetails = env.currentDetails; + this.env = env; +} + +function PluginBase() { + return { + setEnv: setEnv + }; +} + +PluginBase.prototype.setEnv = setEnv; + +module.exports = PluginBase; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js new file mode 100644 index 00000000000..ff684472b7f --- /dev/null +++ b/lib/profilefunctions.js @@ -0,0 +1,81 @@ +'use strict'; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // multiple profile support for predictions + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + function timeStringToSeconds(time) { + var split = time.split(":"); + return parseInt(split[0])*3600 + parseInt(split[1])*60; + } + + // preprocess the timestamps to seconds for a couple orders of magnitude faster operation + function preprocessProfileOnLoad(container) + { + for (var key in container) { + var value = container[key]; + if( Object.prototype.toString.call(value) === '[object Array]' ) { + preprocessProfileOnLoad(value); + } else { + if (value.time) { + var sec = timeStringToSeconds(value.time); + if (!isNaN(sec)) value.timeAsSeconds = sec; + } + } + } + container.timestampsPreProcessed = true; + } + + + function getValueByTime(profile, time, valueContainer) + { + // If the container is an Array, assume it's a valid timestamped value container + + var returnValue = valueContainer; + + if( Object.prototype.toString.call(valueContainer) === '[object Array]' ) { + + var timeAsDate = new Date(time); + var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; + + for (var t in valueContainer) { + var value = valueContainer[t]; + if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { + returnValue = value.value; + } + } + } + + return returnValue; + } + + function getDIA(profile, time) + { + return getValueByTime(profile, time,profile.dia); + } + + function getSensitivity(profile, time) + { + return getValueByTime(profile, time,profile.sens); + } + + function getCarbRatio(profile, time) + { + return getValueByTime(profile, time,profile.carbratio); + } + + function getCarbAbsorptionRate(profile, time) + { + return getValueByTime(profile, time,profile.carbs_hr); + } + + +function Profile(opts) { + + return { + preprocessProfileOnLoad: preprocessProfileOnLoad + }; + +} + +module.exports = Profile; \ No newline at end of file diff --git a/package.json b/package.json index fd31ca30ef9..0ec1fd5c446 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "express": "^4.6.1", "express-extension-to-accept": "0.0.2", "git-rev": "git://github.com/bewest/git-rev.git", + "inherits": "~2.0.1", "long": "~2.2.3", "mongodb": "^1.4.7", "moment": "2.8.1", diff --git a/static/js/client.js b/static/js/client.js index c0a2dab5919..26e210d77e9 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -469,6 +469,32 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } + function updatePluginData(sgv, time) + { + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + env.sgv = Number(sgv); + var iob = Nightscout.iob.calcTotal(treatments, profile, time); + env.iob = Number(iob.iob); + + for (var p in NightscoutPlugins) + { + var plugin = NightscoutPlugins[p]; + plugin.setEnv(env); + } + + } + + function updatePluginVisualisation() + { + for (var p in NightscoutPlugins) + { + var plugin = NightscoutPlugins[p]; + plugin.updateVisualisation(); + } + } + function updateIOBIndicator(time) { if (showIOB()) { var pill = currentDetails.find('span.pill.iob'); @@ -479,6 +505,20 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } var iob = Nightscout.iob.calcTotal(treatments, profile, time); pill.find('em').text(iob.display + 'U'); + +/* + if (typeof latestSGV !== 'undefined' && typeof latestSGV.y !== 'undefined') { + + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + + //NightscoutPlugins.bwp.setEnv(env); + updatePluginData(time); + NightscoutPlugins.bwp.updateBATIndicator(Number(latestSGV.y), Number(iob.iob)); + } +*/ + } else { currentDetails.find('.pill.iob').remove(); } @@ -532,6 +572,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } + // update plugins + + updatePluginData(focusPoint.y,retroTime); + updatePluginVisualisation(); + updateIOBIndicator(retroTime); $('#currentTime') @@ -568,6 +613,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); updateIOBIndicator(nowDate); + // update plugins + + updatePluginData(latestSGV.y,nowDate); + updatePluginVisualisation(); + currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } From 24d00535a440d5ca809ea6d813c953cc434e8b31 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 13:36:12 +0300 Subject: [PATCH 012/661] Plugins can now be data and visualisation providers. Moved IOB to be 100% a plugin --- bundle/bundle.source.js | 2 +- lib/boluswizardpreview.js | 5 ++- lib/iob.js | 26 +++++++++++- static/js/client.js | 85 +++++++++++++++++---------------------- 4 files changed, 68 insertions(+), 50 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index ad5b40ae733..395221ee7ed 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -3,7 +3,6 @@ window.Nightscout = window.Nightscout || {}; window.Nightscout = { - iob: require('../lib/iob')(), units: require('../lib/units')(), profile: require('../lib/profilefunctions')() }; @@ -16,6 +15,7 @@ window.NightscoutPlugins = window.NightscoutPlugins || {}; window.NightscoutPlugins = { + iob: require('../lib/iob')(), bwp: require('../lib/boluswizardpreview')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 5be1d1b70db..3751ef05378 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -51,7 +51,10 @@ function BWP(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation + updateVisualisation: updateVisualisation, + isDataProvider: false, + isVisualisationProvider: true + }; } diff --git a/lib/iob.js b/lib/iob.js index 1188d2249fe..6ea932dec51 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -1,5 +1,10 @@ 'use strict'; +function getData(environment,time) +{ + return calcTotal(environment.treatments,environment.profile,time); +} + function calcTotal(treatments, profile, time) { var iob = 0 @@ -67,10 +72,29 @@ function calcTreatment(treatment, profile, time) { } +function updateVisualisation() { + + var pill = this.currentDetails.find('span.pill.iob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + //var iob = Nightscout.iob.calcTotal(treatments, profile, time); + + pill.find('em').text(this.env.display + 'U'); + +} + + function IOB(opts) { return { - calcTotal: calcTotal + calcTotal: calcTotal, + getData: getData, + updateVisualisation: updateVisualisation, + isDataProvider: true, + isVisualisationProvider: true }; } diff --git a/static/js/client.js b/static/js/client.js index 7aba4635028..d3fbbdcbc27 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -222,11 +222,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return Math.round(raw); } - function showIOB() { - return app.enabledOptions - && app.enabledOptions.indexOf('iob') > -1; - } - // initial setup of chart when data is first made available function initializeCharts() { @@ -468,60 +463,59 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - function updatePluginData(sgv, time) - { + // PLUGIN MANAGEMENT CODE + + function updatePluginData(sgv, time) { var env = {}; env.profile = profile; env.currentDetails = currentDetails; env.sgv = Number(sgv); - var iob = Nightscout.iob.calcTotal(treatments, profile, time); - env.iob = Number(iob.iob); + +/* + return app.enabledOptions + && app.enabledOptions.indexOf('iob') > -1; + + +*/ + + // get additional data from data providers - for (var p in NightscoutPlugins) - { + for (var p in NightscoutPlugins) { + var plugin = NightscoutPlugins[p]; + + var dataProviderEnvironment = {}; + + dataProviderEnvironment.treatments = treatments; + dataProviderEnvironment.profile = profile; + + if (plugin.isDataProvider) { + var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + for (var i in dataFromPlugin) { + env[i] = dataFromPlugin[i]; + } + } + plugin.setEnv(env); + } + + // update data inside the plugins + + for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; plugin.setEnv(env); } } - function updatePluginVisualisation() - { - for (var p in NightscoutPlugins) - { + function updatePluginVisualisation() { + for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; - plugin.updateVisualisation(); + if (plugin.isVisualisationProvider) { + plugin.updateVisualisation(); + } } } - function updateIOBIndicator(time) { - if (showIOB()) { - var pill = currentDetails.find('span.pill.iob'); - - if (!pill || pill.length == 0) { - pill = $(''); - currentDetails.append(pill); - } - var iob = Nightscout.iob.calcTotal(treatments, profile, time); - pill.find('em').text(iob.display + 'U'); - -/* - if (typeof latestSGV !== 'undefined' && typeof latestSGV.y !== 'undefined') { - - var env = {}; - env.profile = profile; - env.currentDetails = currentDetails; - - //NightscoutPlugins.bwp.setEnv(env); - updatePluginData(time); - NightscoutPlugins.bwp.updateBATIndicator(Number(latestSGV.y), Number(iob.iob)); - } -*/ - - } else { - currentDetails.find('.pill.iob').remove(); - } - } + /// END PLUGIN CODE // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m @@ -576,8 +570,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updatePluginData(focusPoint.y,retroTime); updatePluginVisualisation(); - updateIOBIndicator(retroTime); - $('#currentTime') .text(formatTime(retroTime, true)) .css('text-decoration','line-through'); @@ -610,7 +602,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } updateBGDelta(prevSGV, latestSGV); - updateIOBIndicator(nowDate); // update plugins From ad6d286e574c02134bcf5c773f74f0efc19dbb82 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 13:51:46 +0300 Subject: [PATCH 013/661] Plugins now check if they've been enabled (in addition to being mapped) --- static/js/client.js | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d3fbbdcbc27..fd4217a97a2 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,6 +464,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // PLUGIN MANAGEMENT CODE + + function isPluginEnabled(name) { + return app.enabledOptions + && app.enabledOptions.indexOf(name) > -1; + } function updatePluginData(sgv, time) { var env = {}; @@ -471,30 +476,27 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.currentDetails = currentDetails; env.sgv = Number(sgv); -/* - return app.enabledOptions - && app.enabledOptions.indexOf('iob') > -1; - - -*/ - // get additional data from data providers for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - var dataProviderEnvironment = {}; + if (isPluginEnabled(p)) { + + var plugin = NightscoutPlugins[p]; + + var dataProviderEnvironment = {}; - dataProviderEnvironment.treatments = treatments; - dataProviderEnvironment.profile = profile; + dataProviderEnvironment.treatments = treatments; + dataProviderEnvironment.profile = profile; - if (plugin.isDataProvider) { - var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); - for (var i in dataFromPlugin) { - env[i] = dataFromPlugin[i]; + if (plugin.isDataProvider) { + var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + for (var i in dataFromPlugin) { + env[i] = dataFromPlugin[i]; + } } + plugin.setEnv(env); } - plugin.setEnv(env); } // update data inside the plugins @@ -508,9 +510,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updatePluginVisualisation() { for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - if (plugin.isVisualisationProvider) { - plugin.updateVisualisation(); + if (isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + if (plugin.isVisualisationProvider) { + plugin.updateVisualisation(); + } } } } From 1a2f1ec2c72f96c887f71dbbb029aaf47633ef6b Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 15:27:16 +0300 Subject: [PATCH 014/661] Better namespacing for data from plugins --- bundle/bundle.source.js | 2 ++ lib/boluswizardpreview.js | 9 ++++----- lib/iob.js | 5 ++--- lib/pluginbase.js | 3 +++ static/js/client.js | 4 +++- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 395221ee7ed..292f780da7a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -2,6 +2,8 @@ window.Nightscout = window.Nightscout || {}; + // Default features + window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')() diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 3751ef05378..169186d2e50 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -4,7 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - var iob = this.env.iob; var pill = this.currentDetails.find('span.pill.bat'); @@ -15,20 +14,20 @@ function updateVisualisation() { var bat = 0.0; + // TODO: MMOL sgv = Number(sgv)/18; - iob = Number(iob); // Above target -> calculate insulin dose against target_high if (sgv > this.profile.target_high) { var delta = sgv - this.profile.target_high; - bat = (delta / this.profile.sens) - iob; + bat = (delta / this.profile.sens) - this.iob.iob; } // between targets - if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && iob > 0) + if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && this.iob.iob > 0) { // ... } @@ -38,7 +37,7 @@ function updateVisualisation() { if (sgv < this.profile.target_low) { var delta = this.profile.target_low - sgv; - bat = 0-(delta / this.profile.sens) - iob; + bat = 0-(delta / this.profile.sens) - this.iob.iob; } bat = Math.round(bat * 100) / 100; diff --git a/lib/iob.js b/lib/iob.js index 6ea932dec51..218c779c1f8 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -80,9 +80,8 @@ function updateVisualisation() { pill = $(''); this.currentDetails.append(pill); } - //var iob = Nightscout.iob.calcTotal(treatments, profile, time); - - pill.find('em').text(this.env.display + 'U'); + + pill.find('em').text(this.iob.display + 'U'); } diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 0bcb05f77b0..72ed50dd9f6 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -3,6 +3,9 @@ function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; + this.iob = env.iob; + + // TODO: clean! this.env = env; } diff --git a/static/js/client.js b/static/js/client.js index fd4217a97a2..490acb42d52 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -491,9 +491,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (plugin.isDataProvider) { var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + var container = {}; for (var i in dataFromPlugin) { - env[i] = dataFromPlugin[i]; + container[i] = dataFromPlugin[i]; } + env[p] = container; } plugin.setEnv(env); } From 34cf139d6ba58c9a3dd9548ad0ca31e41ac6b57a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 17:45:10 +0300 Subject: [PATCH 015/661] Quick and dirty port of the COB calculator. Data providers iteratively add data to the plugin env as chained. --- bundle/bundle.source.js | 3 +- lib/cob.js | 220 ++++++++++++++++++++++++++++++++++++++++ lib/iob.js | 4 +- static/js/client.js | 11 +- 4 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 lib/cob.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 292f780da7a..9a2f145a24a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -17,7 +17,8 @@ window.NightscoutPlugins = window.NightscoutPlugins || {}; window.NightscoutPlugins = { - iob: require('../lib/iob')(), + iob: require('../lib/iob')(PluginBase), + cob: require('../lib/cob')(PluginBase), bwp: require('../lib/boluswizardpreview')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/cob.js b/lib/cob.js new file mode 100644 index 00000000000..0932366e045 --- /dev/null +++ b/lib/cob.js @@ -0,0 +1,220 @@ +'use strict'; + +function getData(environment,time) +{ + return this.cobTotal(environment.treatments,time); +} + +function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var cob=0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + var time = new Date(); + } + + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) + { + var treatment = treatments[t]; + if(treatment.carbs && treatment.created_at < time) { + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; + if (decaysin_hr > -10) { + var actStart = this.iobTotal(treatments, lastDecayedBy).activity; + var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart+actEnd)/2; + var delayedCarbs = avgActivity*liverSensRatio*sens/carbratio; + var delayMinutes = Math.round(delayedCarbs/carbs_hr*60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; + } + } + + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + cob = 0; + } + + } + } + var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; + return { + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: cob + }; +} + + function iobTotal(treatments, time) { + var iob= 0; + var activity = 0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + var time = new Date(); + } + + for (var t in treatments) { + var treatment = treatments[t]; + if(treatment.created_at < time) { + var tIOB = this.iobCalc(treatment, time); + if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; + } + }; + return { + iob: iob, + activity: activity + }; + } + + +function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } +} + +function iobCalc(treatment, time) { + + var dia=this.profile.dia; + var scaleFactor = 3.0/dia; + var peak = 75; + var sens=this.profile.sens; + var iobContrib, activityContrib; + var t = time; + if (typeof t === 'undefined') { + t = new Date(); + } + + if (treatment.insulin) { + var bolusTime=new Date(treatment.created_at); + var minAgo=scaleFactor*(t-bolusTime)/1000/60; + + if (minAgo < 0) { + iobContrib=0; + activityContrib=0; + } + if (minAgo < peak) { + var x = minAgo/5+1; + iobContrib=treatment.insulin*(1-0.001852*x*x+0.001852*x); + activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; + + } + else if (minAgo < 180) { + var x = (minAgo-75)/5; + iobContrib=treatment.insulin*(0.001323*x*x - .054233*x + .55556); + activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); + } + else { + iobContrib=0; + activityContrib=0; + } + return { + iobContrib: iobContrib, + activityContrib: activityContrib + }; + } + else { + return ''; + } + } + +function cobCalc(treatment, lastDecayedBy, time) { + + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; + + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy-carbTime)/1000/60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); + if(delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; + } + else { + return ''; + } +} + +function updateVisualisation() { + + var pill = this.currentDetails.find('span.pill.cob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + pill.find('em').text(displayCob + " g"); + +} + + +function COB(pluginBase) { + + if (pluginBase) { pluginBase.call(this); } + + return { + cobTotal: cobTotal, + cobCalc: cobCalc, + iobTotal: iobTotal, + iobCalc: iobCalc, + getData: getData, + updateVisualisation: updateVisualisation, + isDataProvider: true, + isVisualisationProvider: true + }; + +} + +module.exports = COB; diff --git a/lib/iob.js b/lib/iob.js index 218c779c1f8..135c6ef11c1 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -86,8 +86,10 @@ function updateVisualisation() { } -function IOB(opts) { +function IOB(pluginBase) { + if (pluginBase) { pluginBase.call(this); } + return { calcTotal: calcTotal, getData: getData, diff --git a/static/js/client.js b/static/js/client.js index 490acb42d52..dd278cd07ce 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -475,7 +475,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.profile = profile; env.currentDetails = currentDetails; env.sgv = Number(sgv); - + env.treatments = treatments; + // get additional data from data providers for (var p in NightscoutPlugins) { @@ -489,6 +490,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; dataProviderEnvironment.treatments = treatments; dataProviderEnvironment.profile = profile; + plugin.setEnv(env); + if (plugin.isDataProvider) { var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); var container = {}; @@ -497,17 +500,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } env[p] = container; } - plugin.setEnv(env); } } // update data inside the plugins + sendEnvToPlugins(env); + } + + function sendEnvToPlugins(env) { for (var p in NightscoutPlugins) { var plugin = NightscoutPlugins[p]; plugin.setEnv(env); } - } function updatePluginVisualisation() { From 0d3be281d1ffde43db348003c941cdcd6d262fcf Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 19 Apr 2015 17:52:13 +0300 Subject: [PATCH 016/661] Cleaner data env building --- lib/cob.js | 4 ++-- lib/iob.js | 4 ++-- static/js/client.js | 12 ++++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/cob.js b/lib/cob.js index 0932366e045..587ab117742 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -1,8 +1,8 @@ 'use strict'; -function getData(environment,time) +function getData() { - return this.cobTotal(environment.treatments,time); + return this.cobTotal(this.env.treatments,this.env.time); } function cobTotal(treatments, time) { diff --git a/lib/iob.js b/lib/iob.js index 135c6ef11c1..ee2f716cecd 100644 --- a/lib/iob.js +++ b/lib/iob.js @@ -1,8 +1,8 @@ 'use strict'; -function getData(environment,time) +function getData() { - return calcTotal(environment.treatments,environment.profile,time); + return calcTotal(this.env.treatments,this.env.profile,this.env.time); } function calcTotal(treatments, profile, time) { diff --git a/static/js/client.js b/static/js/client.js index dd278cd07ce..ce1bd1bdfda 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -476,8 +476,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.currentDetails = currentDetails; env.sgv = Number(sgv); env.treatments = treatments; + env.time = time; - // get additional data from data providers + // Update the env through data provider plugins for (var p in NightscoutPlugins) { @@ -485,15 +486,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var plugin = NightscoutPlugins[p]; - var dataProviderEnvironment = {}; - - dataProviderEnvironment.treatments = treatments; - dataProviderEnvironment.profile = profile; - plugin.setEnv(env); if (plugin.isDataProvider) { - var dataFromPlugin = plugin.getData(dataProviderEnvironment,time); + var dataFromPlugin = plugin.getData(); var container = {}; for (var i in dataFromPlugin) { container[i] = dataFromPlugin[i]; @@ -503,7 +499,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - // update data inside the plugins + // update data the plugins sendEnvToPlugins(env); } From 21ca447270360d32bf91c463cfb8ba9b77bae3d7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Apr 2015 22:44:15 -0700 Subject: [PATCH 017/661] make the stale data time input a little smaller to prevent wrapping on iOS --- static/css/drawer.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index 559c69048d1..37174469a04 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -88,7 +88,7 @@ input[type=number]:invalid { } input.timeago-mins { - width: 40px; + width: 25px; } #eventTime { From 83bc16012b1e833cb4fecad672b66ec54c3ee4cd Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 25 Apr 2015 13:35:21 +0300 Subject: [PATCH 018/661] Update once a second, not five times a second --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index ce1bd1bdfda..0210cc20e85 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1648,7 +1648,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBrushToNow(); } updateChart(false); - }, 200); + }, 1000); } function visibilityChanged() { From 7cdb29fdac5b9d8c647c5c1874dde4f0e76b6766 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 3 May 2015 00:05:38 -0700 Subject: [PATCH 019/661] when stoping the alarm also remove the strike through --- static/js/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/client.js b/static/js/client.js index dd35bd3c6e6..88794c25137 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1514,6 +1514,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); stopAlarm(true, ONE_MIN_IN_MS); } From 8fa49f2c06b047a0c7141fa7ed025cc2f5790556 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 3 May 2015 16:17:21 +0300 Subject: [PATCH 020/661] Plugin to calculate and show the cannula age based on latest Site Change event --- bundle/bundle.source.js | 3 ++- lib/cannulaage.js | 53 +++++++++++++++++++++++++++++++++++++++++ lib/websocket.js | 4 +++- 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 lib/cannulaage.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 9a2f145a24a..75804ac9130 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -19,7 +19,8 @@ window.NightscoutPlugins = { iob: require('../lib/iob')(PluginBase), cob: require('../lib/cob')(PluginBase), - bwp: require('../lib/boluswizardpreview')(PluginBase) + bwp: require('../lib/boluswizardpreview')(PluginBase), + cage: require('../lib/cannulaage')(PluginBase) }; // class inheritance to the plugins from the base + map functions over diff --git a/lib/cannulaage.js b/lib/cannulaage.js new file mode 100644 index 00000000000..d601c422ee1 --- /dev/null +++ b/lib/cannulaage.js @@ -0,0 +1,53 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + + var pill = this.currentDetails.find('span.pill.cage'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + var age = 0; + var found = false; + + for (var t in this.env.treatments) + { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") + { + var treatmentDate = new Date(treatment.created_at); + var hours = Math.abs(new Date() - treatmentDate) / 36e5; + hours = Math.round( hours * 10 ) / 10; + + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { age = hours; } + } + } + } + + pill.find('em').text(age + 'h'); + +}; + + +function CAGE(pluginBase) { + pluginBase.call(this); + + return { + updateVisualisation: updateVisualisation, + isDataProvider: false, + isVisualisationProvider: true + + }; +} + +module.exports = CAGE; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index c3da8be29a9..3444c6ae60b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -7,6 +7,7 @@ var ONE_HOUR = 3600000, ONE_MINUTE = 60000, FIVE_MINUTES = 300000, FORTY_MINUTES = 2400000, + ONE_DAY = 86400000, TWO_DAYS = 172800000; var dir2Char = { @@ -162,6 +163,7 @@ function update() { profileData = []; devicestatusData = {}; var earliest_data = now - TWO_DAYS; + var treatment_earliest_data = now - (ONE_DAY*8); async.parallel({ entries: function(callback) { @@ -216,7 +218,7 @@ function update() { }); } , treatments: function(callback) { - var tq = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; + var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; treatments.list(tq, function (err, results) { treatmentData = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); From 3fdd5a6d67668766b1e33a64e324d2f645da6140 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 10:02:55 +0300 Subject: [PATCH 021/661] PluginBase now injects the pills, instead of the plugin touching anything to do with the browser DOM --- bundle/bundle.source.js | 1 + lib/boluswizardpreview.js | 12 ++++-------- lib/cannulaage.js | 14 ++++---------- lib/cob.js | 13 +++---------- lib/pluginbase.js | 18 +++++++++++++++++- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 75804ac9130..88b5c8a5a1d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -27,6 +27,7 @@ for (var p in window.NightscoutPlugins) { var plugin = window.NightscoutPlugins[p]; inherits(plugin, PluginBase); + plugin.name = p; for (var n in PluginBase.prototype) { var item = PluginBase.prototype[n]; diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 169186d2e50..224b2dae877 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -4,13 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - - var pill = this.currentDetails.find('span.pill.bat'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } var bat = 0.0; @@ -41,7 +34,10 @@ function updateVisualisation() { } bat = Math.round(bat * 100) / 100; - pill.find('em').text(bat + 'U'); + + // display text + + this.updateMajorPillText(bat + 'U','BWP'); }; diff --git a/lib/cannulaage.js b/lib/cannulaage.js index d601c422ee1..d36965ad72a 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -4,14 +4,6 @@ function updateVisualisation() { var sgv = this.env.sgv; - - var pill = this.currentDetails.find('span.pill.cage'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - var age = 0; var found = false; @@ -23,7 +15,9 @@ function updateVisualisation() { { var treatmentDate = new Date(treatment.created_at); var hours = Math.abs(new Date() - treatmentDate) / 36e5; - hours = Math.round( hours * 10 ) / 10; + //hours = Math.round( hours * 10 ) / 10; + + hours = Math.round(hours); if (!found) { found = true; @@ -34,7 +28,7 @@ function updateVisualisation() { } } - pill.find('em').text(age + 'h'); + this.updateMajorPillText(age+'h', 'CAGE'); }; diff --git a/lib/cob.js b/lib/cob.js index 587ab117742..acf867d9edd 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -185,17 +185,10 @@ function cobCalc(treatment, lastDecayedBy, time) { } function updateVisualisation() { - - var pill = this.currentDetails.find('span.pill.cob'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; - pill.find('em').text(displayCob + " g"); + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + this.updateMajorPillText(displayCob + " g",'COB'); } diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 72ed50dd9f6..028eed2db45 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -9,12 +9,28 @@ function setEnv(env) { this.env = env; } +function updateMajorPillText(updatedText, label) { + + var pillName = "span.pill." + this.name; + + var pill = this.currentDetails.find(pillName); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + pill.find('em').text(updatedText); +} + function PluginBase() { return { - setEnv: setEnv + setEnv: setEnv, + updateMajorPillText: updateMajorPillText }; } PluginBase.prototype.setEnv = setEnv; +PluginBase.prototype.updateMajorPillText = updateMajorPillText; module.exports = PluginBase; From d17a6d6473af94b54f59f55edb100e6cb527beed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 10:16:00 +0300 Subject: [PATCH 022/661] Separate area for plugin pills --- lib/pluginbase.js | 5 +++-- static/css/main.css | 44 +++++++++++++++++++++++++++++++++++++------- static/index.html | 1 + static/js/client.js | 2 ++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/pluginbase.js b/lib/pluginbase.js index 028eed2db45..d39cdddc225 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -3,6 +3,7 @@ function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; + this.pluginPills = env.pluginPills; this.iob = env.iob; // TODO: clean! @@ -13,11 +14,11 @@ function updateMajorPillText(updatedText, label) { var pillName = "span.pill." + this.name; - var pill = this.currentDetails.find(pillName); + var pill = this.pluginPills.find(pillName); if (!pill || pill.length == 0) { pill = $(''); - this.currentDetails.append(pill); + this.pluginPills.append(pill); } pill.find('em').text(updatedText); diff --git a/static/css/main.css b/static/css/main.css index de90b71f58f..870ce7b9a0f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,9 +29,9 @@ body { .status { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; + height: 200px; vertical-align: middle; - clear: both; + clear: left right; } .bgStatus { @@ -76,10 +76,20 @@ body { font-size: 30px; } +.pluginPills { + font-size: 22px; + margin-top: 5px; + z-index: 500; +} + .currentDetails > span:not(:first-child) { margin-left: 5px; } +.pluginPills > span:not(:first-child) { + margin-left: 5px; +} + .pill { white-space: nowrap; border-radius: 5px; @@ -169,7 +179,7 @@ body { } #chartContainer { - top: 225px; /*(toolbar height + status height)*/ + top: 245px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -347,6 +357,10 @@ div.tooltip { font-size: 20px; } + .bgStatus .pluginPills { + font-size: 16px; + } + .time { font-size: 70px; line-height: 60px; @@ -375,7 +389,7 @@ div.tooltip { } #chartContainer { - top: 185px; + top: 195px; font-size: 14px; } #chartContainer svg { @@ -416,6 +430,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + .time { font-size: 50px; line-height: 40px; @@ -423,7 +441,7 @@ div.tooltip { } #chartContainer { - top: 165px; + top: 175px; } #chartContainer svg { height: calc(100vh - 165px); @@ -470,6 +488,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + #silenceBtn * { font-size: 20px; } @@ -512,7 +534,7 @@ div.tooltip { } #chartContainer { - top: 190px; + top: 200px; } #chartContainer svg { height: calc(100vh - (190px)); @@ -547,7 +569,7 @@ div.tooltip { } #chartContainer { - top: 130px; + top: 140px; font-size: 10px; } #chartContainer svg { @@ -574,6 +596,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .pluginPills { + font-size: 12px; + } + .time { font-size: 50px; line-height: 40px; @@ -617,6 +643,10 @@ div.tooltip { font-size: 15px; } + .bgStatus .currentDetails { + font-size: 12px; + } + #silenceBtn { right: -25px; } diff --git a/static/index.html b/static/index.html index aed6265697a..6f7b9ea0d32 100644 --- a/static/index.html +++ b/static/index.html @@ -58,6 +58,7 @@

Nightscout

  • Silence for 120 minutes
  • +
    diff --git a/static/js/client.js b/static/js/client.js index 0210cc20e85..fc2eb093c8b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -393,6 +393,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') , currentDetails = $('.bgStatus .currentDetails') + , pluginPills = $('.bgStatus .pluginPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -474,6 +475,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var env = {}; env.profile = profile; env.currentDetails = currentDetails; + env.pluginPills = pluginPills; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; From 6436e6bcbcf26225f47e56e2d1fbbe996b30bc34 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 12:09:20 +0300 Subject: [PATCH 023/661] Simplified the code to detect if plugin implements data processing and/or visualisations --- lib/boluswizardpreview.js | 5 +---- lib/cannulaage.js | 5 +---- lib/cob.js | 4 +--- static/js/client.js | 8 ++++++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 224b2dae877..1b837e2d5de 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -46,10 +46,7 @@ function BWP(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation, - isDataProvider: false, - isVisualisationProvider: true - + updateVisualisation: updateVisualisation }; } diff --git a/lib/cannulaage.js b/lib/cannulaage.js index d36965ad72a..9c8f1e036a5 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -37,10 +37,7 @@ function CAGE(pluginBase) { pluginBase.call(this); return { - updateVisualisation: updateVisualisation, - isDataProvider: false, - isVisualisationProvider: true - + updateVisualisation: updateVisualisation }; } diff --git a/lib/cob.js b/lib/cob.js index acf867d9edd..f0bc520190e 100644 --- a/lib/cob.js +++ b/lib/cob.js @@ -203,9 +203,7 @@ function COB(pluginBase) { iobTotal: iobTotal, iobCalc: iobCalc, getData: getData, - updateVisualisation: updateVisualisation, - isDataProvider: true, - isVisualisationProvider: true + updateVisualisation: updateVisualisation }; } diff --git a/static/js/client.js b/static/js/client.js index fc2eb093c8b..3e37e9b7d6d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -490,7 +490,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; plugin.setEnv(env); - if (plugin.isDataProvider) { + // check if the plugin implements processing data + + if (plugin.getData) { var dataFromPlugin = plugin.getData(); var container = {}; for (var i in dataFromPlugin) { @@ -517,7 +519,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; for (var p in NightscoutPlugins) { if (isPluginEnabled(p)) { var plugin = NightscoutPlugins[p]; - if (plugin.isVisualisationProvider) { + + // check if the plugin implements visualisations + if (plugin.updateVisualisation) { plugin.updateVisualisation(); } } From c5ca38ac9ff21868de11f142d6e618ac367feba4 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 4 May 2015 14:02:25 +0300 Subject: [PATCH 024/661] Small cleanup to cannula age code --- lib/cannulaage.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/cannulaage.js b/lib/cannulaage.js index 9c8f1e036a5..e55059956b4 100644 --- a/lib/cannulaage.js +++ b/lib/cannulaage.js @@ -3,7 +3,6 @@ // class methods function updateVisualisation() { - var sgv = this.env.sgv; var age = 0; var found = false; @@ -14,11 +13,8 @@ function updateVisualisation() { if (treatment.eventType == "Site Change") { var treatmentDate = new Date(treatment.created_at); - var hours = Math.abs(new Date() - treatmentDate) / 36e5; - //hours = Math.round( hours * 10 ) / 10; - - hours = Math.round(hours); - + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + if (!found) { found = true; age = hours; From af0b2f73df4afc77d4e12b1efada4940d5f41dd1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 7 May 2015 10:42:56 +0300 Subject: [PATCH 025/661] Revert the timer change --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 3e37e9b7d6d..3e3be4a581e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1654,7 +1654,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBrushToNow(); } updateChart(false); - }, 1000); + }, 200); } function visibilityChanged() { From 2c6b2b0002b4411f30b7a3ed54ab5a2320975fc1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 7 May 2015 12:16:26 +0300 Subject: [PATCH 026/661] Don't assume we're using mmols --- lib/boluswizardpreview.js | 3 ++- lib/pluginbase.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index 1b837e2d5de..fdee01c15c1 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -8,7 +8,8 @@ function updateVisualisation() { var bat = 0.0; // TODO: MMOL - sgv = Number(sgv)/18; + + sgv = this.scaleBg(sgv); // Above target -> calculate insulin dose against target_high diff --git a/lib/pluginbase.js b/lib/pluginbase.js index d39cdddc225..293dc8df9be 100644 --- a/lib/pluginbase.js +++ b/lib/pluginbase.js @@ -24,6 +24,14 @@ function updateMajorPillText(updatedText, label) { pill.find('em').text(updatedText); } +function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } +} + function PluginBase() { return { setEnv: setEnv, @@ -31,6 +39,7 @@ function PluginBase() { }; } +PluginBase.prototype.scaleBg = scaleBg; PluginBase.prototype.setEnv = setEnv; PluginBase.prototype.updateMajorPillText = updateMajorPillText; From e5c6a8512b837adf8492bdcf38f81189314532ea Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 10 May 2015 20:23:40 +0300 Subject: [PATCH 027/661] Better logic for the bolus wizard preview --- lib/boluswizardpreview.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js index fdee01c15c1..43640ddc343 100644 --- a/lib/boluswizardpreview.js +++ b/lib/boluswizardpreview.js @@ -10,28 +10,21 @@ function updateVisualisation() { // TODO: MMOL sgv = this.scaleBg(sgv); - - // Above target -> calculate insulin dose against target_high - - if (sgv > this.profile.target_high) - { - var delta = sgv - this.profile.target_high; - bat = (delta / this.profile.sens) - this.iob.iob; - } - // between targets + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; - if (sgv >= this.profile.target_low && sgv <= this.profile.target_high && this.iob.iob > 0) - { - // ... + if (outcome > this.profile.target_high) { + var delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; } - - // Above target -> calculate insulin dose against target_low - - if (sgv < this.profile.target_low) - { - var delta = this.profile.target_low - sgv; - bat = 0-(delta / this.profile.sens) - this.iob.iob; + + if (outcome < this.profile.target_low) { + + var delta = Math.abs( outcome - this.profile.target_low); + + bat = delta / this.profile.sens * -1; + } bat = Math.round(bat * 100) / 100; From ec1893113f0fed7b074f67db85ec04c775ea9172 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 08:28:14 -0700 Subject: [PATCH 028/661] normalize indents; use hasOwnProperty; minor clean up --- bundle/bundle.source.js | 44 ++++++------- static/js/client.js | 134 ++++++++++++++++++++-------------------- 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 88b5c8a5a1d..1f85b2d5e06 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,18 +1,17 @@ (function () { - - window.Nightscout = window.Nightscout || {}; - // Default features + window.Nightscout = window.Nightscout || {}; + // Default features window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')() }; - // Plugins - - var inherits = require("inherits"); - var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class + // Plugins + + var inherits = require("inherits"); + var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class window.NightscoutPlugins = window.NightscoutPlugins || {}; @@ -22,20 +21,23 @@ bwp: require('../lib/boluswizardpreview')(PluginBase), cage: require('../lib/cannulaage')(PluginBase) }; - // class inheritance to the plugins from the base + map functions over - - for (var p in window.NightscoutPlugins) { - var plugin = window.NightscoutPlugins[p]; - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - var item = PluginBase.prototype[n]; - plugin[n] = item; - } - } - - console.info("Nightscout bundle ready", window.Nightscout); + + // class inheritance to the plugins from the base + map functions over + for (var p in window.NightscoutPlugins) { + if (window.NightscoutPlugins.hasOwnProperty(p)) { + var plugin = window.NightscoutPlugins[p]; + inherits(plugin, PluginBase); + plugin.name = p; + + for (var n in PluginBase.prototype) { + if (PluginBase.prototype.hasOwnProperty(n)) { + plugin[n] = PluginBase.prototype[n]; + } + } + } + } + + console.info("Nightscout bundle ready", window.Nightscout, window.NightscoutPlugins); })(); diff --git a/static/js/client.js b/static/js/client.js index 3e3be4a581e..029a045863f 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,71 +464,73 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - // PLUGIN MANAGEMENT CODE - - function isPluginEnabled(name) { - return app.enabledOptions - && app.enabledOptions.indexOf(name) > -1; - } - - function updatePluginData(sgv, time) { - var env = {}; - env.profile = profile; - env.currentDetails = currentDetails; - env.pluginPills = pluginPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - - // Update the env through data provider plugins - - for (var p in NightscoutPlugins) { - - if (isPluginEnabled(p)) { - - var plugin = NightscoutPlugins[p]; - - plugin.setEnv(env); - - // check if the plugin implements processing data - - if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - container[i] = dataFromPlugin[i]; - } - env[p] = container; - } - } - } - - // update data the plugins - - sendEnvToPlugins(env); - } - - function sendEnvToPlugins(env) { - for (var p in NightscoutPlugins) { - var plugin = NightscoutPlugins[p]; - plugin.setEnv(env); - } - } - - function updatePluginVisualisation() { - for (var p in NightscoutPlugins) { - if (isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; - - // check if the plugin implements visualisations - if (plugin.updateVisualisation) { - plugin.updateVisualisation(); - } - } - } - } - - /// END PLUGIN CODE + // PLUGIN MANAGEMENT CODE + + function isPluginEnabled(name) { + return app.enabledOptions + && app.enabledOptions.indexOf(name) > -1; + } + + function updatePluginData(sgv, time) { + var env = {}; + env.profile = profile; + env.currentDetails = currentDetails; + env.pluginPills = pluginPills; + env.sgv = Number(sgv); + env.treatments = treatments; + env.time = time; + + // Update the env through data provider plugins + + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + + plugin.setEnv(env); + + // check if the plugin implements processing data + + if (plugin.getData) { + var dataFromPlugin = plugin.getData(); + var container = {}; + for (var i in dataFromPlugin) { + if (dataFromPlugin.hasOwnProperty(i)) { + container[i] = dataFromPlugin[i]; + } + } + env[p] = container; + } + } + } + + // update data the plugins + + sendEnvToPlugins(env); + } + + function sendEnvToPlugins(env) { + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p)) { + var plugin = NightscoutPlugins[p]; + plugin.setEnv(env); + } + } + } + + function updatePluginVisualisation() { + for (var p in NightscoutPlugins) { + if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { + var plugin = NightscoutPlugins[p]; + + // check if the plugin implements visualisations + if (plugin.updateVisualisation) { + plugin.updateVisualisation(); + } + } + } + } + + /// END PLUGIN CODE // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m @@ -1325,7 +1327,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } else if (treatment.glucose) { //no units, assume everything is the same - console.warn('found an glucose value with any units, maybe from an old version?', treatment); + console.warn('found a glucose value without any units, maybe from an old version?', treatment); treatmentGlucose = treatment.glucose; } } From c298320dcd31b8aee43f87f2b06089d743396807 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 11:30:00 -0700 Subject: [PATCH 029/661] tons of plugin refactoring moved plugins to new lib/plugins folder, moved most plugins logic out of bundle source, initial steps to be able to toggle visualisations on the client for enabled plugins --- bower.json | 5 +- bundle/bundle.source.js | 55 +++----- bundle/index.js | 2 +- lib/boluswizardpreview.js | 47 ------- lib/cannulaage.js | 40 ------ lib/cob.js | 211 ----------------------------- lib/pebble.js | 2 +- lib/pluginbase.js | 46 ------- lib/plugins/boluswizardpreview.js | 45 +++++++ lib/plugins/cannulaage.js | 43 ++++++ lib/plugins/cob.js | 214 ++++++++++++++++++++++++++++++ lib/plugins/index.js | 78 +++++++++++ lib/{ => plugins}/iob.js | 34 +++-- lib/plugins/pluginbase.js | 46 +++++++ package.json | 1 + static/index.html | 3 + static/js/client.js | 81 +++-------- static/js/ui-utils.js | 14 +- 18 files changed, 502 insertions(+), 465 deletions(-) delete mode 100644 lib/boluswizardpreview.js delete mode 100644 lib/cannulaage.js delete mode 100644 lib/cob.js delete mode 100644 lib/pluginbase.js create mode 100644 lib/plugins/boluswizardpreview.js create mode 100644 lib/plugins/cannulaage.js create mode 100644 lib/plugins/cob.js create mode 100644 lib/plugins/index.js rename lib/{ => plugins}/iob.js (74%) create mode 100644 lib/plugins/pluginbase.js diff --git a/bower.json b/bower.json index 41363897148..0928db8e8d2 100644 --- a/bower.json +++ b/bower.json @@ -7,8 +7,9 @@ "d3": "3.4.3", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", - "tipsy-jmalonzo": "~1.0.1", - "jsSHA": "~1.5.0" + "jsSHA": "~1.5.0", + "lodash": "~3.9.0", + "tipsy-jmalonzo": "~1.0.1" }, "resolutions": { "jquery": "2.1.0" diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 1f85b2d5e06..8b8ed9d3e03 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,43 +1,22 @@ (function () { - window.Nightscout = window.Nightscout || {}; - - // Default features - window.Nightscout = { - units: require('../lib/units')(), - profile: require('../lib/profilefunctions')() - }; - - // Plugins - - var inherits = require("inherits"); - var PluginBase = require('../lib/pluginbase'); // Define any shared functionality in this class - - window.NightscoutPlugins = window.NightscoutPlugins || {}; - - window.NightscoutPlugins = { - iob: require('../lib/iob')(PluginBase), - cob: require('../lib/cob')(PluginBase), - bwp: require('../lib/boluswizardpreview')(PluginBase), - cage: require('../lib/cannulaage')(PluginBase) - }; - - // class inheritance to the plugins from the base + map functions over - for (var p in window.NightscoutPlugins) { - if (window.NightscoutPlugins.hasOwnProperty(p)) { - var plugin = window.NightscoutPlugins[p]; - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - if (PluginBase.prototype.hasOwnProperty(n)) { - plugin[n] = PluginBase.prototype[n]; - } - } - } - } - - console.info("Nightscout bundle ready", window.Nightscout, window.NightscoutPlugins); + window.Nightscout = window.Nightscout || {}; + + // Default features + window.Nightscout = { + units: require('../lib/units')(), + profile: require('../lib/profilefunctions')(), + plugins: require('../lib/plugins/')() + }; + + window.Nightscout.plugins.register({ + iob: require('../lib/plugins/iob'), + cob: require('../lib/plugins/cob'), + bwp: require('../lib/plugins/boluswizardpreview'), + cage: require('../lib/plugins/cannulaage') + }); + + console.info("Nightscout bundle ready", window.Nightscout); })(); diff --git a/bundle/index.js b/bundle/index.js index 4a8cd1563f2..2593c5a4621 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,7 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: __dirname + '/../lib/', + watch: __dirname + '/../lib/plugins/', mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/boluswizardpreview.js b/lib/boluswizardpreview.js deleted file mode 100644 index 43640ddc343..00000000000 --- a/lib/boluswizardpreview.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -// class methods -function updateVisualisation() { - - var sgv = this.env.sgv; - - var bat = 0.0; - - // TODO: MMOL - - sgv = this.scaleBg(sgv); - - var effect = this.iob.iob * this.profile.sens; - var outcome = sgv - effect; - - if (outcome > this.profile.target_high) { - var delta = outcome - this.profile.target_high; - bat = delta / this.profile.sens; - } - - if (outcome < this.profile.target_low) { - - var delta = Math.abs( outcome - this.profile.target_low); - - bat = delta / this.profile.sens * -1; - - } - - bat = Math.round(bat * 100) / 100; - - // display text - - this.updateMajorPillText(bat + 'U','BWP'); - -}; - - -function BWP(pluginBase) { - pluginBase.call(this); - - return { - updateVisualisation: updateVisualisation - }; -} - -module.exports = BWP; \ No newline at end of file diff --git a/lib/cannulaage.js b/lib/cannulaage.js deleted file mode 100644 index e55059956b4..00000000000 --- a/lib/cannulaage.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -// class methods -function updateVisualisation() { - - var age = 0; - var found = false; - - for (var t in this.env.treatments) - { - var treatment = this.env.treatments[t]; - - if (treatment.eventType == "Site Change") - { - var treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); - - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { age = hours; } - } - } - } - - this.updateMajorPillText(age+'h', 'CAGE'); - -}; - - -function CAGE(pluginBase) { - pluginBase.call(this); - - return { - updateVisualisation: updateVisualisation - }; -} - -module.exports = CAGE; \ No newline at end of file diff --git a/lib/cob.js b/lib/cob.js deleted file mode 100644 index f0bc520190e..00000000000 --- a/lib/cob.js +++ /dev/null @@ -1,211 +0,0 @@ -'use strict'; - -function getData() -{ - return this.cobTotal(this.env.treatments,this.env.time); -} - -function cobTotal(treatments, time) { - var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; - var cob=0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - var time = new Date(); - } - - var isDecaying = 0; - var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; - - for (var t in treatments) - { - var treatment = treatments[t]; - if(treatment.carbs && treatment.created_at < time) { - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; - if (decaysin_hr > -10) { - var actStart = this.iobTotal(treatments, lastDecayedBy).activity; - var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart+actEnd)/2; - var delayedCarbs = avgActivity*liverSensRatio*sens/carbratio; - var delayMinutes = Math.round(delayedCarbs/carbs_hr*60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy-time)/1000/60/60; - } - } - - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } - - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - cob = 0; - } - - } - } - var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; - return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: carbs_hr, - rawCarbImpact: rawCarbImpact, - cob: cob - }; -} - - function iobTotal(treatments, time) { - var iob= 0; - var activity = 0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - var time = new Date(); - } - - for (var t in treatments) { - var treatment = treatments[t]; - if(treatment.created_at < time) { - var tIOB = this.iobCalc(treatment, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - }; - return { - iob: iob, - activity: activity - }; - } - - -function carbImpact(rawCarbImpact, insulinImpact) { - var liverSensRatio = 1.0; - var liverCarbImpactMax = 0.7; - var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); - //var liverCarbImpact = liverSensRatio*insulinImpact; - var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); - var totalImpact = netCarbImpact - insulinImpact; - return { - netCarbImpact: netCarbImpact, - totalImpact: totalImpact - } -} - -function iobCalc(treatment, time) { - - var dia=this.profile.dia; - var scaleFactor = 3.0/dia; - var peak = 75; - var sens=this.profile.sens; - var iobContrib, activityContrib; - var t = time; - if (typeof t === 'undefined') { - t = new Date(); - } - - if (treatment.insulin) { - var bolusTime=new Date(treatment.created_at); - var minAgo=scaleFactor*(t-bolusTime)/1000/60; - - if (minAgo < 0) { - iobContrib=0; - activityContrib=0; - } - if (minAgo < peak) { - var x = minAgo/5+1; - iobContrib=treatment.insulin*(1-0.001852*x*x+0.001852*x); - activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; - - } - else if (minAgo < 180) { - var x = (minAgo-75)/5; - iobContrib=treatment.insulin*(0.001323*x*x - .054233*x + .55556); - activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); - } - else { - iobContrib=0; - activityContrib=0; - } - return { - iobContrib: iobContrib, - activityContrib: activityContrib - }; - } - else { - return ''; - } - } - -function cobCalc(treatment, lastDecayedBy, time) { - - var carbs_hr = this.profile.carbs_hr; - var delay = 20; - var carbs_min = carbs_hr / 60; - var isDecaying = 0; - var initialCarbs; - - if (treatment.carbs) { - var carbTime = new Date(treatment.created_at); - - var decayedBy = new Date(carbTime); - var minutesleft = (lastDecayedBy-carbTime)/1000/60; - decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); - if(delay > minutesleft) { - initialCarbs = parseInt(treatment.carbs); - } - else { - initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; - } - var startDecay = new Date(carbTime); - startDecay.setMinutes(carbTime.getMinutes() + delay); - if (time < lastDecayedBy || time > startDecay) { - isDecaying = 1; - } - else { - isDecaying = 0; - } - return { - initialCarbs: initialCarbs, - decayedBy: decayedBy, - isDecaying: isDecaying, - carbTime: carbTime - }; - } - else { - return ''; - } -} - -function updateVisualisation() { - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; - - this.updateMajorPillText(displayCob + " g",'COB'); - -} - - -function COB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - - return { - cobTotal: cobTotal, - cobCalc: cobCalc, - iobTotal: iobTotal, - iobCalc: iobCalc, - getData: getData, - updateVisualisation: updateVisualisation - }; - -} - -module.exports = COB; diff --git a/lib/pebble.js b/lib/pebble.js index ea9b9bbabc7..4a2910dbc52 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -13,7 +13,7 @@ var DIRECTIONS = { , 'RATE OUT OF RANGE': 9 }; -var iob = require("./iob")(); +var iob = require("./plugins/iob")(); var async = require('async'); var units = require('./units')(); diff --git a/lib/pluginbase.js b/lib/pluginbase.js deleted file mode 100644 index 293dc8df9be..00000000000 --- a/lib/pluginbase.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -function setEnv(env) { - this.profile = env.profile; - this.currentDetails = env.currentDetails; - this.pluginPills = env.pluginPills; - this.iob = env.iob; - - // TODO: clean! - this.env = env; -} - -function updateMajorPillText(updatedText, label) { - - var pillName = "span.pill." + this.name; - - var pill = this.pluginPills.find(pillName); - - if (!pill || pill.length == 0) { - pill = $(''); - this.pluginPills.append(pill); - } - - pill.find('em').text(updatedText); -} - -function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } -} - -function PluginBase() { - return { - setEnv: setEnv, - updateMajorPillText: updateMajorPillText - }; -} - -PluginBase.prototype.scaleBg = scaleBg; -PluginBase.prototype.setEnv = setEnv; -PluginBase.prototype.updateMajorPillText = updateMajorPillText; - -module.exports = PluginBase; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js new file mode 100644 index 00000000000..402b4e5b1e5 --- /dev/null +++ b/lib/plugins/boluswizardpreview.js @@ -0,0 +1,45 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var sgv = this.env.sgv; + + var bat = 0.0; + + // TODO: MMOL + + sgv = this.scaleBg(sgv); + + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; + var delta = 0; + + if (outcome > this.profile.target_high) { + delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; + } + + if (outcome < this.profile.target_low) { + delta = Math.abs( outcome - this.profile.target_low); + bat = delta / this.profile.sens * -1; + } + + bat = Math.round(bat * 100) / 100; + + // display text + this.updateMajorPillText(bat + 'U','BWP'); + +} + + +function BWP(pluginBase) { + pluginBase.call(this); + + return { + label: 'Bolus Wizard Preview', + updateVisualisation: updateVisualisation + }; +} + +module.exports = BWP; \ No newline at end of file diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js new file mode 100644 index 00000000000..d7682669d37 --- /dev/null +++ b/lib/plugins/cannulaage.js @@ -0,0 +1,43 @@ +'use strict'; + +// class methods +function updateVisualisation() { + + var age = 0; + var found = false; + + for (var t in this.env.treatments) { + if (this.env.treatments.hasOwnProperty(t)) { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") { + var treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { + age = hours; + } + } + } + } + } + + this.updateMajorPillText(age+'h', 'CAGE'); + +} + + +function CAGE(pluginBase) { + pluginBase.call(this); + + return { + label: 'Cannula Age', + updateVisualisation: updateVisualisation + }; +} + +module.exports = CAGE; \ No newline at end of file diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js new file mode 100644 index 00000000000..472a330bbd4 --- /dev/null +++ b/lib/plugins/cob.js @@ -0,0 +1,214 @@ +'use strict'; + +function getData() { + return this.cobTotal(this.env.treatments,this.env.time); +} + +function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var cob=0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } + + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.carbs && treatment.created_at < time) { + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = this.iobTotal(treatments, lastDecayedBy).activity; + var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + } + } + + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + cob = 0; + } + + } + } + } + var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; + return { + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: cob + }; +} + +function iobTotal(treatments, time) { + var iob= 0; + var activity = 0; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.created_at < time) { + var tIOB = this.iobCalc(treatment, time); + if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; + } + } + } + + return { + iob: iob, + activity: activity + }; +} + + +function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } +} + +function iobCalc(treatment, time) { + + var dia=this.profile.dia; + var scaleFactor = 3.0/dia; + var peak = 75; + var sens=this.profile.sens; + var iobContrib, activityContrib; + var t = time; + if (typeof t === 'undefined') { + t = new Date(); + } + + if (treatment.insulin) { + var bolusTime=new Date(treatment.created_at); + var minAgo=scaleFactor*(t-bolusTime)/1000/60; + + if (minAgo < 0) { + iobContrib=0; + activityContrib=0; + } + + if (minAgo < peak) { + var x1 = minAgo/5+1; + iobContrib=treatment.insulin*(1-0.001852*x1*x1+0.001852*x1); + activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; + + } else if (minAgo < 180) { + var x2 = (minAgo-75)/5; + iobContrib=treatment.insulin*(0.001323*x2*x2 - .054233*x2 + .55556); + activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); + } else { + iobContrib=0; + activityContrib=0; + } + return { + iobContrib: iobContrib, + activityContrib: activityContrib + }; + } + else { + return ''; + } +} + +function cobCalc(treatment, lastDecayedBy, time) { + + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; + + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy-carbTime)/1000/60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); + if(delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; + } + else { + return ''; + } +} + +function updateVisualisation() { + + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + + this.updateMajorPillText(displayCob + " g",'COB'); + +} + + +function COB(pluginBase) { + + if (pluginBase) { pluginBase.call(this); } + + return { + label: 'Carbs-on-Board', + cobTotal: cobTotal, + cobCalc: cobCalc, + iobTotal: iobTotal, + iobCalc: iobCalc, + getData: getData, + updateVisualisation: updateVisualisation + }; + +} + +module.exports = COB; diff --git a/lib/plugins/index.js b/lib/plugins/index.js new file mode 100644 index 00000000000..ba9c73e025a --- /dev/null +++ b/lib/plugins/index.js @@ -0,0 +1,78 @@ +'use strict'; + +var _ = require('lodash') + , inherits = require("inherits") + , PluginBase = require('./pluginbase') // Define any shared functionality in this class + , allPlugins = [] + , enabledPlugins = []; + +function register(all) { + + allPlugins = []; + + for (var p in all) { + if (all.hasOwnProperty(p)) { + var plugin = all[p](PluginBase); + + inherits(plugin, PluginBase); + plugin.name = p; + + for (var n in PluginBase.prototype) { + if (PluginBase.prototype.hasOwnProperty(n)) { + plugin[n] = PluginBase.prototype[n]; + } + } + allPlugins.push(plugin); + } + } + +} + +function clientInit(app) { + enabledPlugins = []; + console.info('NightscoutPlugins init', app); + function isEnabled(plugin) { + return app.enabledOptions + && app.enabledOptions.indexOf(plugin.name) > -1; + } + + _.forEach(allPlugins, function eachPlugin(plugin) { + plugin.enabled = isEnabled(plugin); + if (plugin.enabled) { + enabledPlugins.push(plugin); + } + }); + console.info('Plugins enabled', enabledPlugins); +} + +function setEnv(env) { + _.forEach(enabledPlugins, function eachPlugin(plugin) { + plugin.setEnv(env); + }); +} + +function updateVisualisations() { + _.forEach(enabledPlugins, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(); + }); +} + +function eachPlugin(f) { + _.forEach(allPlugins, f); +} + +function eachEnabledPlugin(f) { + _.forEach(enabledPlugins, f); +} + +function plugins() { + plugins.register = register; + plugins.clientInit = clientInit; + plugins.setEnv = setEnv; + plugins.updateVisualisations = updateVisualisations; + plugins.eachPlugin = eachPlugin; + plugins.eachEnabledPlugin = eachEnabledPlugin; + return plugins; +} + +module.exports = plugins; \ No newline at end of file diff --git a/lib/iob.js b/lib/plugins/iob.js similarity index 74% rename from lib/iob.js rename to lib/plugins/iob.js index ee2f716cecd..dac9ce089de 100644 --- a/lib/iob.js +++ b/lib/plugins/iob.js @@ -1,8 +1,7 @@ 'use strict'; -function getData() -{ - return calcTotal(this.env.treatments,this.env.profile,this.env.time); +function getData() { + return calcTotal(this.env.treatments,this.env.profile,this.env.time); } function calcTotal(treatments, profile, time) { @@ -50,13 +49,13 @@ function calcTreatment(treatment, profile, time) { var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; if (minAgo < peak) { - var x = minAgo / 5 + 1; - iobContrib = treatment.insulin * (1 - 0.001852 * x * x + 0.001852 * x); + var x1 = minAgo / 5 + 1; + iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; } else if (minAgo < 180) { - var x = (minAgo - 75) / 5; - iobContrib = treatment.insulin * (0.001323 * x * x - .054233 * x + .55556); + var x2 = (minAgo - 75) / 5; + iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); } else { iobContrib = 0; @@ -73,24 +72,23 @@ function calcTreatment(treatment, profile, time) { } function updateVisualisation() { - - var pill = this.currentDetails.find('span.pill.iob'); - - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } - - pill.find('em').text(this.iob.display + 'U'); - + var pill = this.currentDetails.find('span.pill.iob'); + + if (!pill || pill.length == 0) { + pill = $(''); + this.currentDetails.append(pill); + } + + pill.find('em').text(this.iob.display + 'U'); } function IOB(pluginBase) { if (pluginBase) { pluginBase.call(this); } - + return { + label: 'Insulin-on-Board', calcTotal: calcTotal, getData: getData, updateVisualisation: updateVisualisation, diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js new file mode 100644 index 00000000000..29b8724e617 --- /dev/null +++ b/lib/plugins/pluginbase.js @@ -0,0 +1,46 @@ +'use strict'; + +function setEnv(env) { + this.profile = env.profile; + this.currentDetails = env.currentDetails; + this.pluginPills = env.pluginPills; + this.iob = env.iob; + + // TODO: clean! + this.env = env; +} + +function updateMajorPillText(updatedText, label) { + + var pillName = "span.pill." + this.name; + + var pill = this.pluginPills.find(pillName); + + if (!pill || pill.length == 0) { + pill = $(''); + this.pluginPills.append(pill); + } + + pill.find('em').text(updatedText); +} + +function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } +} + +function PluginBase() { + return { + setEnv: setEnv, + updateMajorPillText: updateMajorPillText + }; +} + +PluginBase.prototype.scaleBg = scaleBg; +PluginBase.prototype.setEnv = setEnv; +PluginBase.prototype.updateMajorPillText = updateMajorPillText; + +module.exports = PluginBase; diff --git a/package.json b/package.json index 3deba772972..80267e0ba77 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", "inherits": "~2.0.1", + "lodash": "^3.9.1", "long": "~2.2.3", "mongodb": "^1.4.7", "moment": "2.8.1", diff --git a/static/index.html b/static/index.html index 6f7b9ea0d32..5b25db9df83 100644 --- a/static/index.html +++ b/static/index.html @@ -132,6 +132,9 @@

    Nightscout

    +
    +
    Enable Plugins
    +
    diff --git a/static/js/client.js b/static/js/client.js index 029a045863f..f30a80804f1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,14 +464,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - // PLUGIN MANAGEMENT CODE - - function isPluginEnabled(name) { - return app.enabledOptions - && app.enabledOptions.indexOf(name) > -1; - } - - function updatePluginData(sgv, time) { + function updatePlugins(sgv, time) { var env = {}; env.profile = profile; env.currentDetails = currentDetails; @@ -482,56 +475,27 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Update the env through data provider plugins - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + plugin.setEnv(env); - plugin.setEnv(env); - - // check if the plugin implements processing data - - if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - if (dataFromPlugin.hasOwnProperty(i)) { - container[i] = dataFromPlugin[i]; - } + // check if the plugin implements processing data + if (plugin.getData) { + var dataFromPlugin = plugin.getData(); + var container = {}; + for (var i in dataFromPlugin) { + if (dataFromPlugin.hasOwnProperty(i)) { + container[i] = dataFromPlugin[i]; } - env[p] = container; } + env[plugin.name] = container; } - } - - // update data the plugins - - sendEnvToPlugins(env); - } - - function sendEnvToPlugins(env) { - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p)) { - var plugin = NightscoutPlugins[p]; - plugin.setEnv(env); - } - } - } - - function updatePluginVisualisation() { - for (var p in NightscoutPlugins) { - if (NightscoutPlugins.hasOwnProperty(p) && isPluginEnabled(p)) { - var plugin = NightscoutPlugins[p]; + }); - // check if the plugin implements visualisations - if (plugin.updateVisualisation) { - plugin.updateVisualisation(); - } - } - } + // update data for all the plugins + Nightscout.plugins.setEnv(env); + Nightscout.plugins.updateVisualisations(); } - /// END PLUGIN CODE - // predict for retrospective data // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m // of data instead of 5, which eliminates the incorrect and misleading predictions generated when @@ -580,10 +544,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - // update plugins - - updatePluginData(focusPoint.y,retroTime); - updatePluginVisualisation(); + // update plugins + + updatePlugins(focusPoint.y, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -618,10 +581,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); - // update plugins - - updatePluginData(latestSGV.y,nowDate); - updatePluginVisualisation(); + // update plugins + + updatePlugins(latestSGV.y, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } @@ -1842,6 +1804,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , careportalEnabled: xhr.careportalEnabled , defaults: xhr.defaults }; + Nightscout.plugins.clientInit(app); } }).done(function() { $('.appName').text(app.name); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 7b1c4a54ab7..2e9f0be010f 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -94,8 +94,18 @@ function getBrowserSettings(storage) { } else { $('#12-browser').prop('checked', true); } - } - catch(err) { + + var enablePlugins = $('#enable-plugins'); + Nightscout.plugins.eachPlugin(function each(plugin) { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + enablePlugins.append(dd); + dd.find('input').prop('checked', plugin.enabled); + console.info('appending plugin dd', dd); + }); + + + } catch(err) { console.error(err); showLocalstorageError(); } From 8625ec0c7dc662bb53f4e04b424d79056cd0f75a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 11:37:20 -0700 Subject: [PATCH 030/661] fix iob tests --- tests/iob.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/iob.test.js b/tests/iob.test.js index 58db5edaa7d..042141dd7cc 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -3,7 +3,7 @@ var should = require('should'); var FIVE_MINS = 10 * 60 * 1000; describe('IOB', function ( ) { - var iob = require('../lib/iob')(); + var iob = require('../lib/plugins/iob')(); it('should calculate IOB', function() { From 02d5c387b7b0d3e674b22d47345ace9ff8801b02 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 12:04:11 -0700 Subject: [PATCH 031/661] not using lodash via bower yet --- bower.json | 1 - 1 file changed, 1 deletion(-) diff --git a/bower.json b/bower.json index 0928db8e8d2..372c86fdf38 100644 --- a/bower.json +++ b/bower.json @@ -8,7 +8,6 @@ "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", - "lodash": "~3.9.0", "tipsy-jmalonzo": "~1.0.1" }, "resolutions": { From f64f7ef1ac9cfa378880cdf7dc05f0ee0ac006f2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 12:08:30 -0700 Subject: [PATCH 032/661] remove some redundant comments after refactoring --- bundle/bundle.source.js | 1 - static/js/client.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 8b8ed9d3e03..2782c0304c1 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -2,7 +2,6 @@ window.Nightscout = window.Nightscout || {}; - // Default features window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')(), diff --git a/static/js/client.js b/static/js/client.js index f30a80804f1..638510f12f3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -544,8 +544,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - // update plugins - updatePlugins(focusPoint.y, retroTime); $('#currentTime') @@ -581,8 +579,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateBGDelta(prevSGV, latestSGV); - // update plugins - updatePlugins(latestSGV.y, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); From 95ed7e44812aae703278a711ce44416fd312c2d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 24 May 2015 13:59:00 -0700 Subject: [PATCH 033/661] show plugin setting works, lots more refactoring --- README.md | 1 + bundle/bundle.source.js | 8 +- env.js | 1 + lib/plugins/boluswizardpreview.js | 4 +- lib/plugins/cannulaage.js | 4 +- lib/plugins/cob.js | 7 +- lib/plugins/index.js | 60 +-- lib/plugins/iob.js | 6 +- lib/plugins/pluginbase.js | 5 +- package.json | 1 - static/index.html | 4 +- static/js/client.js | 11 +- static/js/ui-utils.js | 644 +++++++++++++++--------------- 13 files changed, 379 insertions(+), 377 deletions(-) diff --git a/README.md b/README.md index a635bc91d92..136fd3cede1 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm + * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled ## Setting environment variables diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2782c0304c1..bb601616de4 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,10 +9,10 @@ }; window.Nightscout.plugins.register({ - iob: require('../lib/plugins/iob'), - cob: require('../lib/plugins/cob'), - bwp: require('../lib/plugins/boluswizardpreview'), - cage: require('../lib/plugins/cannulaage') + iob: require('../lib/plugins/iob')(), + cob: require('../lib/plugins/cob')(), + bwp: require('../lib/plugins/boluswizardpreview')(), + cage: require('../lib/plugins/cannulaage')() }); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/env.js b/env.js index 5c06e5c9a0a..a0f381031ff 100644 --- a/env.js +++ b/env.js @@ -92,6 +92,7 @@ function config ( ) { env.defaults.alarmTimeAgoWarnMins = readENV('ALARM_TIMEAGO_WARN_MINS', env.defaults.alarmTimeAgoWarnMins); env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent); env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); + env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); //console.log(JSON.stringify(env.defaults)); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 402b4e5b1e5..adce8df2d5b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -33,9 +33,7 @@ function updateVisualisation() { } -function BWP(pluginBase) { - pluginBase.call(this); - +function BWP() { return { label: 'Bolus Wizard Preview', updateVisualisation: updateVisualisation diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index d7682669d37..2d218f23bee 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -31,9 +31,7 @@ function updateVisualisation() { } -function CAGE(pluginBase) { - pluginBase.call(this); - +function CAGE() { return { label: 'Cannula Age', updateVisualisation: updateVisualisation diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 472a330bbd4..d49f7f8cf0b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -43,7 +43,7 @@ function cobTotal(treatments, time) { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } else { @@ -195,10 +195,7 @@ function updateVisualisation() { } -function COB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - +function COB() { return { label: 'Carbs-on-Board', cobTotal: cobTotal, diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ba9c73e025a..ce14b1edefa 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,8 +1,7 @@ 'use strict'; var _ = require('lodash') - , inherits = require("inherits") - , PluginBase = require('./pluginbase') // Define any shared functionality in this class + , PluginBase = require('./pluginbase')() // Define any shared functionality in this class , allPlugins = [] , enabledPlugins = []; @@ -10,21 +9,11 @@ function register(all) { allPlugins = []; - for (var p in all) { - if (all.hasOwnProperty(p)) { - var plugin = all[p](PluginBase); - - inherits(plugin, PluginBase); - plugin.name = p; - - for (var n in PluginBase.prototype) { - if (PluginBase.prototype.hasOwnProperty(n)) { - plugin[n] = PluginBase.prototype[n]; - } - } - allPlugins.push(plugin); - } - } + _.forIn(all, function eachPlugin(plugin, name) { + plugin.name = name; + _.extend(plugin, PluginBase); + allPlugins.push(plugin); + }); } @@ -51,8 +40,8 @@ function setEnv(env) { }); } -function updateVisualisations() { - _.forEach(enabledPlugins, function eachPlugin(plugin) { +function updateVisualisations(clientSettings) { + eachShownPlugin(clientSettings, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(); }); } @@ -62,17 +51,34 @@ function eachPlugin(f) { } function eachEnabledPlugin(f) { - _.forEach(enabledPlugins, f); + _.forEach(enabledPlugins, f); +} + +function eachShownPlugin(clientSettings, f) { + var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + }); + + _.forEach(filtered, f); +} + +function enabledPluginNames() { + return _.map(enabledPlugins, function mapped(plugin) { + return plugin.name; + }).join(' '); } function plugins() { - plugins.register = register; - plugins.clientInit = clientInit; - plugins.setEnv = setEnv; - plugins.updateVisualisations = updateVisualisations; - plugins.eachPlugin = eachPlugin; - plugins.eachEnabledPlugin = eachEnabledPlugin; - return plugins; + return { + register: register + , clientInit: clientInit + , setEnv: setEnv + , updateVisualisations: updateVisualisations + , eachPlugin: eachPlugin + , eachEnabledPlugin: eachEnabledPlugin + , eachShownPlugin: eachShownPlugin + , enabledPluginNames: enabledPluginNames + } } module.exports = plugins; \ No newline at end of file diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index dac9ce089de..6722e966e15 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -83,10 +83,7 @@ function updateVisualisation() { } -function IOB(pluginBase) { - - if (pluginBase) { pluginBase.call(this); } - +function IOB() { return { label: 'Insulin-on-Board', calcTotal: calcTotal, @@ -95,7 +92,6 @@ function IOB(pluginBase) { isDataProvider: true, isVisualisationProvider: true }; - } module.exports = IOB; \ No newline at end of file diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 29b8724e617..a154801cf41 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -35,12 +35,9 @@ function scaleBg(bg) { function PluginBase() { return { setEnv: setEnv, + scaleBg: scaleBg, updateMajorPillText: updateMajorPillText }; } -PluginBase.prototype.scaleBg = scaleBg; -PluginBase.prototype.setEnv = setEnv; -PluginBase.prototype.updateMajorPillText = updateMajorPillText; - module.exports = PluginBase; diff --git a/package.json b/package.json index 80267e0ba77..605edb83489 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "express-extension-to-accept": "0.0.2", "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", - "inherits": "~2.0.1", "lodash": "^3.9.1", "long": "~2.2.3", "mongodb": "^1.4.7", diff --git a/static/index.html b/static/index.html index 5b25db9df83..df6c473f343 100644 --- a/static/index.html +++ b/static/index.html @@ -132,8 +132,8 @@

    Nightscout

    -
    -
    Enable Plugins
    +
    +
    Show Plugins
    diff --git a/static/js/client.js b/static/js/client.js index 638510f12f3..d4624fc07b7 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -473,9 +473,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.treatments = treatments; env.time = time; - // Update the env through data provider plugins - - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + Nightscout.plugins.eachShownPlugin(browserSettings, function updateEachPlugin(plugin) { + // Update the env through data provider plugins plugin.setEnv(env); // check if the plugin implements processing data @@ -491,9 +490,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); - // update data for all the plugins + // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnv(env); - Nightscout.plugins.updateVisualisations(); + Nightscout.plugins.updateVisualisations(browserSettings); } // predict for retrospective data @@ -1800,7 +1799,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , careportalEnabled: xhr.careportalEnabled , defaults: xhr.defaults }; - Nightscout.plugins.clientInit(app); } }).done(function() { $('.appName').text(app.name); @@ -1810,6 +1808,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('.serverSettings').show(); } $('#treatmentDrawerToggle').toggle(app.careportalEnabled); + Nightscout.plugins.clientInit(app); browserSettings = getBrowserSettings(browserStorage); init(); }); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 2e9f0be010f..50acb03dc56 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -3,430 +3,440 @@ var openDraw = null; function rawBGsEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; } function getBrowserSettings(storage) { - var json = {}; - - function scaleBg(bg) { - if (json.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } + var json = {}; + + function scaleBg(bg) { + if (json.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function appendThresholdValue(threshold) { + return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + } + + try { + var json = { + 'units': storage.get('units'), + 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), + 'alarmHigh': storage.get('alarmHigh'), + 'alarmLow': storage.get('alarmLow'), + 'alarmUrgentLow': storage.get('alarmUrgentLow'), + 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), + 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), + 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), + 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), + 'nightMode': storage.get('nightMode'), + 'showRawbg': storage.get('showRawbg'), + 'customTitle': storage.get('customTitle'), + 'theme': storage.get('theme'), + 'timeFormat': storage.get('timeFormat'), + 'showPlugins': storage.get('showPlugins') + }; + + // Default browser units to server units if undefined. + json.units = setDefault(json.units, app.units); + if (json.units == 'mmol') { + $('#mmol-browser').prop('checked', true); + } else { + $('#mgdl-browser').prop('checked', true); + } + + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); + $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); + $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); + $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); + + json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); + $('#nightmode-browser').prop('checked', json.nightMode); + + if (rawBGsEnabled()) { + $('#show-rawbg-option').show(); + json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); + $('#show-rawbg-' + json.showRawbg).prop('checked', true); + } else { + json.showRawbg = 'never'; + $('#show-rawbg-option').hide(); } - function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); + $('h1.customTitle').text(json.customTitle); + $('input#customTitle').prop('value', json.customTitle); + + json.theme = setDefault(json.theme, app.defaults.theme); + if (json.theme == 'colors') { + $('#theme-colors-browser').prop('checked', true); + } else { + $('#theme-default-browser').prop('checked', true); } - try { - var json = { - 'units': storage.get('units'), - 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), - 'alarmHigh': storage.get('alarmHigh'), - 'alarmLow': storage.get('alarmLow'), - 'alarmUrgentLow': storage.get('alarmUrgentLow'), - 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), - 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), - 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), - 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), - 'nightMode': storage.get('nightMode'), - 'showRawbg': storage.get('showRawbg'), - 'customTitle': storage.get('customTitle'), - 'theme': storage.get('theme'), - 'timeFormat': storage.get('timeFormat') - }; - - // Default browser units to server units if undefined. - json.units = setDefault(json.units, app.units); - if (json.units == 'mmol') { - $('#mmol-browser').prop('checked', true); - } else { - $('#mgdl-browser').prop('checked', true); - } - - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); - $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); - $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); - $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); - $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - - json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); - $('#nightmode-browser').prop('checked', json.nightMode); - - if (rawBGsEnabled()) { - $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); - $('#show-rawbg-' + json.showRawbg).prop('checked', true); - } else { - json.showRawbg = 'never'; - $('#show-rawbg-option').hide(); - } - - json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); - $('h1.customTitle').text(json.customTitle); - $('input#customTitle').prop('value', json.customTitle); - - json.theme = setDefault(json.theme, app.defaults.theme); - if (json.theme == 'colors') { - $('#theme-colors-browser').prop('checked', true); - } else { - $('#theme-default-browser').prop('checked', true); - } - - json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); - - if (json.timeFormat == '24') { - $('#24-browser').prop('checked', true); - } else { - $('#12-browser').prop('checked', true); - } - - var enablePlugins = $('#enable-plugins'); - Nightscout.plugins.eachPlugin(function each(plugin) { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - enablePlugins.append(dd); - dd.find('input').prop('checked', plugin.enabled); - console.info('appending plugin dd', dd); - }); - - - } catch(err) { - console.error(err); - showLocalstorageError(); + json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); + + if (json.timeFormat == '24') { + $('#24-browser').prop('checked', true); + } else { + $('#12-browser').prop('checked', true); } - return json; + json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); + var showPluginsSettings = $('#show-plugins'); + Nightscout.plugins.eachPlugin(function each(plugin) { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + }); + + + } catch(err) { + console.error(err); + showLocalstorageError(); + } + + return json; } function setDefault(variable, defaultValue) { - if (typeof(variable) === 'object') { - return defaultValue; - } - return variable; + if (typeof(variable) === 'object') { + return defaultValue; + } + return variable; } function storeInBrowser(data) { - for (var k in data) { - if (data.hasOwnProperty(k)) { - browserStorage.set(k, data[k]); - } + for (var k in data) { + if (data.hasOwnProperty(k)) { + browserStorage.set(k, data[k]); } + } } function getQueryParms() { - var params = {}; - if (location.search) { - location.search.substr(1).split('&').forEach(function(item) { - params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); - }); - } - return params; + var params = {}; + if (location.search) { + location.search.substr(1).split('&').forEach(function(item) { + params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' '); + }); + } + return params; } function isTouch() { - try { document.createEvent('TouchEvent'); return true; } - catch (e) { return false; } + try { document.createEvent('TouchEvent'); return true; } + catch (e) { return false; } } function closeDrawer(id, callback) { - openDraw = null; - $("html, body").animate({ scrollTop: 0 }); - $(id).animate({right: '-300px'}, 300, function () { - $(id).css('display', 'none'); - if (callback) callback(); - }); + openDraw = null; + $("html, body").animate({ scrollTop: 0 }); + $(id).animate({right: '-300px'}, 300, function () { + $(id).css('display', 'none'); + if (callback) callback(); + }); } function toggleDrawer(id, openCallback, closeCallback) { - function openDrawer(id, callback) { - function closeOpenDraw(callback) { - if (openDraw) { - closeDrawer(openDraw, callback); - } else { - callback() - } - } - - closeOpenDraw(function () { - openDraw = id; - $(id).css('display', 'block').animate({right: '0'}, 300, function () { - if (callback) callback(); - }); - }); - + function openDrawer(id, callback) { + function closeOpenDraw(callback) { + if (openDraw) { + closeDrawer(openDraw, callback); + } else { + callback() + } } - if (openDraw == id) { - closeDrawer(id, closeCallback); - } else { - openDrawer(id, openCallback); - } + closeOpenDraw(function () { + openDraw = id; + $(id).css('display', 'block').animate({right: '0'}, 300, function () { + if (callback) callback(); + }); + }); + + } + + if (openDraw == id) { + closeDrawer(id, closeCallback); + } else { + openDrawer(id, openCallback); + } } function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(currentTime()); + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(currentTime()); } function currentTime() { - var now = new Date(); - var hours = now.getHours(); - var minutes = now.getMinutes(); + var now = new Date(); + var hours = now.getHours(); + var minutes = now.getMinutes(); - if (hours<10) hours = '0' + hours; - if (minutes<10) minutes = '0' + minutes; + if (hours<10) hours = '0' + hours; + if (minutes<10) minutes = '0' + minutes; - return ''+ hours + ':' + minutes; + return ''+ hours + ':' + minutes; } function formatTime(date) { - var hours = date.getHours(); - var minutes = date.getMinutes(); - var ampm = hours >= 12 ? 'pm' : 'am'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' - minutes = minutes < 10 ? '0' + minutes : minutes; - return hours + ':' + minutes + ' ' + ampm; + var hours = date.getHours(); + var minutes = date.getMinutes(); + var ampm = hours >= 12 ? 'pm' : 'am'; + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + minutes = minutes < 10 ? '0' + minutes : minutes; + return hours + ':' + minutes + ' ' + ampm; } function closeNotification() { - var notify = $('#notification'); - notify.hide(); - notify.find('span').html(''); + var notify = $('#notification'); + notify.hide(); + notify.find('span').html(''); } function showNotification(note, type) { - var notify = $('#notification'); - notify.hide(); + var notify = $('#notification'); + notify.hide(); - // Notification types: 'info', 'warn', 'success', 'urgent'. - // - default: 'urgent' - notify.removeClass('info warn urgent'); - notify.addClass(type ? type : 'urgent'); + // Notification types: 'info', 'warn', 'success', 'urgent'. + // - default: 'urgent' + notify.removeClass('info warn urgent'); + notify.addClass(type ? type : 'urgent'); - notify.find('span').html(note); - notify.css('left', 'calc(50% - ' + (notify.width() / 2) + 'px)'); - notify.show(); + notify.find('span').html(note); + notify.css('left', 'calc(50% - ' + (notify.width() / 2) + 'px)'); + notify.show(); } function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' - $('.browserSettings').html('Settings'+msg+''); - $('#save').hide(); + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' + $('.browserSettings').html('Settings'+msg+''); + $('#save').hide(); } function treatmentSubmit(event) { - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = $('#preBolus').val(); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = $('#preBolus').val(); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + var eventTimeDisplay = ''; + if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { + var value = $('#eventTimeValue').val(); + var eventTimeParts = value.split(':'); + data.eventTime = new Date(); + data.eventTime.setHours(eventTimeParts[0]); + data.eventTime.setMinutes(eventTimeParts[1]); + data.eventTime.setSeconds(0); + data.eventTime.setMilliseconds(0); + eventTimeDisplay = formatTime(data.eventTime); } - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { - var value = $('#eventTimeValue').val(); - var eventTimeParts = value.split(':'); - data.eventTime = new Date(); - data.eventTime.setHours(eventTimeParts[0]); - data.eventTime.setMinutes(eventTimeParts[1]); - data.eventTime.setSeconds(0); - data.eventTime.setMilliseconds(0); - eventTimeDisplay = formatTime(data.eventTime); - } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType + - '\nCarbs Given: ' + data.carbs + - '\nInsulin Given: ' + data.insulin + - '\nPre Bolus: ' + data.preBolus + - '\nNotes: ' + data.notes + - '\nEntered By: ' + data.enteredBy + - '\nEvent Time: ' + eventTimeDisplay); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } + var dataJson = JSON.stringify(data, null, ' '); + + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType + + '\nCarbs Given: ' + data.carbs + + '\nInsulin Given: ' + data.insulin + + '\nPre Bolus: ' + data.preBolus + + '\nNotes: ' + data.notes + + '\nEntered By: ' + data.enteredBy + + '\nEvent Time: ' + eventTimeDisplay); + + if (ok) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); } + } - if (event) { - event.preventDefault(); - } + if (event) { + event.preventDefault(); + } } var querystring = getQueryParms(); function Dropdown(el) { - this.ddmenuitem = 0; + this.ddmenuitem = 0; - this.$el = $(el); - var that = this; + this.$el = $(el); + var that = this; - $(document).click(function() { that.close(); }); + $(document).click(function() { that.close(); }); } Dropdown.prototype.close = function () { - if (this.ddmenuitem) { - this.ddmenuitem.css('visibility', 'hidden'); - this.ddmenuitem = 0; - } + if (this.ddmenuitem) { + this.ddmenuitem.css('visibility', 'hidden'); + this.ddmenuitem = 0; + } }; Dropdown.prototype.open = function (e) { - this.close(); - this.ddmenuitem = $(this.$el).css('visibility', 'visible'); - e.stopPropagation(); + this.close(); + this.ddmenuitem = $(this.$el).css('visibility', 'visible'); + e.stopPropagation(); }; $('#drawerToggle').click(function(event) { - toggleDrawer('#drawer'); - event.preventDefault(); + toggleDrawer('#drawer'); + event.preventDefault(); }); $('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); + event.preventDefault(); }); $('#treatmentDrawer').find('button').click(treatmentSubmit); $('#eventTime input:radio').change(function (){ - if ($('#othertime').attr('checked')) { - $('#eventTimeValue').focus(); - } + if ($('#othertime').attr('checked')) { + $('#eventTimeValue').focus(); + } }); $('#eventTimeValue').focus(function () { - $('#othertime').attr('checked', 'checked'); + $('#othertime').attr('checked', 'checked'); }); $('#notification').click(function(event) { - closeNotification(); - event.preventDefault(); + closeNotification(); + event.preventDefault(); }); $('#save').click(function(event) { - storeInBrowser({ - 'units': $('input:radio[name=units-browser]:checked').val(), - 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), - 'alarmHigh': $('#alarm-high-browser').prop('checked'), - 'alarmLow': $('#alarm-low-browser').prop('checked'), - 'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'), - 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'), - 'alarmTimeAgoWarnMins': parseInt($('#alarm-timeagowarnmins-browser').val()) || 15, - 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'), - 'alarmTimeAgoUrgentMins': parseInt($('#alarm-timeagourgentmins-browser').val()) || 30, - 'nightMode': $('#nightmode-browser').prop('checked'), - 'showRawbg': $('input:radio[name=show-rawbg]:checked').val(), - 'customTitle': $('input#customTitle').prop('value'), - 'theme': $('input:radio[name=theme-browser]:checked').val(), - 'timeFormat': $('input:radio[name=timeformat-browser]:checked').val() + function checkedPluginNames() { + var checkedPlugins = [] + $('#show-plugins input:checked').each(function eachPluginCheckbox(index, checkbox) { + checkedPlugins.push($(checkbox).val()); }); - - event.preventDefault(); - reload(); + return checkedPlugins.join(' '); + } + + storeInBrowser({ + 'units': $('input:radio[name=units-browser]:checked').val(), + 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), + 'alarmHigh': $('#alarm-high-browser').prop('checked'), + 'alarmLow': $('#alarm-low-browser').prop('checked'), + 'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'), + 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'), + 'alarmTimeAgoWarnMins': parseInt($('#alarm-timeagowarnmins-browser').val()) || 15, + 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'), + 'alarmTimeAgoUrgentMins': parseInt($('#alarm-timeagourgentmins-browser').val()) || 30, + 'nightMode': $('#nightmode-browser').prop('checked'), + 'showRawbg': $('input:radio[name=show-rawbg]:checked').val(), + 'customTitle': $('input#customTitle').prop('value'), + 'theme': $('input:radio[name=theme-browser]:checked').val(), + 'timeFormat': $('input:radio[name=timeformat-browser]:checked').val(), + 'showPlugins': checkedPluginNames() + }); + + event.preventDefault(); + reload(); }); $('#useDefaults').click(function(event) { - //remove all known settings, since there might be something else is in localstorage - var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoWarnMins', 'alarmTimeAgoUrgent', 'alarmTimeAgoUrgentMins', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat']; - settings.forEach(function(setting) { - browserStorage.remove(setting); - }); - event.preventDefault(); - reload(); + //remove all known settings, since there might be something else is in localstorage + var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoWarnMins', 'alarmTimeAgoUrgent', 'alarmTimeAgoUrgentMins', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat', 'showPlugins']; + settings.forEach(function(setting) { + browserStorage.remove(setting); + }); + event.preventDefault(); + reload(); }); function reload() { - // reload for changes to take effect - // -- strip '#' so form submission does not fail - var url = window.location.href; - url = url.replace(/#$/, ''); - window.location = url; + // reload for changes to take effect + // -- strip '#' so form submission does not fail + var url = window.location.href; + url = url.replace(/#$/, ''); + window.location = url; } $(function() { - // Tooltips can remain in the way on touch screens. - var notTouchScreen = (!isTouch()); - if (notTouchScreen) { - $('.tip').tipsy(); - } else { - // Drawer info tips should be displayed on touchscreens. - $('#drawer').find('.tip').tipsy(); - } - $.fn.tipsy.defaults = { - fade: true, - gravity: 'n', - opacity: 0.75 - }; - - if (querystring.notify) { - showNotification(querystring.notify, querystring.notifytype); - } - - if (querystring.drawer) { - openDrawer('#drawer'); - } + // Tooltips can remain in the way on touch screens. + var notTouchScreen = (!isTouch()); + if (notTouchScreen) { + $('.tip').tipsy(); + } else { + // Drawer info tips should be displayed on touchscreens. + $('#drawer').find('.tip').tipsy(); + } + $.fn.tipsy.defaults = { + fade: true, + gravity: 'n', + opacity: 0.75 + }; + + if (querystring.notify) { + showNotification(querystring.notify, querystring.notifytype); + } + + if (querystring.drawer) { + openDrawer('#drawer'); + } }); From 736a32187b67b51bdac4937619651f5226cd236d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 01:29:32 -0700 Subject: [PATCH 034/661] added tooltips to plugin pills for extra info; lots more refactoring --- bundle/bundle.source.js | 2 + bundle/index.js | 2 +- lib/nsutils.js | 21 +++ lib/plugins/boluswizardpreview.js | 58 +++--- lib/plugins/cannulaage.js | 56 +++--- lib/plugins/cob.js | 300 ++++++++++++------------------ lib/plugins/index.js | 138 +++++++------- lib/plugins/iob.js | 155 +++++++-------- lib/plugins/pluginbase.js | 34 +++- static/js/client.js | 17 +- 10 files changed, 392 insertions(+), 391 deletions(-) create mode 100644 lib/nsutils.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index bb601616de4..85a1fd42b28 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -8,6 +8,8 @@ plugins: require('../lib/plugins/')() }; + console.info('plugins', window.Nightscout.plugins); + window.Nightscout.plugins.register({ iob: require('../lib/plugins/iob')(), cob: require('../lib/plugins/cob')(), diff --git a/bundle/index.js b/bundle/index.js index 2593c5a4621..4a8cd1563f2 100644 --- a/bundle/index.js +++ b/bundle/index.js @@ -5,7 +5,7 @@ var browserify_express = require('browserify-express'); function bundle() { return browserify_express({ entry: __dirname + '/bundle.source.js', - watch: __dirname + '/../lib/plugins/', + watch: __dirname + '/../lib/', mount: '/public/js/bundle.js', verbose: true, //minify: true, diff --git a/lib/nsutils.js b/lib/nsutils.js new file mode 100644 index 00000000000..c96f859575b --- /dev/null +++ b/lib/nsutils.js @@ -0,0 +1,21 @@ +'use strict'; + +function init() { + + function nsutils() { + return nsutils; + } + + nsutils.toFixed = function toFixed(value) { + if (value === 0) { + return '0'; + } else { + var fixed = value.toFixed(2); + return fixed == '-0.00' ? '0.00' : fixed; + } + }; + + return nsutils(); +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index adce8df2d5b..efe2a36f59a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,43 +1,49 @@ 'use strict'; -// class methods -function updateVisualisation() { +var nsutils = require('../nsutils')(); - var sgv = this.env.sgv; +function init() { - var bat = 0.0; + function bwp() { + return bwp; + } - // TODO: MMOL + bwp.label = 'Bolus Wizard Preview'; - sgv = this.scaleBg(sgv); + bwp.updateVisualisation = function updateVisualisation() { - var effect = this.iob.iob * this.profile.sens; - var outcome = sgv - effect; - var delta = 0; + var sgv = this.env.sgv; - if (outcome > this.profile.target_high) { - delta = outcome - this.profile.target_high; - bat = delta / this.profile.sens; - } + var bat = 0.0; - if (outcome < this.profile.target_low) { - delta = Math.abs( outcome - this.profile.target_low); - bat = delta / this.profile.sens * -1; - } + // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion + sgv = this.scaleBg(sgv); - bat = Math.round(bat * 100) / 100; + var effect = this.iob.iob * this.profile.sens; + var outcome = sgv - effect; + var delta = 0; + var info = null; - // display text - this.updateMajorPillText(bat + 'U','BWP'); + if (outcome > this.profile.target_high) { + delta = outcome - this.profile.target_high; + bat = delta / this.profile.sens; + } -} + if (outcome < this.profile.target_low) { + delta = Math.abs(outcome - this.profile.target_low); + bat = delta / this.profile.sens * -1; + } + bat = nsutils.toFixed((bat * 100) / 100); + + // display text + var info = [{label: 'Expected effect', value: '-' + Math.round(effect)}, {label: 'Expected outcome', value: Math.round(outcome)}]; + this.updatePillText(bat + 'U', 'BWP', info); -function BWP() { - return { - label: 'Bolus Wizard Preview', - updateVisualisation: updateVisualisation }; + + return bwp(); + } -module.exports = BWP; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 2d218f23bee..c23c9c24cc0 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -1,41 +1,47 @@ 'use strict'; -// class methods -function updateVisualisation() { +var moment = require('moment'); - var age = 0; - var found = false; +function init() { - for (var t in this.env.treatments) { - if (this.env.treatments.hasOwnProperty(t)) { - var treatment = this.env.treatments[t]; + function cage() { + return cage; + } + + cage.label = 'Cannula Age'; + + cage.updateVisualisation = function updateVisualisation() { - if (treatment.eventType == "Site Change") { - var treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + var age = 0; + var found = false; + var treatmentDate = null; - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { + for (var t in this.env.treatments) { + if (this.env.treatments.hasOwnProperty(t)) { + var treatment = this.env.treatments[t]; + + if (treatment.eventType == "Site Change") { + treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + + if (!found) { + found = true; age = hours; + } else { + if (hours < age) { + age = hours; + } } } } } - } - this.updateMajorPillText(age+'h', 'CAGE'); + this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted', value: moment(treatmentDate).format('lll')}]); -} - - -function CAGE() { - return { - label: 'Cannula Age', - updateVisualisation: updateVisualisation }; + + return cage(); } -module.exports = CAGE; \ No newline at end of file + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index d49f7f8cf0b..5565a46e505 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -1,211 +1,151 @@ 'use strict'; -function getData() { - return this.cobTotal(this.env.treatments,this.env.time); -} - -function cobTotal(treatments, time) { - var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; - var cob=0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - time = new Date(); - } - - var isDecaying = 0; - var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; - - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.carbs && treatment.created_at < time) { - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - if (decaysin_hr > -10) { - var actStart = this.iobTotal(treatments, lastDecayedBy).activity; - var actEnd = this.iobTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - } - } - - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } +var iob = require('./iob')() + , moment = require('moment'); - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - cob += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - cob = 0; - } +function init() { - } - } + function cob() { + return cob; } - var rawCarbImpact = isDecaying*sens/carbratio*carbs_hr/60; - return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: carbs_hr, - rawCarbImpact: rawCarbImpact, - cob: cob - }; -} -function iobTotal(treatments, time) { - var iob= 0; - var activity = 0; - if (!treatments) return {}; - if (typeof time === 'undefined') { - time = new Date(); - } + cob.label = 'Carbs-on-Board'; - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.created_at < time) { - var tIOB = this.iobCalc(treatment, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - } - } - - return { - iob: iob, - activity: activity + cob.getData = function getData() { + return cob.cobTotal(this.env.treatments, this.env.time); }; -} + cob.cobTotal = function cobTotal(treatments, time) { + var liverSensRatio = 1; + var sens = this.profile.sens; + var carbratio = this.profile.carbratio; + var totalCOB = 0; + var lastCarbs = null; + if (!treatments) return {}; + if (typeof time === 'undefined') { + time = new Date(); + } -function carbImpact(rawCarbImpact, insulinImpact) { - var liverSensRatio = 1.0; - var liverCarbImpactMax = 0.7; - var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio*insulinImpact); - //var liverCarbImpact = liverSensRatio*insulinImpact; - var netCarbImpact = Math.max(0, rawCarbImpact-liverCarbImpact); - var totalImpact = netCarbImpact - insulinImpact; - return { - netCarbImpact: netCarbImpact, - totalImpact: totalImpact - } -} - -function iobCalc(treatment, time) { + var isDecaying = 0; + var lastDecayedBy = new Date('1/1/1970'); + var carbs_hr = this.profile.carbs_hr; + + for (var t in treatments) { + if (treatments.hasOwnProperty(t)) { + var treatment = treatments[t]; + if (treatment.carbs && treatment.created_at < time) { + lastCarbs = treatment; + var cCalc = this.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + } + } - var dia=this.profile.dia; - var scaleFactor = 3.0/dia; - var peak = 75; - var sens=this.profile.sens; - var iobContrib, activityContrib; - var t = time; - if (typeof t === 'undefined') { - t = new Date(); - } + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } - if (treatment.insulin) { - var bolusTime=new Date(treatment.created_at); - var minAgo=scaleFactor*(t-bolusTime)/1000/60; + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } + else { + totalCOB = 0; + } - if (minAgo < 0) { - iobContrib=0; - activityContrib=0; + } + } } + var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; - if (minAgo < peak) { - var x1 = minAgo/5+1; - iobContrib=treatment.insulin*(1-0.001852*x1*x1+0.001852*x1); - activityContrib=sens*treatment.insulin*(2/dia/60/peak)*minAgo; - - } else if (minAgo < 180) { - var x2 = (minAgo-75)/5; - iobContrib=treatment.insulin*(0.001323*x2*x2 - .054233*x2 + .55556); - activityContrib=sens*treatment.insulin*(2/dia/60-(minAgo-peak)*2/dia/60/(60*dia-peak)); - } else { - iobContrib=0; - activityContrib=0; - } return { - iobContrib: iobContrib, - activityContrib: activityContrib + decayedBy: lastDecayedBy, + isDecaying: isDecaying, + carbs_hr: carbs_hr, + rawCarbImpact: rawCarbImpact, + cob: totalCOB, + lastCarbs: lastCarbs }; - } - else { - return ''; - } -} + }; -function cobCalc(treatment, lastDecayedBy, time) { + cob.carbImpact = function carbImpact(rawCarbImpact, insulinImpact) { + var liverSensRatio = 1.0; + var liverCarbImpactMax = 0.7; + var liverCarbImpact = Math.min(liverCarbImpactMax, liverSensRatio * insulinImpact); + //var liverCarbImpact = liverSensRatio*insulinImpact; + var netCarbImpact = Math.max(0, rawCarbImpact - liverCarbImpact); + var totalImpact = netCarbImpact - insulinImpact; + return { + netCarbImpact: netCarbImpact, + totalImpact: totalImpact + } + }; - var carbs_hr = this.profile.carbs_hr; - var delay = 20; - var carbs_min = carbs_hr / 60; - var isDecaying = 0; - var initialCarbs; + cob.cobCalc = function cobCalc(treatment, lastDecayedBy, time) { - if (treatment.carbs) { - var carbTime = new Date(treatment.created_at); + var carbs_hr = this.profile.carbs_hr; + var delay = 20; + var carbs_min = carbs_hr / 60; + var isDecaying = 0; + var initialCarbs; - var decayedBy = new Date(carbTime); - var minutesleft = (lastDecayedBy-carbTime)/1000/60; - decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay,minutesleft) + treatment.carbs/carbs_min); - if(delay > minutesleft) { - initialCarbs = parseInt(treatment.carbs); - } - else { - initialCarbs = parseInt(treatment.carbs) + minutesleft*carbs_min; - } - var startDecay = new Date(carbTime); - startDecay.setMinutes(carbTime.getMinutes() + delay); - if (time < lastDecayedBy || time > startDecay) { - isDecaying = 1; + if (treatment.carbs) { + var carbTime = new Date(treatment.created_at); + + var decayedBy = new Date(carbTime); + var minutesleft = (lastDecayedBy - carbTime) / 1000 / 60; + decayedBy.setMinutes(decayedBy.getMinutes() + Math.max(delay, minutesleft) + treatment.carbs / carbs_min); + if (delay > minutesleft) { + initialCarbs = parseInt(treatment.carbs); + } + else { + initialCarbs = parseInt(treatment.carbs) + minutesleft * carbs_min; + } + var startDecay = new Date(carbTime); + startDecay.setMinutes(carbTime.getMinutes() + delay); + if (time < lastDecayedBy || time > startDecay) { + isDecaying = 1; + } + else { + isDecaying = 0; + } + return { + initialCarbs: initialCarbs, + decayedBy: decayedBy, + isDecaying: isDecaying, + carbTime: carbTime + }; } else { - isDecaying = 0; + return ''; } - return { - initialCarbs: initialCarbs, - decayedBy: decayedBy, - isDecaying: isDecaying, - carbTime: carbTime - }; - } - else { - return ''; - } -} - -function updateVisualisation() { - - var displayCob = Math.round(this.env.cob.cob * 10) / 10; + }; - this.updateMajorPillText(displayCob + " g",'COB'); - -} + cob.updateVisualisation = function updateVisualisation() { + var displayCob = Math.round(this.env.cob.cob * 10) / 10; + var info = null; + if (this.env.cob.lastCarbs) { + var when = moment(new Date(this.env.cob.lastCarbs.created_at)).format('lll'); + var amount = this.env.cob.lastCarbs.carbs + 'g'; + info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] + } -function COB() { - return { - label: 'Carbs-on-Board', - cobTotal: cobTotal, - cobCalc: cobCalc, - iobTotal: iobTotal, - iobCalc: iobCalc, - getData: getData, - updateVisualisation: updateVisualisation + this.updatePillText(displayCob + " g", 'COB', info); }; + return cob(); + } -module.exports = COB; +module.exports = init; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ce14b1edefa..0603b28df34 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,84 +1,80 @@ 'use strict'; var _ = require('lodash') - , PluginBase = require('./pluginbase')() // Define any shared functionality in this class - , allPlugins = [] - , enabledPlugins = []; + , PluginBase = require('./pluginbase')(); // Define any shared functionality in this class -function register(all) { +function init() { - allPlugins = []; + var allPlugins = [] + , enabledPlugins = []; - _.forIn(all, function eachPlugin(plugin, name) { - plugin.name = name; - _.extend(plugin, PluginBase); - allPlugins.push(plugin); - }); - -} - -function clientInit(app) { - enabledPlugins = []; - console.info('NightscoutPlugins init', app); - function isEnabled(plugin) { - return app.enabledOptions - && app.enabledOptions.indexOf(plugin.name) > -1; + function plugins() { + return plugins; } - _.forEach(allPlugins, function eachPlugin(plugin) { - plugin.enabled = isEnabled(plugin); - if (plugin.enabled) { - enabledPlugins.push(plugin); + plugins.register = function register(all) { + allPlugins = []; + + _.forIn(all, function eachPlugin(plugin, name) { + if (!plugin.name) plugin.name = name; + _.extend(plugin, PluginBase); + allPlugins.push(plugin); + }); + }; + + plugins.clientInit = function clientInit(app) { + enabledPlugins = []; + console.info('NightscoutPlugins init', app); + function isEnabled(plugin) { + return app.enabledOptions + && app.enabledOptions.indexOf(plugin.name) > -1; } - }); - console.info('Plugins enabled', enabledPlugins); -} -function setEnv(env) { - _.forEach(enabledPlugins, function eachPlugin(plugin) { - plugin.setEnv(env); - }); -} - -function updateVisualisations(clientSettings) { - eachShownPlugin(clientSettings, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(); - }); -} - -function eachPlugin(f) { - _.forEach(allPlugins, f); -} + _.forEach(allPlugins, function eachPlugin(plugin) { + plugin.enabled = isEnabled(plugin); + if (plugin.enabled) { + enabledPlugins.push(plugin); + } + }); + console.info('Plugins enabled', enabledPlugins); + }; + + plugins.eachPlugin = function eachPlugin(f) { + _.forEach(allPlugins, f); + }; + + plugins.eachEnabledPlugin = function eachEnabledPlugin(f) { + _.forEach(enabledPlugins, f); + }; + + plugins.eachShownPlugin = function eachShownPlugin(clientSettings, f) { + var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + }); + + _.forEach(filtered, f); + }; + + plugins.setEnvs = function setEnvs(env) { + plugins.eachEnabledPlugin(function eachPlugin(plugin) { + plugin.setEnv(env); + }); + }; + + plugins.updateVisualisations = function updateVisualisations(clientSettings) { + plugins.eachShownPlugin(clientSettings, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(); + }); + }; + + plugins.enabledPluginNames = function enabledPluginNames() { + return _.map(enabledPlugins, function mapped(plugin) { + return plugin.name; + }).join(' '); + }; + + return plugins(); -function eachEnabledPlugin(f) { - _.forEach(enabledPlugins, f); -} - -function eachShownPlugin(clientSettings, f) { - var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { - return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; - }); - - _.forEach(filtered, f); -} - -function enabledPluginNames() { - return _.map(enabledPlugins, function mapped(plugin) { - return plugin.name; - }).join(' '); -} - -function plugins() { - return { - register: register - , clientInit: clientInit - , setEnv: setEnv - , updateVisualisations: updateVisualisations - , eachPlugin: eachPlugin - , eachEnabledPlugin: eachEnabledPlugin - , eachShownPlugin: eachShownPlugin - , enabledPluginNames: enabledPluginNames - } } -module.exports = plugins; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 6722e966e15..2c941646659 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -1,97 +1,104 @@ 'use strict'; -function getData() { - return calcTotal(this.env.treatments,this.env.profile,this.env.time); -} +var _ = require('lodash') + , moment = require('moment') + , nsutils = require('../nsutils')(); -function calcTotal(treatments, profile, time) { +function init() { - var iob = 0 - , activity = 0; + function iob() { + return iob; + } - if (!treatments) return {}; + iob.label = 'Insulin-on-Board'; - if (profile === undefined) { - //if there is no profile default to 3 hour dia - profile = {dia: 3, sens: 0}; - } + iob.getData = function getData() { + return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); + }; - if (time === undefined) { - time = new Date(); - } + iob.calcTotal = function calcTotal(treatments, profile, time) { - treatments.forEach(function (treatment) { - if (new Date(treatment.created_at) < time) { - var tIOB = calcTreatment(treatment, profile, time); - if (tIOB && tIOB.iobContrib) iob += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) activity += tIOB.activityContrib; - } - }); + var totalIOB = 0 + , totalActivity = 0; - return { - iob: iob, - display: iob.toFixed(2) == '-0.00' ? '0.00' : iob.toFixed(2), - activity: activity - }; -} + if (!treatments) return {}; -function calcTreatment(treatment, profile, time) { - - var dia = profile.dia - , scaleFactor = 3.0 / dia - , peak = 75 - , sens = profile.sens - , iobContrib = 0 - , activityContrib = 0; - - if (treatment.insulin) { - var bolusTime = new Date(treatment.created_at); - var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; - - if (minAgo < peak) { - var x1 = minAgo / 5 + 1; - iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); - activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; - - } else if (minAgo < 180) { - var x2 = (minAgo - 75) / 5; - iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); - activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); - } else { - iobContrib = 0; - activityContrib = 0; + if (profile === undefined) { + //if there is no profile default to 3 hour dia + profile = {dia: 3, sens: 0}; } - } + if (time === undefined) { + time = new Date(); + } - return { - iobContrib: iobContrib, - activityContrib: activityContrib + var lastBolus = null; + + _.forEach(treatments, function eachTreatment(treatment) { + if (new Date(treatment.created_at) < time) { + var tIOB = iob.calcTreatment(treatment, profile, time); + if (tIOB.iobContrib > 0) { + lastBolus = treatment; + } + if (tIOB && tIOB.iobContrib) totalIOB += tIOB.iobContrib; + if (tIOB && tIOB.activityContrib) totalActivity += tIOB.activityContrib; + } + }); + + return { + iob: totalIOB, + display: nsutils.toFixed(totalIOB), + activity: totalActivity, + lastBolus: lastBolus + }; }; -} + iob.calcTreatment = function calcTreatment(treatment, profile, time) { -function updateVisualisation() { - var pill = this.currentDetails.find('span.pill.iob'); + var dia = profile.dia + , scaleFactor = 3.0 / dia + , peak = 75 + , sens = profile.sens + , result = { + iobContrib: 0 + , activityContrib: 0 + }; - if (!pill || pill.length == 0) { - pill = $(''); - this.currentDetails.append(pill); - } + if (treatment.insulin) { + var bolusTime = new Date(treatment.created_at); + var minAgo = scaleFactor * (time - bolusTime) / 1000 / 60; - pill.find('em').text(this.iob.display + 'U'); -} + if (minAgo < peak) { + var x1 = minAgo / 5 + 1; + result.iobContrib = treatment.insulin * (1 - 0.001852 * x1 * x1 + 0.001852 * x1); + result.activityContrib = sens * treatment.insulin * (2 / dia / 60 / peak) * minAgo; + + } else if (minAgo < 180) { + var x2 = (minAgo - 75) / 5; + result.iobContrib = treatment.insulin * (0.001323 * x2 * x2 - .054233 * x2 + .55556); + result.activityContrib = sens * treatment.insulin * (2 / dia / 60 - (minAgo - peak) * 2 / dia / 60 / (60 * dia - peak)); + } + + } + return result; -function IOB() { - return { - label: 'Insulin-on-Board', - calcTotal: calcTotal, - getData: getData, - updateVisualisation: updateVisualisation, - isDataProvider: true, - isVisualisationProvider: true }; + + iob.updateVisualisation = function updateVisualisation() { + var info = null; + + if (this.iob.lastBolus) { + var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); + var amount = nsutils.toFixed(Number(this.iob.lastBolus.insulin)) + 'U'; + info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] + } + + this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + }; + + return iob(); + } -module.exports = IOB; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index a154801cf41..7de702e7571 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function setEnv(env) { this.profile = env.profile; this.currentDetails = env.currentDetails; @@ -10,18 +12,42 @@ function setEnv(env) { this.env = env; } -function updateMajorPillText(updatedText, label) { +function updatePillText(updatedText, label, info, major) { + + var self = this; var pillName = "span.pill." + this.name; - var pill = this.pluginPills.find(pillName); + var container = major ? this.currentDetails : this.pluginPills; + + var pill = container.find(pillName); if (!pill || pill.length == 0) { pill = $(''); - this.pluginPills.append(pill); + container.append(pill); } pill.find('em').text(updatedText); + + if (info) { + + var html = _.map(info, function mapInfo(i) { + return '' + i.label + ' ' + i.value; + }).join('
    \n'); + + pill.mouseover(function pillMouseover(event) { + self.env.tooltip.transition().duration(200).style('opacity', .9); + self.env.tooltip.html(html) + .style('left', (event.pageX) + 'px') + .style('top', (event.pageY + 15) + 'px'); + }); + + pill.mouseout(function pillMouseout() { + self.env.tooltip.transition() + .duration(200) + .style('opacity', 0); + }); + } } function scaleBg(bg) { @@ -36,7 +62,7 @@ function PluginBase() { return { setEnv: setEnv, scaleBg: scaleBg, - updateMajorPillText: updateMajorPillText + updatePillText: updatePillText }; } diff --git a/static/js/client.js b/static/js/client.js index d4624fc07b7..9f38a0a5583 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -472,26 +472,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; + env.tooltip = tooltip; - Nightscout.plugins.eachShownPlugin(browserSettings, function updateEachPlugin(plugin) { + //all enabled plugins get a chance to add data, even if they aren't shown + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { // Update the env through data provider plugins plugin.setEnv(env); // check if the plugin implements processing data if (plugin.getData) { - var dataFromPlugin = plugin.getData(); - var container = {}; - for (var i in dataFromPlugin) { - if (dataFromPlugin.hasOwnProperty(i)) { - container[i] = dataFromPlugin[i]; - } - } - env[plugin.name] = container; + env[plugin.name] = plugin.getData(); } }); // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnv(env); + Nightscout.plugins.setEnvs(env); + + //only shown plugins get a chance to update visualisations Nightscout.plugins.updateVisualisations(browserSettings); } From fd70e1219f88c608d5d1f735b8a214e1eb900d20 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 02:10:18 -0700 Subject: [PATCH 035/661] only make the header/status area taller when a minor pill is shown --- lib/plugins/boluswizardpreview.js | 1 + lib/plugins/cannulaage.js | 1 + lib/plugins/cob.js | 1 + lib/plugins/index.js | 5 ++- lib/plugins/iob.js | 3 +- lib/plugins/pluginbase.js | 6 +-- static/css/main.css | 61 +++++++++++++++++++++---------- static/index.html | 4 +- static/js/client.js | 24 +++++++++--- 9 files changed, 73 insertions(+), 33 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index efe2a36f59a..58ea2a18e36 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -9,6 +9,7 @@ function init() { } bwp.label = 'Bolus Wizard Preview'; + bwp.pluginType = 'minor-pill'; bwp.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index c23c9c24cc0..ad641112030 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -9,6 +9,7 @@ function init() { } cage.label = 'Cannula Age'; + cage.pluginType = 'minor-pill'; cage.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 5565a46e505..078c77a9a02 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -10,6 +10,7 @@ function init() { } cob.label = 'Carbs-on-Board'; + cob.pluginType = 'minor-pill'; cob.getData = function getData() { return cob.cobTotal(this.env.treatments, this.env.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 0603b28df34..32deffd248e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -47,7 +47,8 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.eachShownPlugin = function eachShownPlugin(clientSettings, f) { + plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { + console.info('eachShownPlugins'); var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); @@ -62,7 +63,7 @@ function init() { }; plugins.updateVisualisations = function updateVisualisations(clientSettings) { - plugins.eachShownPlugin(clientSettings, function eachPlugin(plugin) { + plugins.eachShownPlugins(clientSettings, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(); }); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 2c941646659..c949bf5cbd4 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -11,6 +11,7 @@ function init() { } iob.label = 'Insulin-on-Board'; + iob.pluginType = 'major-pill'; iob.getData = function getData() { return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); @@ -94,7 +95,7 @@ function init() { info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + this.updatePillText(this.iob.display + 'U', 'IOB', info); }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 7de702e7571..3b86d8794dc 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -4,8 +4,8 @@ var _ = require('lodash'); function setEnv(env) { this.profile = env.profile; - this.currentDetails = env.currentDetails; - this.pluginPills = env.pluginPills; + this.majorPills = env.majorPills; + this.minorPills = env.minorPills; this.iob = env.iob; // TODO: clean! @@ -18,7 +18,7 @@ function updatePillText(updatedText, label, info, major) { var pillName = "span.pill." + this.name; - var container = major ? this.currentDetails : this.pluginPills; + var container = this.pluginType == 'major-pill' ? this.majorPills : this.minorPills; var pill = container.find(pillName); diff --git a/static/css/main.css b/static/css/main.css index 870ce7b9a0f..8faaa1a4a0d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -29,11 +29,15 @@ body { .status { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 200px; + height: 180px; vertical-align: middle; clear: left right; } +.has-minor-pills .status { + height: 200px; +} + .bgStatus { float: right; text-align: center; @@ -72,21 +76,21 @@ body { vertical-align: middle; } -.bgStatus .currentDetails { +.bgStatus .majorPills { font-size: 30px; } -.pluginPills { +.minorPills { font-size: 22px; margin-top: 5px; z-index: 500; } -.currentDetails > span:not(:first-child) { +.majorPills > span:not(:first-child) { margin-left: 5px; } -.pluginPills > span:not(:first-child) { +.minorPills > span:not(:first-child) { margin-left: 5px; } @@ -179,7 +183,7 @@ body { } #chartContainer { - top: 245px; /*(toolbar height + status height)*/ + top: 225px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -190,6 +194,10 @@ body { position:absolute; } +.has-minor-pills #chartContainer { + top: 245px; +} + #chartContainer svg { height: calc(100vh - (180px + 45px)); width: 100%; @@ -353,11 +361,11 @@ div.tooltip { line-height: 70px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 20px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 16px; } @@ -389,9 +397,14 @@ div.tooltip { } #chartContainer { - top: 195px; + top: 185px; font-size: 14px; } + + .has-minor-pills #chartContainer { + top: 195px; + } + #chartContainer svg { height: calc(100vh - 185px); } @@ -426,11 +439,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -484,11 +497,11 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -534,8 +547,13 @@ div.tooltip { } #chartContainer { - top: 200px; + top: 190px; } + + .has-minor-pills #chartContainer { + top: 200px; + } + #chartContainer svg { height: calc(100vh - (190px)); } @@ -569,9 +587,14 @@ div.tooltip { } #chartContainer { - top: 140px; + top: 130px; font-size: 10px; } + + .has-minor-pills #chartContainer { + top: 140px; + } + #chartContainer svg { height: calc(100vh - 130px); } @@ -592,11 +615,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .pluginPills { + .bgStatus .minorPills { font-size: 12px; } @@ -639,11 +662,11 @@ div.tooltip { line-height: 50px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 15px; } - .bgStatus .currentDetails { + .bgStatus .majorPills { font-size: 12px; } diff --git a/static/index.html b/static/index.html index df6c473f343..b422ed627bb 100644 --- a/static/index.html +++ b/static/index.html @@ -57,8 +57,8 @@

    Nightscout

  • Silence for 90 minutes
  • Silence for 120 minutes
  • -
    -
    +
    +
    diff --git a/static/js/client.js b/static/js/client.js index 9f38a0a5583..0a92c204e17 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -392,8 +392,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , bgStatus = $('.bgStatus') , currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') - , currentDetails = $('.bgStatus .currentDetails') - , pluginPills = $('.bgStatus .pluginPills') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -442,10 +442,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updateBGDelta(prevEntry, currentEntry) { - var pill = currentDetails.find('span.pill.bgdelta'); + var pill = majorPills.find('span.pill.bgdelta'); if (!pill || pill.length == 0) { pill = $(''); - currentDetails.append(pill); + majorPills.append(pill); } var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); @@ -467,13 +467,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updatePlugins(sgv, time) { var env = {}; env.profile = profile; - env.currentDetails = currentDetails; - env.pluginPills = pluginPills; + env.majorPills = majorPills; + env.minorPills = minorPills; env.sgv = Number(sgv); env.treatments = treatments; env.time = time; env.tooltip = tooltip; + var hasMinorPill = false; + //all enabled plugins get a chance to add data, even if they aren't shown Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { // Update the env through data provider plugins @@ -485,6 +487,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); + Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { + console.info('plugin.pluginType', plugin.pluginType); + if (plugin.pluginType == 'minor-pill') { + hasMinorPill = true; + } + }); + + $('.container').toggleClass('has-minor-pills', hasMinorPill); + + // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnvs(env); From aa49c9fc17663cc930d98aaecb44fc720981a08e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 25 May 2015 22:01:20 +0300 Subject: [PATCH 036/661] Display rounding closer to pump display, improved display of units in preview message --- lib/plugins/boluswizardpreview.js | 4 +++- lib/plugins/cannulaage.js | 4 +++- lib/plugins/iob.js | 4 ++-- lib/plugins/pluginbase.js | 26 +++++++++++++++++++++++++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index efe2a36f59a..2f3d08aa404 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -35,9 +35,11 @@ function init() { } bat = nsutils.toFixed((bat * 100) / 100); + outcome = this.roundBGToDisplayFormat(outcome); + var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); // display text - var info = [{label: 'Expected effect', value: '-' + Math.round(effect)}, {label: 'Expected outcome', value: Math.round(outcome)}]; + var info = [{label: 'Insulin on Board', value: displayIOB}, {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()}, {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()}]; this.updatePillText(bat + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index c23c9c24cc0..03b4298b54c 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -15,6 +15,7 @@ function init() { var age = 0; var found = false; var treatmentDate = null; + var message = 'no notes'; for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { @@ -30,13 +31,14 @@ function init() { } else { if (hours < age) { age = hours; + if (treatment.notes) { message = treatment.notes; } } } } } } - this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted', value: moment(treatmentDate).format('lll')}]); + this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}, {label: 'Notes:', value: message}]); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 2c941646659..2c16bf31801 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -90,11 +90,11 @@ function init() { if (this.iob.lastBolus) { var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); - var amount = nsutils.toFixed(Number(this.iob.lastBolus.insulin)) + 'U'; + var amount = this.roundInsulinForDisplayFormat(Number(this.iob.lastBolus.insulin)) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.iob.display + 'U', 'IOB', info, true); + this.updatePillText(this.roundInsulinForDisplayFormat(this.iob.display) + 'U', 'IOB', info, true); }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 7de702e7571..ff4d551c219 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -50,6 +50,27 @@ function updatePillText(updatedText, label, info, major) { } } +function roundInsulinForDisplayFormat(iob) { + var denominator = 0.1; + + if (iob > 0.5 && iob <= 1) { denominator = 0.05; } + if (iob <= 0.5) { denominator = 0.025; } + + return Math.round(iob / denominator) * denominator; +} + +function getBGUnits() { + if (browserSettings.units == 'mmol') return 'mmol/L'; + return "mg/dl"; +} + +function roundBGToDisplayFormat(bg) { + if (browserSettings.units == 'mmol') { + return Math.round(bg * 100) / 100; + } + return Math.round(bg); +} + function scaleBg(bg) { if (browserSettings.units == 'mmol') { return Nightscout.units.mgdlToMMOL(bg); @@ -62,7 +83,10 @@ function PluginBase() { return { setEnv: setEnv, scaleBg: scaleBg, - updatePillText: updatePillText + updatePillText: updatePillText, + roundBGToDisplayFormat: roundBGToDisplayFormat, + roundInsulinForDisplayFormat: roundInsulinForDisplayFormat, + getBGUnits: getBGUnits }; } From d89436aa1e9e587e9d54849fe2e62f3a5f3dc797 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 25 May 2015 23:32:43 +0300 Subject: [PATCH 037/661] De-medtronicfied display and no more "no notes" message --- lib/plugins/cannulaage.js | 7 +++++-- lib/plugins/pluginbase.js | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 5759bd102f5..ea27594de4a 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -16,7 +16,7 @@ function init() { var age = 0; var found = false; var treatmentDate = null; - var message = 'no notes'; + var message = ''; for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { @@ -39,7 +39,10 @@ function init() { } } - this.updatePillText(age + 'h', 'CAGE', [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}, {label: 'Notes:', value: message}]); + var labels = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; + if (message != '') labels.push({label: 'Notes:', value: message}); + + this.updatePillText(age + 'h', 'CAGE', labels); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 556035e2dff..02dba2ec468 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -50,16 +50,22 @@ function updatePillText(updatedText, label, info, major) { } } -function roundInsulinForDisplayFormat(iob) { +function roundInsulinForDisplayFormat(iob, roundingStyle) { if (iob == 0) return 0; - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} - if (iob <= 0.5) { denominator = 0.025; digits = 3;} - - return (Math.floor(iob / denominator) * denominator).toFixed(digits); + if (roundingStyle === undefined) roundingStyle = 'generic'; + + if (roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} + if (iob <= 0.5) { denominator = 0.025; digits = 3;} + return (Math.floor(iob / denominator) * denominator).toFixed(digits); + } + + return (Math.floor(iob / 0.01) * 0.01).toFixed(2); + } function getBGUnits() { From ff7f54d23169b3e5c7bcff8ffc418943ebb6116e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 13:39:26 -0700 Subject: [PATCH 038/661] moved plugin registration to the plugins/index.js --- bundle/bundle.source.js | 7 +------ lib/plugins/index.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 85a1fd42b28..2558c36e970 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -10,12 +10,7 @@ console.info('plugins', window.Nightscout.plugins); - window.Nightscout.plugins.register({ - iob: require('../lib/plugins/iob')(), - cob: require('../lib/plugins/cob')(), - bwp: require('../lib/plugins/boluswizardpreview')(), - cage: require('../lib/plugins/cannulaage')() - }); + window.Nightscout.plugins.registerDefaults(); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 32deffd248e..ca241498b1f 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -12,11 +12,17 @@ function init() { return plugins; } - plugins.register = function register(all) { - allPlugins = []; + plugins.registerDefaults = function registerDefaults() { + plugins.register([ + require('./iob')(), + require('./cob')(), + require('./boluswizardpreview')(), + require('./cannulaage')() + ]) + }; - _.forIn(all, function eachPlugin(plugin, name) { - if (!plugin.name) plugin.name = name; + plugins.register = function register(all) { + _.forEach(all, function eachPlugin(plugin) { _.extend(plugin, PluginBase); allPlugins.push(plugin); }); From b5402e0f6327cb13d65f71c75191b69d6c577c4d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 13:44:07 -0700 Subject: [PATCH 039/661] minor clean up --- lib/plugins/boluswizardpreview.js | 14 ++++++++------ lib/plugins/cannulaage.js | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 769fb8f4906..6f0eb73a42f 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,7 +1,5 @@ 'use strict'; -var nsutils = require('../nsutils')(); - function init() { function bwp() { @@ -23,7 +21,6 @@ function init() { var effect = this.iob.iob * this.profile.sens; var outcome = sgv - effect; var delta = 0; - var info = null; if (outcome > this.profile.target_high) { delta = outcome - this.profile.target_high; @@ -36,11 +33,16 @@ function init() { } bolusEstimate = this.roundInsulinForDisplayFormat(bolusEstimate); - outcome = this.roundBGToDisplayFormat(outcome); - var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); + outcome = this.roundBGToDisplayFormat(outcome); + var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); // display text - var info = [{label: 'Insulin on Board', value: displayIOB + 'U'}, {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()}, {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()}]; + var info = [ + {label: 'Insulin on Board', value: displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()} + , {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()} + ]; + this.updatePillText(bolusEstimate + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index ea27594de4a..997fd56bfc2 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -39,10 +39,10 @@ function init() { } } - var labels = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') labels.push({label: 'Notes:', value: message}); + var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; + if (message != '') info.push({label: 'Notes:', value: message}); - this.updatePillText(age + 'h', 'CAGE', labels); + this.updatePillText(age + 'h', 'CAGE', info); }; From 2dcc8d4d785fdc02674216042d678a1157298d99 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 23:19:06 -0700 Subject: [PATCH 040/661] removed settings; fixed profiles/profile mixups; fix current/id queries --- Makefile | 3 +- env.js | 6 +- lib/api/devicestatus/index.js | 4 +- lib/api/entries/index.js | 46 ++++++---- lib/api/index.js | 3 +- lib/api/settings/index.js | 73 --------------- lib/api/treatments/index.js | 6 +- lib/bootevent.js | 1 - lib/profile.js | 2 + lib/settings.js | 164 ---------------------------------- tests/api.entries.test.js | 2 +- tests/security.test.js | 3 +- 12 files changed, 43 insertions(+), 270 deletions(-) delete mode 100644 lib/api/settings/index.js delete mode 100644 lib/settings.js diff --git a/Makefile b/Makefile index 43db4909df3..ec7e4f21041 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,7 @@ MONGO_CONNECTION?=mongodb://localhost/test_db CUSTOMCONNSTR_mongo_settings_collection?=test_settings CUSTOMCONNSTR_mongo_collection?=test_sgvs MONGO_SETTINGS=MONGO_CONNECTION=${MONGO_CONNECTION} \ - CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} \ - CUSTOMCONNSTR_mongo_settings_collection=${CUSTOMCONNSTR_mongo_settings_collection} + CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} # XXX.bewest: Mocha is an odd process, and since things are being # wrapped and transformed, this odd path needs to be used, not the diff --git a/env.js b/env.js index a0f381031ff..c455e62bbba 100644 --- a/env.js +++ b/env.js @@ -12,7 +12,6 @@ function config ( ) { * * PORT - serve http on this port * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with "sgv" documents - * * CUSTOMCONNSTR_mongo_settings_collection - name of mongo collection to store configurable settings * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization * * NIGHTSCOUT_STATIC_FILES - the "base directory" to use for serving * static files over http. Default value is the included `static` @@ -50,7 +49,6 @@ function config ( ) { console.info('MQTT configured to use a custom client id, it will override the default: ', env.mqtt_client_id); } } - env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings'); env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments'); env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); @@ -176,11 +174,9 @@ function config ( ) { // This allows a provided json config to override environment variables var DB = require('./database_configuration.json'), DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection, - DB_SETTINGS_COLLECTION = DB.settings_collection ? DB.settings_collection : env.settings_collection; + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection env.mongo = DB_URL; env.mongo_collection = DB_COLLECTION; - env.settings_collection = DB_SETTINGS_COLLECTION; env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); return env; diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 40d3a3246d3..7a5f65ee1f0 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -21,8 +21,8 @@ function configure (app, wares, devicestatus) { if (!q.count) { q.count = 10; } - devicestatus.list(q, function (err, profiles) { - return res.json(profiles); + devicestatus.list(q, function (err, results) { + return res.json(results); }); }); diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 53d3e4e339e..1593e543748 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -123,15 +123,14 @@ function configure (app, wares, core) { es.pipeline(inputs( ), persist(done)); } - api.param('model', function (req, res, next, model) { + function prepReqModel(req, model) { var find = { }; switch (model) { case 'treatments': case 'devicestatus': - case 'settings': - case 'profile': + //TODO: profiles not working now, maybe profiles are special + //case 'profiles': req.model = core[model]; - // find.type = model; break; case 'meter': @@ -155,14 +154,22 @@ function configure (app, wares, core) { req.model = core.entries; break; } + if (!req.query.find) { req.query.find = find; } else { req.query.find.type = find.type; } + } + + api.param('model', function (req, res, next, model) { + prepReqModel(req, model); next( ); }); + api.get('/entries/current', function(req, res, next) { + //assume sgv + req.params.model = 'sgv'; entries.list({count: 1}, function(err, records) { res.entries = records; res.entries_err = err; @@ -170,6 +177,25 @@ function configure (app, wares, core) { }); }, format_entries); + // Fetch one entry by id + api.get('/entries/:id', function(req, res, next) { + var ID_PATTERN = /^[a-f\d]{24}$/; + if (ID_PATTERN.test(req.params.id)) { + //assume sgv + req.params.model = 'sgv'; + entries.getEntry(req.params.id, function(err, entry) { + res.entries = [entry]; + res.entries_err = err; + next(); + }); + } else { + //TODO: /entries/:id is blocking /entries/:model + req.params.model = req.params.id; + prepReqModel(req, req.params.model); + query_models(req, res, next); + } + }, format_entries); + api.get('/entries/:model', query_models, format_entries); api.get('/entries', query_models, format_entries); @@ -180,7 +206,7 @@ function configure (app, wares, core) { req.model = core.entries; } var query = req.query; - if (!query.count) { query.count = 10 }; + if (!query.count) { query.count = 10 } req.model.list(query, function(err, entries) { res.entries = entries; res.entries_err = err; @@ -204,16 +230,6 @@ function configure (app, wares, core) { }, insert_entries, format_entries); } - // Fetch one entry by id - api.get('/entries/:id', function(req, res, next) { - entries.getEntry(req.params.id, function(err, entry) { - res.entries = [entry]; - res.entries_err = err; - next() - }); - }, format_entries); - - return api; } module.exports = configure; diff --git a/lib/api/index.js b/lib/api/index.js index 04280c607ea..7c67de12a3f 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -47,9 +47,8 @@ function create (env, core) { // Entries and settings app.use('/', require('./entries/')(app, wares, core)); - app.use('/', require('./settings/')(app, wares, core.settings)); app.use('/', require('./treatments/')(app, wares, core.treatments)); - app.use('/', require('./profile/')(app, wares, core.profile)); + app.use('/', require('./profile/')(app, wares, core.profiles)); app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); // Status diff --git a/lib/api/settings/index.js b/lib/api/settings/index.js deleted file mode 100644 index c997024270a..00000000000 --- a/lib/api/settings/index.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -var consts = require('../../constants'); - -function configure (app, wares, settings) { - var express = require('express'), - api = express.Router( ) - ; - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - - /**********\ - * Settings - \**********/ - // Handler for grabbing alias/profile - api.param('alias', function (req, res, next, alias) { - settings.alias(alias, function (err, profile) { - req.alias = profile; - next(err); - }); - }); - - // List settings available - api.get('/settings/', function(req, res) { - settings.list(function (err, profiles) { - return res.json(profiles); - }); - }); - - // Fetch settings - api.get('/settings/:alias', function(req, res) { - res.json(req.alias); - }); - - function config_authed (app, api, wares, settings) { - - // Delete settings - api.delete('/settings/:alias', wares.verifyAuthorization, function(req, res) { - settings.remove(req.alias.alias, function ( ) { - res.json({ }); - }); - }); - - function save_settings (req, res) { - var b = req.body; - b.alias = req.params.alias - settings.update(b, function (err, profile) { - res.json(profile); - }); - } - - // Update settings - api.put('/settings/:alias', wares.verifyAuthorization, save_settings); - api.post('/settings/:alias', wares.verifyAuthorization, save_settings); - - } - - if (app.enabled('api')) { - config_authed(app, api, wares, settings); - } - - return api; -} - -module.exports = configure; - diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 259b7f00fe1..7d7e89d0b5d 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -15,10 +15,10 @@ function configure (app, wares, treatments) { // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); - // List settings available + // List treatments available api.get('/treatments/', function(req, res) { - treatments.list({find: req.params}, function (err, profiles) { - return res.json(profiles); + treatments.list({find: req.params}, function (err, results) { + return res.json(results); }); }); diff --git a/lib/bootevent.js b/lib/bootevent.js index 44ef41c8b36..9bb1fc16c47 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,7 +18,6 @@ function boot (env) { /////////////////////////////////////////////////// ctx.pushover = require('./pushover')(env); ctx.entries = require('./entries')(env.mongo_collection, ctx.store, ctx.pushover); - ctx.settings = require('./settings')(env.settings_collection, ctx.store); ctx.treatments = require('./treatments')(env.treatments_collection, ctx.store, ctx.pushover); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); ctx.profiles = require('./profile')(env.profile_collection, ctx.store); diff --git a/lib/profile.js b/lib/profile.js index ec2182198db..08e7b9ca254 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -4,6 +4,8 @@ function storage (collection, storage) { var ObjectID = require('mongodb').ObjectID; + console.info('>>>>profile storage init', collection, storage); + function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { diff --git a/lib/settings.js b/lib/settings.js deleted file mode 100644 index 2d0b9984ab3..00000000000 --- a/lib/settings.js +++ /dev/null @@ -1,164 +0,0 @@ -'use strict'; - - -var ObjectID = require('mongodb').ObjectID; -function defaults ( ) { - var DEFAULT_SETTINGS_JSON = { - "units": "mg/dl" - }; // possible future settings: "theme": "subdued", "websockets": false, alertLow: 80, alertHigh: 180 - return DEFAULT_SETTINGS_JSON; -} - -function configure (collection, storage) { - var DEFAULT_SETTINGS_JSON = { - "units": "mg/dl" - }; - - function pop (fn) { - return function (err, results) { - if (err) fn(err); - fn(err, results.pop( )); - } - } - - function alias (alias, fn) { - return api( ).find({ alias: alias }).toArray(pop(fn)); - } - - function create (obj, fn) { - var result = merge(DEFAULT_SETTINGS_JSON, obj); - result.alias = obj.alias; - result.created_at = (new Date( )).toISOString( ); - api( ).insert(result, function (err, doc) { - fn(null, doc); - }); - } - - function lint (obj) { - var result = merge(DEFAULT_SETTINGS_JSON, json); - if (result.alias) return result; - } - - function list (fn) { - return api( ).find({ }, { alias: 1, nick: 1 }).toArray(pop(fn)); - } - - function remove (alias, fn) { - return api( ).remove({ alias: alias }, fn); - } - - var with_collection = storage.with_collection(collection); - function getSettings (fn) { - with_collection(function(err, collection) { - if (err) - fn(err); - else - // Retrieve the existing settings record. - collection.find().toArray(function(err, settings) { - if (err) - fn(err); - else - // Strip the record of the enclosing square brackets. - settings = settings.pop( ); - if (!settings) { - settings = DEFAULT_SETTINGS_JSON; - } - fn(null, settings); - }); - }); - } - var whitelist = [ 'units', 'data' ]; - - function merge (older, newer) { - var update = { }; - whitelist.forEach(function (prop) { - if (prop in newer) { - update[prop] = newer[prop]; - } - }); - return update; - } - - function update (json, fn) { - var updated = (new Date( )).toISOString( ); - alias(json.alias, function last (err, older) { - if (err) { return fn(err); } - var result; - if (older && older._id) { - // result = merge(older, json); - result = json; - result.updated_at = updated; - var deltas = Object.keys(result); - if (deltas.length < 1) { - fn("Bad Keys"); - return; - } - api( ).update( - { '_id' : new ObjectID(older._id) }, - { $set: result }, - function (err, res) { - // Return to the calling function to display our success. - fn(err, res); - } - ); - } else { - create(json, fn); - } - }); - } - - function updateSettings (json, fn) { - getSettings(function last (err, older) { - var result = merge(older, json); - var deltas = Object.keys(result); - if (deltas.length < 1) { - fn("Bad Keys"); - return; - } - result.updated_at = (new Date( )).toISOString( ); - with_collection(function(err, collection) { - if (err) { - console.log('error', result); - return fn(err); - } else if (older && older._id && Object.keys(deltas).length > 0) { - collection.update( - { '_id' : new ObjectID(older._id) }, - { $set: result }, - function (err, stats) { - // Return to the calling function to display our success. - fn(err, result); - } - ); - } else { - collection.insert(result, function (err, doc) { - fn(null, result); - - }); - - } - }); - }); - } - - function clear (fn) { - with_collection(function (err, collection) { - collection.remove({ }, fn); - }); - } - - function api ( ) { - return storage.pool.db.collection(collection); - } - - api.getSettings = getSettings; - api.updateSettings = updateSettings; - api.remove = remove; - api.clear = clear; - api.list = list; - api.create = create; - api.lint = lint; - api.alias = alias; - api.update = update; - return api; -} -module.exports = configure; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 7e0529a099f..5d0203528e0 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -69,7 +69,7 @@ describe('Entries REST api', function ( ) { }); }); - it('/entries/ID', function (done) { + it('/entries/sgv/ID', function (done) { var app = this.app; this.archive.list({count: 1}, function(err, records) { var currentId = records.pop()._id.toString(); diff --git a/tests/security.test.js b/tests/security.test.js index 42cfa4215e2..60b63f6aa25 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -14,10 +14,9 @@ describe('API_SECRET', function ( ) { ctx.wares = require('../lib/middleware/')(env); ctx.store = require('../lib/storage')(env); ctx.archive = require('../lib/entries').storage(env.mongo_collection, ctx.store); - ctx.settings = require('../lib/settings')(env.settings_collection, ctx.store); ctx.store(function ( ) { - ctx.app = api(env, ctx.wares, ctx.archive, ctx.settings); + ctx.app = api(env, ctx.wares, ctx.archive); scope.app = ctx.app; ctx.archive.create(load('json'), fn); scope.archive = ctx.archive; From 3d08a0185077646aac7aeade5328e6d58221e70d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 25 May 2015 23:20:50 -0700 Subject: [PATCH 041/661] remove debug --- lib/profile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/profile.js b/lib/profile.js index 08e7b9ca254..ec2182198db 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -4,8 +4,6 @@ function storage (collection, storage) { var ObjectID = require('mongodb').ObjectID; - console.info('>>>>profile storage init', collection, storage); - function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); api().insert(obj, function (err, doc) { From 149213677aa0804b57b5e23a1a19083fb55542d0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 26 May 2015 00:04:07 -0700 Subject: [PATCH 042/661] only list enabled plugins in the settings panel, and some minor tweeks --- lib/plugins/index.js | 2 -- static/css/main.css | 34 +++++++++++++++++++++++----------- static/js/ui-utils.js | 2 +- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index ca241498b1f..3cca62db8fb 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -30,7 +30,6 @@ function init() { plugins.clientInit = function clientInit(app) { enabledPlugins = []; - console.info('NightscoutPlugins init', app); function isEnabled(plugin) { return app.enabledOptions && app.enabledOptions.indexOf(plugin.name) > -1; @@ -54,7 +53,6 @@ function init() { }; plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - console.info('eachShownPlugins'); var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); diff --git a/static/css/main.css b/static/css/main.css index 8faaa1a4a0d..d338b93d23c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -82,7 +82,7 @@ body { .minorPills { font-size: 22px; - margin-top: 5px; + margin-top: 10px; z-index: 500; } @@ -397,16 +397,20 @@ div.tooltip { } #chartContainer { - top: 185px; + top: 165px; font-size: 14px; } + #chartContainer svg { + height: calc(100vh - 165px); + } + .has-minor-pills #chartContainer { - top: 195px; + top: 175px; } #chartContainer svg { - height: calc(100vh - 185px); + height: calc(100vh - 175px); } } @@ -449,7 +453,7 @@ div.tooltip { .time { font-size: 50px; - line-height: 40px; + line-height: 35px; width: 250px; } @@ -550,12 +554,16 @@ div.tooltip { top: 190px; } + #chartContainer svg { + height: calc(100vh - (190px)); + } + .has-minor-pills #chartContainer { - top: 200px; + top: 210px; } - #chartContainer svg { - height: calc(100vh - (190px)); + .has-minor-pills #chartContainer svg { + height: calc(100vh - (210px)); } } @@ -591,12 +599,16 @@ div.tooltip { font-size: 10px; } + #chartContainer svg { + height: calc(100vh - 130px); + } + .has-minor-pills #chartContainer { - top: 140px; + top: 140px; } - #chartContainer svg { - height: calc(100vh - 130px); + .has-minor-pills #chartContainer svg { + height: calc(100vh - 140px); } } diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 50acb03dc56..ec744ae1318 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -98,7 +98,7 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); - Nightscout.plugins.eachPlugin(function each(plugin) { + Nightscout.plugins.eachEnabledPlugin(function each(plugin) { var id = 'plugin-' + plugin.name; var dd = $('
    '); showPluginsSettings.append(dd); From c236b55f70a2e1e4a6be0e027cd7a7c4c96dc906 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 27 May 2015 01:27:57 -0700 Subject: [PATCH 043/661] convert entries/treatment pushovers in to a server side plugin --- bundle/bundle.source.js | 4 +- lib/bootevent.js | 5 +- lib/entries.js | 152 ++---------------------------- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/cannulaage.js | 2 +- lib/plugins/cob.js | 2 +- lib/plugins/index.js | 18 +++- lib/plugins/iob.js | 2 +- lib/plugins/pluginbase.js | 2 +- lib/treatments.js | 69 ++------------ static/js/client.js | 4 +- tests/api.entries.test.js | 13 ++- tests/api.status.test.js | 7 +- 13 files changed, 50 insertions(+), 232 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2558c36e970..1fd4f3a2d78 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,13 +5,11 @@ window.Nightscout = { units: require('../lib/units')(), profile: require('../lib/profilefunctions')(), - plugins: require('../lib/plugins/')() + plugins: require('../lib/plugins/')().registerClientDefaults() }; console.info('plugins', window.Nightscout.plugins); - window.Nightscout.plugins.registerDefaults(); - console.info("Nightscout bundle ready", window.Nightscout); })(); diff --git a/lib/bootevent.js b/lib/bootevent.js index 1f7cd8bc3a0..2f81a590b50 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -16,9 +16,10 @@ function boot (env) { /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); - ctx.entries = require('./entries')(env.mongo_collection, ctx.store, ctx.pushover, env); - ctx.treatments = require('./treatments')(env.treatments_collection, ctx.store, ctx.pushover); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); ctx.profiles = require('./profile')(env.profile_collection, ctx.store); ctx.pebble = require('./pebble'); diff --git a/lib/entries.js b/lib/entries.js index 239d4f71130..adfe7dea56b 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -5,12 +5,6 @@ var sgvdata = require('sgvdata'); var units = require('./units')(); var ObjectID = require('mongodb').ObjectID; -// declare local constants for time differences -var TIME_10_MINS = 10 * 60 * 1000, - TIME_15_MINS = 15 * 60 * 1000, - TIME_30_MINS = TIME_15_MINS * 2; - - /**********\ * Entries * Encapsulate persistent storage of sgv entries. @@ -28,11 +22,11 @@ function find_sgv_query (opts) { return opts; } -function storage(name, storage, pushover, env) { +function storage(env, ctx) { // TODO: Code is a little redundant. - var with_collection = storage.with_collection(name); + var with_collection = ctx.store.with_collection(env.mongo_collection); // query for entries from storage function list (opts, fn) { @@ -44,8 +38,7 @@ function storage(name, storage, pushover, env) { // determine find options function find ( ) { var finder = find_sgv_query(opts); - var q = finder && finder.find ? finder.find : { }; - return q; + return finder && finder.find ? finder.find : { }; // return this.find(q); } @@ -126,143 +119,12 @@ function storage(name, storage, pushover, env) { fn(firstErr, docs); } }); - sendPushover(doc); - }); - }); - } - - function sendPushover(doc) { - if (doc.type && doc.date && pushover) { - if (doc.type == 'mbg') { - sendMBGPushover(doc); - } else if (doc.type == 'sgv') { - sendSGVPushover(doc); - } - } - } - //currently the Android upload will send the last MBG over and over, make sure we get a single notification - var lastMBGDate = 0; - - function sendMBGPushover(doc) { - - if (doc.mbg && doc.type == 'mbg' && doc.date != lastMBGDate) { - var offset = new Date().getTime() - doc.date; - if (offset > TIME_10_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime()); - } else { - var mbg = doc.mbg; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(doc.date), - priority: 0, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); + ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { + if (plugin.processEntry) plugin.processEntry(doc, ctx, env) }); - } - lastMBGDate = doc.date; - } - } - - // global variable for last alert time - var lastAlert = 0; - var lastSGVDate = 0; - - function sendSGVPushover(doc) { - - if (!doc.sgv || doc.type != 'sgv') { - return; - } - - var now = new Date().getTime(), - offset = new Date().getTime() - doc.date; - - if (offset > TIME_10_MINS || doc.date == lastSGVDate) { - console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + doc.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); - return; - } - - // initialize message data - var sinceLastAlert = now - lastAlert, - title = 'CGM Alert', - priority = 0, - sound = null, - readingtime = doc.date, - readago = now - readingtime; - - console.info('now: ' + now); - console.info('doc.sgv: ' + doc.sgv); - console.info('doc.direction: ' + doc.direction); - console.info('doc.date: ' + doc.date); - console.info('readingtime: ' + readingtime); - console.info('readago: ' + readago); - - // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high - if (doc.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS) { - title = 'CGM Error'; - priority = 1; - sound = 'persistent'; - } - } else if (doc.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { - title = 'Urgent Low'; - priority = 2; - sound = 'persistent'; - } else if (doc.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; - priority = 1; - sound = 'falling'; - } else if (doc.sgv < 120 && doc.direction == 'DoubleDown') { - title = 'Double Down'; - priority = 1; - sound = 'falling'; - } else if (doc.sgv == 100 && doc.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) - title = 'Perfect'; - priority = 0; - sound = 'cashregister'; - } else if (doc.sgv > 120 && doc.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Double Up'; - priority = 1; - sound = 'climb'; - } else if (doc.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { - title = 'High'; - priority = 1; - sound = 'climb'; - } else if (doc.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { - title = 'Urgent High'; - priority = 1; - sound = 'persistent'; - } - - if (sound != null) { - lastAlert = now; - - var msg = { - expire: 14400, // 4 hours - message: 'BG NOW: ' + doc.sgv, - title: title, - sound: sound, - timestamp: new Date(doc.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); }); - } - - - lastSGVDate = doc.date; + }); } function getEntry(id, fn) { @@ -283,7 +145,7 @@ function storage(name, storage, pushover, env) { function api ( ) { // obtain handle usable for querying the collection associated // with these records - return storage.pool.db.collection(name); + return ctx.store.pool.db.collection(env.mongo_collection); } // Expose all the useful functions diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6f0eb73a42f..e330258b881 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -7,7 +7,7 @@ function init() { } bwp.label = 'Bolus Wizard Preview'; - bwp.pluginType = 'minor-pill'; + bwp.pluginType = 'pill-minor'; bwp.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 997fd56bfc2..636fe0d5892 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -9,7 +9,7 @@ function init() { } cage.label = 'Cannula Age'; - cage.pluginType = 'minor-pill'; + cage.pluginType = 'pill-minor'; cage.updateVisualisation = function updateVisualisation() { diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 078c77a9a02..572dd3ac84b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -10,7 +10,7 @@ function init() { } cob.label = 'Carbs-on-Board'; - cob.pluginType = 'minor-pill'; + cob.pluginType = 'pill-minor'; cob.getData = function getData() { return cob.cobTotal(this.env.treatments, this.env.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3cca62db8fb..306a4288041 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -12,13 +12,19 @@ function init() { return plugins; } - plugins.registerDefaults = function registerDefaults() { + plugins.registerServerDefaults = function registerServerDefaults() { + plugins.register([ require('./pushnotify')() ]); + return plugins; + }; + + plugins.registerClientDefaults = function registerClientDefaults() { plugins.register([ require('./iob')(), require('./cob')(), require('./boluswizardpreview')(), require('./cannulaage')() - ]) + ]); + return plugins; }; plugins.register = function register(all) { @@ -28,11 +34,12 @@ function init() { }); }; - plugins.clientInit = function clientInit(app) { + plugins.init = function init(envOrApp) { enabledPlugins = []; function isEnabled(plugin) { - return app.enabledOptions - && app.enabledOptions.indexOf(plugin.name) > -1; + //TODO: unify client/server env/app + var enable = envOrApp.enabledOptions || envOrApp.enable; + return enable && enable.indexOf(plugin.name) > -1; } _.forEach(allPlugins, function eachPlugin(plugin) { @@ -42,6 +49,7 @@ function init() { } }); console.info('Plugins enabled', enabledPlugins); + return plugins; }; plugins.eachPlugin = function eachPlugin(f) { diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 7ff0828d22d..8202565502b 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -11,7 +11,7 @@ function init() { } iob.label = 'Insulin-on-Board'; - iob.pluginType = 'major-pill'; + iob.pluginType = 'pill-major'; iob.getData = function getData() { return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 02dba2ec468..abedce5ffb1 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -18,7 +18,7 @@ function updatePillText(updatedText, label, info, major) { var pillName = "span.pill." + this.name; - var container = this.pluginType == 'major-pill' ? this.majorPills : this.minorPills; + var container = this.pluginType == 'pill-major' ? this.majorPills : this.minorPills; var pill = container.find(pillName); diff --git a/lib/treatments.js b/lib/treatments.js index 2db959b12f4..c0859a07e5c 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -1,6 +1,6 @@ 'use strict'; -function storage (collection, storage, pushover) { +function storage (env, ctx) { function create (obj, fn) { @@ -47,80 +47,29 @@ function storage (collection, storage, pushover) { if (obj.notes) pbTreat.notes = obj.notes; api( ).insert(pbTreat, function() { - //nothing to do here + //nothing to do here }); } - if (pushover) { - - //since we don't know the time zone on the device viewing the push messeage - //we can only show the amount of adjustment - var timeAdjustment = calcTimeAdjustment(eventTime); - - var text = (obj.glucose ? 'BG: ' + obj.glucose + ' (' + obj.glucoseType + ')' : '') + - (obj.carbs ? '\nCarbs: ' + obj.carbs : '') + - (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + obj.preBolus + ' minutes)' : '')+ - (obj.insulin ? '\nInsulin: ' + obj.insulin : '')+ - (obj.enteredBy ? '\nEntered By: ' + obj.enteredBy : '') + - (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - (obj.notes ? '\nNotes: ' + obj.notes : ''); - - var msg = { - expire: 14400, // 4 hours - message: text, - title: obj.eventType, - sound: 'gamelan', - timestamp: new Date( ), - priority: (obj.eventType == 'Note' ? -1 : 0), - retry: 30 - }; + //TODO: call plugins with processTreatments + ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { + if (plugin.processTreatment) plugin.processTreatment(obj, eventTime, preBolusCarbs, ctx, env) + }); - pushover.send( msg, function( err, result ) { - console.log(result); - }); - } }); } - function calcTimeAdjustment(eventTime) { - - if (!eventTime) return null; - - var now = (new Date()).getTime(), - other = eventTime.getTime(), - past = other < now, - offset = Math.abs(now - other); - - var MINUTE = 60 * 1000, - HOUR = 3600 * 1000; - - var parts = {}; - - if (offset <= MINUTE) - return 'now'; - else if (offset < (HOUR * 2)) - parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; - else - parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; - - if (past) - return parts.value + ' ' + parts.label + ' ago'; - else - return 'in ' + parts.value + ' ' + parts.label; - } - function list (opts, fn) { function find ( ) { - var q = opts && opts.find ? opts.find : { }; - return q; + return opts && opts.find ? opts.find : { }; } - return storage.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); + return ctx.store.limit.call(api().find(find( )).sort({created_at: -1}), opts).toArray(fn); } function api ( ) { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(env.treatments_collection); } api.list = list; diff --git a/static/js/client.js b/static/js/client.js index 33ab07ccd56..5ba08515df1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -489,7 +489,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { console.info('plugin.pluginType', plugin.pluginType); - if (plugin.pluginType == 'minor-pill') { + if (plugin.pluginType == 'pill-minor') { hasMinorPill = true; } }); @@ -1818,7 +1818,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('.serverSettings').show(); } $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.clientInit(app); + Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); init(); }); diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 5d0203528e0..3c9026bf657 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -8,18 +8,17 @@ describe('Entries REST api', function ( ) { before(function (done) { var env = require('../env')( ); this.wares = require('../lib/middleware/')(env); - var store = require('../lib/storage')(env); - this.archive = require('../lib/entries').storage(env.mongo_collection, store); - var bootevent = require('../lib/bootevent'); + this.archive = null; this.app = require('express')( ); this.app.enable('api'); var self = this; + var bootevent = require('../lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; - self.app.use('/', entries(self.app, self.wares, ctx)); - self.archive.create(load('json'), done); + env.store = ctx.store; + self.app.use('/', entries(self.app, self.wares, ctx)); + self.archive = require('../lib/entries')(env, ctx); + self.archive.create(load('json'), done); }); - // store(function ( ) { }); }); after(function (done) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index c668bba9f2d..88a9bc23b9a 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -13,11 +13,12 @@ describe('Status REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - store(function ( ) { - var entriesStorage = require('../lib/entries').storage(env.mongo_collection, store); - self.app.use('/api', api(env, entriesStorage)); + var bootevent = require('../lib/bootevent'); + bootevent(env).boot(function booted (ctx) { + self.app.use('/api', api(env, ctx.entries)); done(); }); + }); it('should be a module', function ( ) { From 9c83998f87be5cba2cbd9bb65122a37f05f78428 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 27 May 2015 18:27:50 -0700 Subject: [PATCH 044/661] initial pushnotify plugin --- lib/plugins/pushnotify.js | 219 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 lib/plugins/pushnotify.js diff --git a/lib/plugins/pushnotify.js b/lib/plugins/pushnotify.js new file mode 100644 index 00000000000..c938af66130 --- /dev/null +++ b/lib/plugins/pushnotify.js @@ -0,0 +1,219 @@ +'use strict'; + +var units = require('../units')(); + +function init() { + + // declare local constants for time differences + var TIME_10_MINS = 10 * 60 * 1000, + TIME_15_MINS = 15 * 60 * 1000, + TIME_30_MINS = TIME_15_MINS * 2; + + //the uploader may the last MBG multiple times, make sure we get a single notification + var lastMBGDate = 0; + + //simple SGV Alert throttling + //TODO: single snooze for websockets and push (when we add push callbacks) + var lastAlert = 0; + var lastSGVDate = 0; + + + function pushnotify() { + return pushnotify; + } + + pushnotify.label = 'Push Notify'; + pushnotify.pluginType = 'server-process'; + + pushnotify.processEntry = function processEntry(entry, ctx, env) { + if (entry.type && entry.date && ctx.pushover) { + if (entry.type == 'mbg' || entry.type == 'meter') { + sendMBGPushover(entry, ctx); + } else if (entry.type == 'sgv') { + sendSGVPushover(entry, ctx); + } + } + }; + + pushnotify.processTreatment = function processTreatment(treatment, eventTime, preBolusCarbs, ctx, env) { + + if (!ctx.pushover) return; + + //since we don't know the time zone on the device viewing the push message + //we can only show the amount of adjustment + var timeAdjustment = calcTimeAdjustment(eventTime); + + var text = (treatment.glucose ? 'BG: ' + treatment.glucose + ' (' + treatment.glucoseType + ')' : '') + + (treatment.carbs ? '\nCarbs: ' + treatment.carbs : '') + + (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + (treatment.insulin ? '\nInsulin: ' + treatment.insulin : '')+ + (treatment.enteredBy ? '\nEntered By: ' + treatment.enteredBy : '') + + (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + (treatment.notes ? '\nNotes: ' + treatment.notes : ''); + + var msg = { + expire: 14400, // 4 hours + message: text, + title: treatment.eventType, + sound: 'gamelan', + timestamp: new Date( ), + priority: (treatment.eventType == 'Note' ? -1 : 0), + retry: 30 + }; + + ctx.pushover.send( msg, function( err, result ) { + console.log(result); + }); + + }; + + + function sendMBGPushover(entry, ctx) { + + if (entry.mbg && entry.type == 'mbg' && entry.date != lastMBGDate) { + var offset = new Date().getTime() - entry.date; + if (offset > TIME_10_MINS) { + console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime()); + } else { + var mbg = entry.mbg; + if (env.DISPLAY_UNITS == 'mmol') { + mbg = units.mgdlToMMOL(mbg); + } + var msg = { + expire: 14400, // 4 hours + message: '\nMeter BG: ' + mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(entry.date), + priority: 0, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + } + lastMBGDate = entry.date; + } + } + + function sendSGVPushover(entry, ctx) { + + if (!entry.sgv || entry.type != 'sgv') { + return; + } + + var now = new Date().getTime(), + offset = new Date().getTime() - entry.date; + + if (offset > TIME_10_MINS || entry.date == lastSGVDate) { + console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); + return; + } + + // initialize message data + var sinceLastAlert = now - lastAlert, + title = 'CGM Alert', + priority = 0, + sound = null, + readingtime = entry.date, + readago = now - readingtime; + + console.info('now: ' + now); + console.info('doc.sgv: ' + entry.sgv); + console.info('doc.direction: ' + entry.direction); + console.info('doc.date: ' + entry.date); + console.info('readingtime: ' + readingtime); + console.info('readago: ' + readago); + + // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high + if (entry.sgv < 39) { + if (sinceLastAlert > TIME_30_MINS) { + title = 'CGM Error'; + priority = 1; + sound = 'persistent'; + } + } else if (entry.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { + title = 'Urgent Low'; + priority = 2; + sound = 'persistent'; + } else if (entry.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { + title = 'Low'; + priority = 1; + sound = 'falling'; + } else if (entry.sgv < 120 && entry.direction == 'DoubleDown') { + title = 'Double Down'; + priority = 1; + sound = 'falling'; + } else if (entry.sgv == 100 && entry.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) + title = 'Perfect'; + priority = 0; + sound = 'cashregister'; + } else if (entry.sgv > 120 && entry.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { + title = 'Double Up'; + priority = 1; + sound = 'climb'; + } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { + title = 'High'; + priority = 1; + sound = 'climb'; + } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + title = 'Urgent High'; + priority = 1; + sound = 'persistent'; + } + + if (sound != null) { + lastAlert = now; + + var msg = { + expire: 14400, // 4 hours + message: 'BG NOW: ' + entry.sgv, + title: title, + sound: sound, + timestamp: new Date(entry.date), + priority: priority, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + } + + + lastSGVDate = entry.date; + } + + function calcTimeAdjustment(eventTime) { + + if (!eventTime) return null; + + var now = (new Date()).getTime(), + other = eventTime.getTime(), + past = other < now, + offset = Math.abs(now - other); + + var MINUTE = 60 * 1000, + HOUR = 3600 * 1000; + + var parts = {}; + + if (offset <= MINUTE) + return 'now'; + else if (offset < (HOUR * 2)) + parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; + else + parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; + + if (past) + return parts.value + ' ' + parts.label + ' ago'; + else + return 'in ' + parts.value + ' ' + parts.label; + } + + return pushnotify(); +} + + +module.exports = init; \ No newline at end of file From 96b7e438809f27eedbc7b1ad331fdf3d7d898730 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 29 May 2015 01:55:31 -0700 Subject: [PATCH 045/661] initial tests for cob --- tests/cob.test.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/cob.test.js diff --git a/tests/cob.test.js b/tests/cob.test.js new file mode 100644 index 00000000000..d2c2dddb121 --- /dev/null +++ b/tests/cob.test.js @@ -0,0 +1,69 @@ +'use strict'; + +var should = require('should'); + +describe('COB', function ( ) { + var cob = require('../lib/plugins/cob')(); + + cob.profile = { + sens: 95 + , carbratio: 18 + , carbs_hr: 30 + }; + + it('should calculate IOB, multiple treatments', function() { + + var treatments = [ + { + "carbs": "100", + "created_at": new Date("2015-05-29T02:03:48.827Z") + }, + { + "carbs": "10", + "created_at": new Date("2015-05-29T03:45:10.670Z") + } + ]; + + var after100 = cob.cobTotal(treatments, new Date("2015-05-29T02:03:49.827Z")); + var before10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:10.670Z")); + var after10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:11.670Z")); + + console.info('>>>>after100:', after100); + console.info('>>>>before10:', before10); + console.info('>>>>after2nd:', after10); + + after100.cob.should.equal(100); + Math.round(before10.cob).should.equal(59); + Math.round(after10.cob).should.equal(69); //WTF == 128 + }); + + it('should calculate IOB, single treatment', function() { + + var treatments = [ + { + "carbs": "8", + "created_at": new Date("2015-05-29T04:40:40.174Z") + } + ]; + + var rightAfterCorrection = new Date("2015-05-29T04:41:40.174Z"); + var later1 = new Date("2015-05-29T05:04:40.174Z"); + var later2 = new Date("2015-05-29T05:20:00.174Z"); + var later3 = new Date("2015-05-29T05:50:00.174Z"); + var later4 = new Date("2015-05-29T06:50:00.174Z"); + + var result1 = cob.cobTotal(treatments, rightAfterCorrection); + var result2 = cob.cobTotal(treatments, later1); + var result3 = cob.cobTotal(treatments, later2); + var result4 = cob.cobTotal(treatments, later3); + var result5 = cob.cobTotal(treatments, later4); + + result1.cob.should.equal(8); + result2.cob.should.equal(6); + result3.cob.should.equal(0); + result4.cob.should.equal(0); + result5.cob.should.equal(0); + }); + + +}); \ No newline at end of file From 3125df976ff276fa7471935e1bc5e3a592a47f84 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 29 May 2015 01:56:10 -0700 Subject: [PATCH 046/661] cob clean up --- lib/plugins/cob.js | 63 +++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 572dd3ac84b..a96a3084568 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -1,6 +1,7 @@ 'use strict'; -var iob = require('./iob')() +var _ = require('lodash') + , iob = require('./iob')() , moment = require('moment'); function init() { @@ -31,42 +32,42 @@ function init() { var lastDecayedBy = new Date('1/1/1970'); var carbs_hr = this.profile.carbs_hr; - for (var t in treatments) { - if (treatments.hasOwnProperty(t)) { - var treatment = treatments[t]; - if (treatment.carbs && treatment.created_at < time) { - lastCarbs = treatment; - var cCalc = this.cobCalc(treatment, lastDecayedBy, time); - var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - if (decaysin_hr > -10) { - var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; - var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; - var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); - if (delayMinutes > 0) { - cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); - decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; - } + _.forEach(treatments, function eachTreatment(treatment) { + if (treatment.carbs && treatment.created_at < time) { + lastCarbs = treatment; + var cCalc = cob.cobCalc(treatment, lastDecayedBy, time); + var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; + if (decaysin_hr > -10) { + var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var avgActivity = (actStart + actEnd) / 2; + var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; + var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + if (delayMinutes > 0) { + cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); + decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; } + } - if (cCalc) { - lastDecayedBy = cCalc.decayedBy; - } + console.info('cCalc', cCalc, ' --- treatment', treatment); - if (decaysin_hr > 0) { - //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); - isDecaying = cCalc.isDecaying; - } - else { - totalCOB = 0; - } + if (cCalc) { + lastDecayedBy = cCalc.decayedBy; + } + if (decaysin_hr > 0) { + //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); + console.info('COB contrib', cCalc.initialCarbs, decaysin_hr * carbs_hr); + totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + isDecaying = cCalc.isDecaying; + } else { + totalCOB = 0; } + } - } + }); + var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; return { From 049c9008db2a1acaf810252db38d0935b481b351 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 08:58:26 -0700 Subject: [PATCH 047/661] fixed minor annoyance --- static/js/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/client.js b/static/js/client.js index 33ab07ccd56..50b3267aadc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1206,6 +1206,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $(this).removeClass('playing'); }); + closeNotification(); $('#container').removeClass('alarming'); // only emit ack if client invoke by button press From b57a4bd7d18ed58f51e63c4fe6f52bfc76480486 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 19:14:00 -0700 Subject: [PATCH 048/661] improve client perf cache displayBG for treatments use _.debouce to avoid calling expensive functions too often --- bundle/bundle.source.js | 3 +- lib/plugins/index.js | 15 +++- static/js/client.js | 161 ++++++++++++++++++---------------------- 3 files changed, 86 insertions(+), 93 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 2558c36e970..d36e76b0974 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -8,8 +8,7 @@ plugins: require('../lib/plugins/')() }; - console.info('plugins', window.Nightscout.plugins); - + window._ = require('lodash'); window.Nightscout.plugins.registerDefaults(); console.info("Nightscout bundle ready", window.Nightscout); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3cca62db8fb..1f29faea388 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -41,7 +41,6 @@ function init() { enabledPlugins.push(plugin); } }); - console.info('Plugins enabled', enabledPlugins); }; plugins.eachPlugin = function eachPlugin(f) { @@ -52,12 +51,20 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - var filtered = _.filter(enabledPlugins, function filterPlugins(plugin) { + plugins.shownPlugins = function(clientSettings) { + return _.filter(enabledPlugins, function filterPlugins(plugin) { return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; }); + }; + + plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { + _.forEach(plugins.shownPlugins(clientSettings), f); + }; - _.forEach(filtered, f); + plugins.hasShownType = function hasShownType(pluginType, clientSettings) { + return _.find(plugins.shownPlugins(clientSettings), function findWithType(plugin) { + return plugin.pluginType == pluginType; + }) != undefined; }; plugins.setEnvs = function setEnvs(env) { diff --git a/static/js/client.js b/static/js/client.js index 50b3267aadc..44f2644798c 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -5,6 +5,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; 'use strict'; var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds , ONE_MIN_IN_MS = 60000 @@ -293,7 +294,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // clears the current user brush and resets to the current real time data - function updateBrushToNow(skipBrushing) { + var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { // get current time range var dataRange = d3.extent(data, dateFn); @@ -310,7 +311,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // clear user brush tracking brushInProgress = false; } - } + }, DEBOUNCE_MS); function brushStarted() { // update the opacity of the context data points to brush extent @@ -359,8 +360,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return errorDisplay; } - // function to call when context chart is brushed - function brushed(skipTimer) { + var brushed = _.debounce(function brushed(skipTimer) { if (!skipTimer) { // set a timer to reset focus chart to real-time data @@ -487,16 +487,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } }); - Nightscout.plugins.eachShownPlugins(browserSettings, function eachPlugin(plugin) { - console.info('plugin.pluginType', plugin.pluginType); - if (plugin.pluginType == 'minor-pill') { - hasMinorPill = true; - } - }); - - $('.container').toggleClass('has-minor-pills', hasMinorPill); - - // update data for all the plugins, before updating visualisations Nightscout.plugins.setEnvs(env); @@ -552,7 +542,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.removeClass('urgent warning inrange'); } - updatePlugins(focusPoint.y, retroTime); + updatePlugins(focusPoint && focusPoint.y, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -738,7 +728,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function prepareTreatCircles(sel) { sel.attr('cx', function (d) { return xScale(d.created_at); }) - .attr('cy', function (d) { return yScale(scaledTreatmentBG(d)); }) + .attr('cy', function (d) { return yScale(d.displayBG); }) .attr('r', function () { return dotRadius('mbg'); }) .attr('stroke-width', 2) .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) @@ -747,46 +737,39 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return sel; } - try { - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - //TODO: integrate with drawTreatment() - - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - } catch (err) { - console.error(err); - } - } + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + + (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + + (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + + (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + + (d.notes ? 'Notes: ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + }, DEBOUNCE_MS); // called for initial update and updates for resize - function updateChart(init) { + var updateChart = _.debounce(function updateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -1133,7 +1116,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (init) { $('.container').removeClass('loading'); } - } + }, DEBOUNCE_MS); function sgvToColor(sgv) { var color = 'grey'; @@ -1255,23 +1238,30 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - function scaledTreatmentBG(treatment) { + function displayTreatmentBG(treatment) { function calcBGByTime(time) { - var closeBGs = data.filter(function(d) { - if (!d.y) { - return false; - } else { - return Math.abs((new Date(d.date)).getTime() - time) <= SIX_MINS_IN_MS; - } + var withBGs = _.filter(data, function(d) { + return d.y && d.type == 'sgv'; }); - var totalBG = 0; - closeBGs.forEach(function(d) { - totalBG += Number(d.y); + var beforeTreatment = _.findLast(withBGs, function (d) { + return d.date.getTime() <= time; + }); + var afterTreatment = _.find(withBGs, function (d) { + return d.date.getTime() >= time; }); - return totalBG > 0 ? (totalBG / closeBGs.length) : 450; + var calcedBG = 0; + if (beforeTreatment && afterTreatment) { + calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; + } else if (beforeTreatment) { + calcedBG = Number(beforeTreatment.y); + } else if (afterTreatment) { + calcedBG = Number(afterTreatment.y); + } + + return calcedBG || 400; } var treatmentGlucose = null; @@ -1345,7 +1335,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .data(arc_data) .enter() .append('g') - .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(scaledTreatmentBG(treatment)) + ')') + .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') .on('mouseover', function () { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + @@ -1615,16 +1605,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - // look for resize but use timer to only call the update script when a resize stops - var resizeTimer; - function updateChartSoon(updateToNow) { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(function () { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - }, 200); + //updateBrushToNow and updateChart are both _.debounced + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); } function visibilityChanged() { @@ -1633,13 +1619,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - updateChartSoon(true); + refreshChart(true); } } - window.onresize = function () { - updateChartSoon() - }; + window.onresize = refreshChart; document.addEventListener('webkitvisibilitychange', visibilityChanged); @@ -1663,7 +1647,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; li.addClass('selected'); var hours = Number(li.data('hours')); foucusRangeMS = hours * 60 * 60 * 1000; - updateChartSoon(); + refreshChart(); }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1680,11 +1664,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; prevSGV = d[0][d[0].length - 2]; } - treatments = d[3]; - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - }); - profile = d[4][0]; cal = d[5][d[5].length-1]; devicestatusData = d[6]; @@ -1721,6 +1700,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; d.color = 'transparent'; }); + treatments = d[3]; + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + updateTitle(); if (!isInitialData) { isInitialData = true; @@ -1821,6 +1807,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('#treatmentDrawerToggle').toggle(app.careportalEnabled); Nightscout.plugins.clientInit(app); browserSettings = getBrowserSettings(browserStorage); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('minor-pill', browserSettings)); init(); }); From 601b1faa39dee023968a36ef1f5ee9ab46a5229e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 30 May 2015 19:38:28 -0700 Subject: [PATCH 049/661] hide uploaderBattery pill till we have data for it --- static/css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index d338b93d23c..4f1bc73b5e9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -178,6 +178,10 @@ body { background: #808080; } +#uploaderBattery { + display: none; +} + #uploaderBattery label { padding: 0 !important; } From bf03d74a9cfad78bf08dac12a98b5e311942527d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 31 May 2015 09:26:30 +0300 Subject: [PATCH 050/661] Optimization for querySelectorAll, which seems to be non-performant and used by D3 a _lot_. One-liner to disable drawing of treatments not visible on screen. --- static/js/client.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/static/js/client.js b/static/js/client.js index 44f2644798c..14a1dcdb31b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,6 +1,46 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +/* + * query + * Abstraction to querySelectorAll for increased + * performance and greater usability + * @param {String} selector + * @param {Element} context (optional) + * @return {Array} + */ + +(function(win){ + 'use strict'; + + console.log("Foo"); + + var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, + periodRe = /\./g, + slice = [].slice; + + win.query = function(selector, context){ + context = context || document; + // Redirect call to the more performant function + // if it's a simple selector and return an array + // for easier usage + if(simpleRe.test(selector)){ + switch(selector[0]){ + case '#': + return [context.getElementById(selector.substr(1))]; + case '.': + return slice.call(context.getElementsByClassName(selector.substr(1).replace(periodRe, ' '))); + default: + return slice.call(context.getElementsByTagName(selector)); + } + } + // If not a simple selector, query the DOM as usual + // and return an array for easier usage + return slice.call(context.querySelectorAll(selector)); + }; + +})(this); + (function () { 'use strict'; @@ -1299,6 +1339,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (!treatment.carbs && !treatment.insulin) return; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; var insulin = treatment.insulin || 1; From 484610f150a17dcdaf24c7825cd21e632083b353 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 31 May 2015 09:34:10 +0300 Subject: [PATCH 051/661] Removing debug messaging --- static/js/client.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 14a1dcdb31b..39ca213fee1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -12,8 +12,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; (function(win){ 'use strict'; - - console.log("Foo"); var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, periodRe = /\./g, From 7f09e037d4e6ed80a845c1cd141de4685e324a09 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 09:39:22 -0700 Subject: [PATCH 052/661] upgrade to current d3.js --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index 372c86fdf38..da22cab72e3 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", - "d3": "3.4.3", + "d3": "~3.5.5", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", From 2ca9c8ac73bae462d63c223d3a21527a6dc8f495 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 09:40:47 -0700 Subject: [PATCH 053/661] is the querySelectorAll optimization used? --- static/js/client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 39ca213fee1..d9912ad61fe 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -13,11 +13,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; (function(win){ 'use strict'; - var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, + var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, periodRe = /\./g, slice = [].slice; win.query = function(selector, context){ + console.info('win.query() called'); context = context || document; // Redirect call to the more performant function // if it's a simple selector and return an array From db85587df7896d9689fcca7a58e4df852dd7a99f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 10:04:20 -0700 Subject: [PATCH 054/661] remove the querySelectorAll optimization since it's not getting used --- static/js/client.js | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d9912ad61fe..d6ee629b1ab 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,45 +1,6 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -/* - * query - * Abstraction to querySelectorAll for increased - * performance and greater usability - * @param {String} selector - * @param {Element} context (optional) - * @return {Array} - */ - -(function(win){ - 'use strict'; - - var simpleRe = /^(#?[\w-]+|\.[\w-.]+)$/, - periodRe = /\./g, - slice = [].slice; - - win.query = function(selector, context){ - console.info('win.query() called'); - context = context || document; - // Redirect call to the more performant function - // if it's a simple selector and return an array - // for easier usage - if(simpleRe.test(selector)){ - switch(selector[0]){ - case '#': - return [context.getElementById(selector.substr(1))]; - case '.': - return slice.call(context.getElementsByClassName(selector.substr(1).replace(periodRe, ' '))); - default: - return slice.call(context.getElementsByTagName(selector)); - } - } - // If not a simple selector, query the DOM as usual - // and return an array for easier usage - return slice.call(context.querySelectorAll(selector)); - }; - -})(this); - (function () { 'use strict'; @@ -1338,8 +1299,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (!treatment.carbs && !treatment.insulin) return; - // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; From 982e0ca18e2a5df7559ee6842acdf1c1ea4d8946 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 31 May 2015 16:32:00 -0700 Subject: [PATCH 055/661] use the carbs set on the treatment, instead of cCalc.initialCarbs, keep it simple for now --- lib/plugins/cob.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 078c77a9a02..0ab8dc7ee25 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -56,7 +56,7 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * carbs_hr); //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } From 4dfa2c438c76ab00efd36b664c135c0241dcb898 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:20:18 -0700 Subject: [PATCH 056/661] ctx everywhere; fixed security.test.js --- app.js | 7 +- lib/api/devicestatus/index.js | 10 +- lib/api/entries/index.js | 22 ++-- lib/api/index.js | 10 +- lib/api/profile/index.js | 14 +-- lib/api/treatments/index.js | 10 +- lib/bootevent.js | 8 +- lib/devicestatus.js | 6 +- lib/mqtt.js | 16 +-- lib/pebble.js | 17 ++- lib/profile.js | 4 +- lib/websocket.js | 12 +-- server.js | 7 +- tests/api.entries.test.js | 1 - tests/pebble.test.js | 193 +++++++++++++++++----------------- tests/security.test.js | 27 +++-- 16 files changed, 173 insertions(+), 191 deletions(-) diff --git a/app.js b/app.js index cab4dbe1655..c15b5e1a6df 100644 --- a/app.js +++ b/app.js @@ -6,13 +6,8 @@ function create (env, ctx) { // api and json object variables /////////////////////////////////////////////////// var api = require('./lib/api/')(env, ctx); - var pebble = ctx.pebble; var app = express(); - app.entries = ctx.entries; - app.treatments = ctx.treatments; - app.profiles = ctx.profiles; - app.devicestatus = ctx.devicestatus; var appInfo = env.name + ' ' + env.version; app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. @@ -32,7 +27,7 @@ function create (env, ctx) { // pebble data - app.get('/pebble', pebble(ctx.entries, ctx.treatments, ctx.profiles, ctx.devicestatus, env)); + app.get('/pebble', ctx.pebble); //app.get('/package.json', software); diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 7a5f65ee1f0..05980ffbe36 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, devicestatus) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -21,16 +21,16 @@ function configure (app, wares, devicestatus) { if (!q.count) { q.count = 10; } - devicestatus.list(q, function (err, results) { + ctx.devicestatus.list(q, function (err, results) { return res.json(results); }); }); - function config_authed (app, api, wares, devicestatus) { + function config_authed (app, api, wares, ctx) { api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { var obj = req.body; - devicestatus.create(obj, function (err, created) { + ctx.devicestatus.create(obj, function (err, created) { if (err) res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); else @@ -41,7 +41,7 @@ function configure (app, wares, devicestatus) { } if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { - config_authed(app, api, wares, devicestatus); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1593e543748..0e5e6a4c1fe 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -7,8 +7,8 @@ var sgvdata = require('sgvdata'); /**********\ * Entries \**********/ -function configure (app, wares, core) { - var entries = core.entries; +function configure (app, wares, ctx) { + var entries = ctx.entries; var express = require('express'), api = express.Router( ) ; @@ -128,30 +128,30 @@ function configure (app, wares, core) { switch (model) { case 'treatments': case 'devicestatus': - //TODO: profiles not working now, maybe profiles are special - //case 'profiles': - req.model = core[model]; + //TODO: profile not working now, maybe profiles are special + //case 'profile': + req.model = ctx[model]; break; case 'meter': case 'mbg': find.type = 'mbg'; - req.model = core.entries; + req.model = ctx.entries; break; case 'cal': find.type = 'cal'; - req.model = core.entries; + req.model = ctx.entries; break; case 'sensor': find.type = 'sensor'; - req.model = core.entries; + req.model = ctx.entries; break; case 'sgv': find.type = 'sgv'; - req.model = core.entries; + req.model = ctx.entries; break; default: - req.model = core.entries; + req.model = ctx.entries; break; } @@ -203,7 +203,7 @@ function configure (app, wares, core) { function query_models (req, res, next) { // If "?count=" is present, use that number to decided how many to return. if (!req.model) { - req.model = core.entries; + req.model = ctx.entries; } var query = req.query; if (!query.count) { query.count = 10 } diff --git a/lib/api/index.js b/lib/api/index.js index 7c67de12a3f..a53840fd5e7 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,6 +1,6 @@ 'use strict'; -function create (env, core) { +function create (env, ctx) { var express = require('express'), app = express( ) ; @@ -46,10 +46,10 @@ function create (env, core) { } // Entries and settings - app.use('/', require('./entries/')(app, wares, core)); - app.use('/', require('./treatments/')(app, wares, core.treatments)); - app.use('/', require('./profile/')(app, wares, core.profiles)); - app.use('/', require('./devicestatus/')(app, wares, core.devicestatus)); + app.use('/', require('./entries/')(app, wares, ctx)); + app.use('/', require('./treatments/')(app, wares, ctx)); + app.use('/', require('./profile/')(app, wares, ctx)); + app.use('/', require('./devicestatus/')(app, wares, ctx)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index bc499141ffd..c8f62323ddd 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, profile) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -17,24 +17,24 @@ function configure (app, wares, profile) { // List profiles available api.get('/profile/', function(req, res) { - profile.list(function (err, attribute) { + ctx.profile.list(function (err, attribute) { return res.json(attribute); }); }); // List current active record (in current state LAST record is current active) api.get('/profile/current', function(req, res) { - profile.last( function(err, records) { + ctx.profile.last( function(err, records) { return res.json(records.length > 0 ? records[0] : null); }); }); - function config_authed (app, api, wares, profile) { + function config_authed (app, api, wares, ctx) { // create new record api.post('/profile/', wares.verifyAuthorization, function(req, res) { var data = req.body; - profile.create(data, function (err, created) { + ctx.profile.create(data, function (err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); console.log('Error creating profile'); @@ -50,7 +50,7 @@ function configure (app, wares, profile) { // update record api.put('/profile/', wares.verifyAuthorization, function(req, res) { var data = req.body; - profile.save(data, function (err, created) { + ctx.profile.save(data, function (err, created) { if (err) { res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); console.log('Error saving profile'); @@ -65,7 +65,7 @@ function configure (app, wares, profile) { } if (app.enabled('api')) { - config_authed(app, api, wares, profile); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 7d7e89d0b5d..eb093d2b4cb 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -2,7 +2,7 @@ var consts = require('../../constants'); -function configure (app, wares, treatments) { +function configure (app, wares, ctx) { var express = require('express'), api = express.Router( ); @@ -17,16 +17,16 @@ function configure (app, wares, treatments) { // List treatments available api.get('/treatments/', function(req, res) { - treatments.list({find: req.params}, function (err, results) { + ctx.treatments.list({find: req.params}, function (err, results) { return res.json(results); }); }); - function config_authed (app, api, wares, treatments) { + function config_authed (app, api, wares, ctx) { api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { var treatment = req.body; - treatments.create(treatment, function (err, created) { + ctx.treatments.create(treatment, function (err, created) { if (err) res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); else @@ -37,7 +37,7 @@ function configure (app, wares, treatments) { } if (app.enabled('api') && app.enabled('careportal')) { - config_authed(app, api, wares, treatments); + config_authed(app, api, wares, ctx); } return api; diff --git a/lib/bootevent.js b/lib/bootevent.js index 2f81a590b50..dd46a6eb92f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -20,15 +20,15 @@ function boot (env) { ctx.pushover = require('./pushover')(env); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx.store); - ctx.profiles = require('./profile')(env.profile_collection, ctx.store); - ctx.pebble = require('./pebble'); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); console.info("Ensuring indexes"); store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profiles( ), ctx.profiles.indexedFields); + store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); next( ); }) diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 00d9ce2d9f1..a831e936bab 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -1,6 +1,6 @@ 'use strict'; -function storage (collection, storage) { +function storage (collection, ctx) { function create(obj, fn) { if (! obj.hasOwnProperty("created_at")){ @@ -29,11 +29,11 @@ function storage (collection, storage) { function list(opts, fn) { var q = opts && opts.find ? opts.find : { }; - return storage.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); + return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); } function api() { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(collection); } diff --git a/lib/mqtt.js b/lib/mqtt.js index cfc4675f9f9..f19a32641a3 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -168,7 +168,7 @@ function iter_mqtt_record_stream (packet, prop, sync) { return stream.pipe(es.map(map)); } -function configure(env, core, devicestatus) { +function configure(env, ctx) { var uri = env['MQTT_MONITOR']; var opts = { encoding: 'binary', @@ -212,37 +212,37 @@ function configure(env, core, devicestatus) { console.log("WRITE TO MONGO"); var download_timestamp = moment(packet.download_timestamp); if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(core.persist(function empty(err, result) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING MERGED SGV TO MONGO", err, result); })); iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(core.persist(function empty(err, result) { + .pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING Cal TO MONGO", err, result.length); })); iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(core.persist(function empty(err, result) { + .pipe(ctx.entries.persist(function empty(err, result) { console.log("DONE WRITING Meter TO MONGO", err, result.length); })); } packet.type = "download"; - devicestatus.create({ + ctx.devicestatus.create({ uploaderBattery: packet.uploader_battery, created_at: download_timestamp.toISOString() }, function empty(err, result) { console.log("DONE WRITING TO MONGO devicestatus ", result, err); }); - core.create([ packet ], function empty(err, res) { + ctx.entries.create([ packet ], function empty(err, res) { console.log("Download written to mongo: ", packet) }); - // core.write(packet); + // ctx.entries.write(packet); break; default: console.log(topic, 'on message', 'msg', msg); - // core.write(msg); + // ctx.entries.write(msg); break; } }); diff --git a/lib/pebble.js b/lib/pebble.js index 4a2910dbc52..4804aa7be09 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -62,7 +62,7 @@ function pebble (req, res) { async.parallel({ devicestatus: function (callback) { - req.devicestatus.last(function (err, value) { + req.ctx.devicestatus.last(function (err, value) { if (!err && value) { uploaderBattery = value.uploaderBattery; } else { @@ -96,7 +96,7 @@ function pebble (req, res) { , cal: function(callback) { if (req.rawbg) { var cq = { count: req.count, find: {type: 'cal'} }; - req.entries.list(cq, function (err, results) { + req.ctx.entries.list(cq, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -119,7 +119,7 @@ function pebble (req, res) { , entries: function(callback) { var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; - req.entries.list(q, function(err, results) { + req.ctx.entries.list(q, function(err, results) { if (!err && results) { results.forEach(function(element, index) { if (element) { @@ -162,7 +162,7 @@ function pebble (req, res) { function loadTreatments(req, earliest_data, fn) { if (req.iob) { var q = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; - req.treatments.list(q, fn); + req.ctx.treatments.list(q, fn); } else { fn(null, []); } @@ -170,18 +170,15 @@ function loadTreatments(req, earliest_data, fn) { function loadProfile(req, fn) { if (req.iob) { - req.profile.list(fn); + req.ctx.profile.list(fn); } else { fn(null, []); } } -function configure (entries, treatments, profile, devicestatus, env) { +function configure (env, ctx) { function middle (req, res, next) { - req.entries = entries; - req.treatments = treatments; - req.profile = profile; - req.devicestatus = devicestatus; + req.ctx = ctx; req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1; req.iob = env.enable && env.enable.indexOf('iob') > -1; req.mmol = (req.query.units || env.DISPLAY_UNITS) === 'mmol'; diff --git a/lib/profile.js b/lib/profile.js index ec2182198db..7a7486411cb 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -1,7 +1,7 @@ 'use strict'; -function storage (collection, storage) { +function storage (collection, ctx) { var ObjectID = require('mongodb').ObjectID; function create (obj, fn) { @@ -28,7 +28,7 @@ function storage (collection, storage) { } function api () { - return storage.pool.db.collection(collection); + return ctx.store.pool.db.collection(collection); } api.list = list; diff --git a/lib/websocket.js b/lib/websocket.js index 3444c6ae60b..9681ae76b8b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,6 @@ var async = require('async'); -function websocket (env, server, entries, treatments, profiles, devicestatus) { +function websocket (env, ctx, server) { "use strict"; // CONSTANTS var ONE_HOUR = 3600000, @@ -168,7 +168,7 @@ function update() { async.parallel({ entries: function(callback) { var q = { find: {"date": {"$gte": earliest_data}} }; - entries.list(q, function (err, results) { + ctx.entries.list(q, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -200,7 +200,7 @@ function update() { } , cal: function(callback) { var cq = { count: 1, find: {"type": "cal"} }; - entries.list(cq, function (err, results) { + ctx.entries.list(cq, function (err, results) { if (!err && results) { results.forEach(function (element) { if (element) { @@ -219,7 +219,7 @@ function update() { } , treatments: function(callback) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; - treatments.list(tq, function (err, results) { + ctx.treatments.list(tq, function (err, results) { treatmentData = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); @@ -229,7 +229,7 @@ function update() { }); } , profile: function(callback) { - profiles.list(function (err, results) { + ctx.profile.list(function (err, results) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. results.forEach(function (element, index, array) { @@ -244,7 +244,7 @@ function update() { }); } , devicestatus: function(callback) { - devicestatus.last(function (err, result) { + ctx.devicestatus.last(function (err, result) { if (!err && result) { devicestatusData = { uploaderBattery: result.uploaderBattery diff --git a/server.js b/server.js index bd8c7e0497c..69ff7da48c2 100644 --- a/server.js +++ b/server.js @@ -45,22 +45,21 @@ function create (app) { var bootevent = require('./lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); if (env.MQTT_MONITOR) { - var mqtt = require('./lib/mqtt')(env, app.entries, app.devicestatus); + var mqtt = require('./lib/mqtt')(env, ctx); var es = require('event-stream'); - es.pipeline(mqtt.entries, app.entries.map( ), mqtt.every(app.entries)); + es.pipeline(mqtt.entries, ctx.entries.map( ), mqtt.every(ctx.entries)); } /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// var websocket = require('./lib/websocket'); - var io = websocket(env, server, app.entries, app.treatments, app.profiles, app.devicestatus); + var io = websocket(env, ctx, server); }) ; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 3c9026bf657..00e9df932bb 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -14,7 +14,6 @@ describe('Entries REST api', function ( ) { var self = this; var bootevent = require('../lib/bootevent'); bootevent(env).boot(function booted (ctx) { - env.store = ctx.store; self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 030be48a222..ffef87af812 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -2,106 +2,101 @@ var request = require('supertest'); var should = require('should'); -//Mock entries -var entries = { - list: function(opts, callback) { - var sgvs = [ - { device: 'dexcom', - date: 1422727301000, - dateString: 'Sat Jan 31 10:01:41 PST 2015', - sgv: 82, - direction: 'Flat', - type: 'sgv', - filtered: 113984, - unfiltered: 111920, - rssi: 179, - noise: 1 - }, - { device: 'dexcom', - date: 1422727001000, - dateString: 'Sat Jan 31 09:56:41 PST 2015', - sgv: 84, - direction: 'Flat', - type: 'sgv', - filtered: 115680, - unfiltered: 113552, - rssi: 179, - noise: 1 - }, - { device: 'dexcom', - date: 1422726701000, - dateString: 'Sat Jan 31 09:51:41 PST 2015', - sgv: 86, - direction: 'Flat', - type: 'sgv', - filtered: 117808, - unfiltered: 114640, - rssi: 169, - noise: 1 - }, - { device: 'dexcom', - date: 1422726401000, - dateString: 'Sat Jan 31 09:46:41 PST 2015', - sgv: 88, - direction: 'Flat', - type: 'sgv', - filtered: 120464, - unfiltered: 116608, - rssi: 175, - noise: 1 - }, - { device: 'dexcom', - date: 1422726101000, - dateString: 'Sat Jan 31 09:41:41 PST 2015', - sgv: 91, - direction: 'Flat', - type: 'sgv', - filtered: 124048, - unfiltered: 118880, - rssi: 174, - noise: 1 +//Mocked ctx +var ctx = { + entries: { + list: function (opts, callback) { + var sgvs = [ + { device: 'dexcom', + date: 1422727301000, + dateString: 'Sat Jan 31 10:01:41 PST 2015', + sgv: 82, + direction: 'Flat', + type: 'sgv', + filtered: 113984, + unfiltered: 111920, + rssi: 179, + noise: 1 + }, + { device: 'dexcom', + date: 1422727001000, + dateString: 'Sat Jan 31 09:56:41 PST 2015', + sgv: 84, + direction: 'Flat', + type: 'sgv', + filtered: 115680, + unfiltered: 113552, + rssi: 179, + noise: 1 + }, + { device: 'dexcom', + date: 1422726701000, + dateString: 'Sat Jan 31 09:51:41 PST 2015', + sgv: 86, + direction: 'Flat', + type: 'sgv', + filtered: 117808, + unfiltered: 114640, + rssi: 169, + noise: 1 + }, + { device: 'dexcom', + date: 1422726401000, + dateString: 'Sat Jan 31 09:46:41 PST 2015', + sgv: 88, + direction: 'Flat', + type: 'sgv', + filtered: 120464, + unfiltered: 116608, + rssi: 175, + noise: 1 + }, + { device: 'dexcom', + date: 1422726101000, + dateString: 'Sat Jan 31 09:41:41 PST 2015', + sgv: 91, + direction: 'Flat', + type: 'sgv', + filtered: 124048, + unfiltered: 118880, + rssi: 174, + noise: 1 + } + ]; + + var cals = [ + { device: 'dexcom', + date: 1422647711000, + dateString: 'Fri Jan 30 11:55:11 PST 2015', + slope: 895.8571693029189, + intercept: 34281.06876195567, + scale: 1, + type: 'cal' + } + ]; + + var count = (opts && opts.count) || 1; + + if (opts && opts.find && opts.find.sgv) { + callback(null, sgvs.slice(0, count)); + } else if (opts && opts.find && opts.find.type == 'cal') { + callback(null, cals.slice(0, count)); } - ]; - - var cals = [ - { device: 'dexcom', - date: 1422647711000, - dateString: 'Fri Jan 30 11:55:11 PST 2015', - slope: 895.8571693029189, - intercept: 34281.06876195567, - scale: 1, - type: 'cal' - } - ]; - - var count = (opts && opts.count) || 1; - - if (opts && opts.find && opts.find.sgv) { - callback(null, sgvs.slice(0, count)); - } else if (opts && opts.find && opts.find.type == 'cal') { - callback(null, cals.slice(0, count)); + } + }, treatments: { + list: function (callback) { + callback(null, []); + } + }, profile: { + list: function (callback) { + callback(null, []); + } + }, devicestatus: { + last: function (callback) { + callback(null, {uploaderBattery: 100}); } } -}; - -//Mock devicestatus -var treatments = { - list: function(callback) { - callback(null, []); - } -}; - -var profile = { - list: function(callback) { - callback(null, []); - } -}; - -var devicestatus = { - last: function(callback) { - callback(null, {uploaderBattery: 100}); - } -}; +} describe('Pebble Endpoint without Raw', function ( ) { var pebble = require('../lib/pebble'); @@ -109,7 +104,7 @@ describe('Pebble Endpoint without Raw', function ( ) { var env = require('../env')( ); this.app = require('express')( ); this.app.enable('api'); - this.app.use('/pebble', pebble(entries, treatments, profile, devicestatus, env)); + this.app.use('/pebble', pebble(env, ctx)); done(); }); @@ -174,7 +169,7 @@ describe('Pebble Endpoint with Raw', function ( ) { envRaw.enable = "rawbg"; this.appRaw = require('express')( ); this.appRaw.enable('api'); - this.appRaw.use('/pebble', pebbleRaw(entries, treatments, profile, devicestatus, envRaw)); + this.appRaw.use('/pebble', pebbleRaw(envRaw, ctx)); done(); }); diff --git a/tests/security.test.js b/tests/security.test.js index 60b63f6aa25..8852aba855b 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,19 +10,16 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - var ctx = { }; - ctx.wares = require('../lib/middleware/')(env); - ctx.store = require('../lib/storage')(env); - ctx.archive = require('../lib/entries').storage(env.mongo_collection, ctx.store); - - ctx.store(function ( ) { - ctx.app = api(env, ctx.wares, ctx.archive); + var bootevent = require('../lib/bootevent'); + bootevent(env).boot(function booted (ctx) { + var wares = require('../lib/middleware/')(env); + ctx.app = api(env, wares, ctx); scope.app = ctx.app; - ctx.archive.create(load('json'), fn); - scope.archive = ctx.archive; + scope.entries = ctx.entries; + ctx.entries.create(load('json'), function () { + fn(ctx); + }); }); - - return ctx; } /* before(function (done) { @@ -30,14 +27,14 @@ describe('API_SECRET', function ( ) { }); */ after(function (done) { - scope.archive( ).remove({ }, done); + scope.entries( ).remove({ }, done); }); it('should work fine absent', function (done) { delete process.env.API_SECRET; var env = require('../env')( ); should.not.exist(env.api_secret); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { ctx.app.enabled('api').should.be.false; ping_status(ctx.app, again); function again ( ) { @@ -53,7 +50,7 @@ describe('API_SECRET', function ( ) { process.env.API_SECRET = 'this is my long pass phrase'; var env = require('../env')( ); env.api_secret.should.equal(known); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); ctx.app.enabled('api').should.be.true; // ping_status(ctx.app, done); @@ -74,7 +71,7 @@ describe('API_SECRET', function ( ) { process.env.API_SECRET = 'this is my long pass phrase'; var env = require('../env')( ); env.api_secret.should.equal(known); - var ctx = setup_app(env, function ( ) { + setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); ctx.app.enabled('api').should.be.true; // ping_status(ctx.app, done); From cdc9bf0da983a81dcc8e4fed5218efb8240e97c5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:45:45 -0700 Subject: [PATCH 057/661] fix merge when there are no sensor records --- lib/mqtt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index f19a32641a3..a89d5663386 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -106,9 +106,9 @@ function sgvSensorMerge(packet) { , sensorsLength = sensors.length; if (sgvsLength >= 0 && sensorsLength == 0) { - merged.concat(sgvs); + merged = sgvs; } else { - var smallerLength = sgvsLength < sensorsLength ? sgvsLength : sensorsLength; + var smallerLength = Math.min(sgvsLength, sensorsLength); for (var i = 1; i <= smallerLength; i++) { var sgv = sgvs[sgvsLength - i]; var sensor = sensors[sensorsLength - i]; From 6ce422136736dbfab65b8541c8e4380db02ad671 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 4 Jun 2015 23:46:19 -0700 Subject: [PATCH 058/661] clean up --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 9681ae76b8b..c517fc85f9f 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -163,7 +163,7 @@ function update() { profileData = []; devicestatusData = {}; var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY*8); + var treatment_earliest_data = now - (ONE_DAY*8); async.parallel({ entries: function(callback) { From e5431b625c511366d98ae2be557aecaf9a28ef87 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 5 Jun 2015 17:45:24 -0700 Subject: [PATCH 059/661] stop time ago and battery from showing till we have data --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 35304232d04..b33cc6abd12 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -438,6 +438,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } function updateBGDelta(prevEntry, currentEntry) { @@ -1111,9 +1114,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); - if (init) { - $('.container').removeClass('loading'); - } }, DEBOUNCE_MS); function sgvToColor(sgv) { From 2a201ca2afe2acbabe6057459a2f66fefcb56c29 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 5 Jun 2015 17:45:24 -0700 Subject: [PATCH 060/661] stop time ago and battery from showing till we have data --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index d6ee629b1ab..ca222e187b7 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -438,6 +438,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } function updateBGDelta(prevEntry, currentEntry) { @@ -1113,9 +1116,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); - if (init) { - $('.container').removeClass('loading'); - } }, DEBOUNCE_MS); function sgvToColor(sgv) { From a7cf86e4842452d27f6e28612defbd0066f70d86 Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 5 Nov 2014 16:32:19 -0800 Subject: [PATCH 061/661] pulled over @bewest's ticker --- lib/bootevent.js | 7 +++++++ lib/ticker.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 lib/ticker.js diff --git a/lib/bootevent.js b/lib/bootevent.js index dd46a6eb92f..12ed373d9ec 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -29,6 +29,13 @@ function boot (env) { store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); + + ctx.heartbeat = require('./ticker')(env, ctx); + ctx.heartbeat.uptime( ); + + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick) + }); next( ); }) diff --git a/lib/ticker.js b/lib/ticker.js new file mode 100644 index 00000000000..0be1293f297 --- /dev/null +++ b/lib/ticker.js @@ -0,0 +1,35 @@ + +var es = require('event-stream'); +var Stream = require('stream'); + +function heartbeat (env, ctx) { + var beats = 0; + var started = new Date( ); + var id; + var interval = env.HEARTBEAT || 20000; + function ictus ( ) { + var tick = { + now: new Date( ) + , type: 'heartbeat' + , sig: 'internal://' + ['heartbeat', beats ].join('/') + , beat: beats++ + , interval: interval + , started: started + }; + return tick; + } + function repeat ( ) { + stream.emit('tick', ictus( )); + } + function ender ( ) { + if (id) cancelInterval(id); + stream.emit('end'); + } + var stream = new Stream; + stream.readable = true; + stream.uptime = repeat; + id = setInterval(repeat, interval); + return stream; +} +module.exports = heartbeat; + From 84e655ad79cc2b1d8978ca0251be0cf2030e8168 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 01:02:28 -0700 Subject: [PATCH 062/661] factored data loading and AR2 forecasts out of websockets --- lib/bootevent.js | 5 +- lib/data.js | 141 +++++++++++++ lib/entries.js | 2 +- lib/plugins/ar2.js | 67 +++++++ lib/websocket.js | 478 +++++++++++++-------------------------------- server.js | 15 +- 6 files changed, 353 insertions(+), 355 deletions(-) create mode 100644 lib/data.js create mode 100644 lib/plugins/ar2.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 12ed373d9ec..f3d6a450265 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -31,11 +31,8 @@ function boot (env) { store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); ctx.heartbeat = require('./ticker')(env, ctx); - ctx.heartbeat.uptime( ); - ctx.heartbeat.on('tick', function(tick) { - console.info('tick', tick) - }); + ctx.data = require('./data')(env, ctx); next( ); }) diff --git a/lib/data.js b/lib/data.js new file mode 100644 index 00000000000..bbea35845d2 --- /dev/null +++ b/lib/data.js @@ -0,0 +1,141 @@ +'use strict'; + +var async = require('async'); + +function init (env, ctx) { + + ctx.data = { + sgvs: [] + , treatments: [] + , mbgs: [] + , cals: [] + , profile: [] + , devicestatus: {} + , lastUpdated: 0 + }; + + var ONE_DAY = 86400000 + , TWO_DAYS = 172800000 + ; + + var dir2Char = { + 'NONE': '⇼', + 'DoubleUp': '⇈', + 'SingleUp': '↑', + 'FortyFiveUp': '↗', + 'Flat': '→', + 'FortyFiveDown': '↘', + 'SingleDown': '↓', + 'DoubleDown': '⇊', + 'NOT COMPUTABLE': '-', + 'RATE OUT OF RANGE': '↮' + }; + + function directionToChar(direction) { + return dir2Char[direction] || '-'; + } + + ctx.data.update = function update (done) { + + //save some chars + var d = ctx.data; + + console.log('running data.update'); + var now = d.lastUpdated = Date.now(); + + var earliest_data = now - TWO_DAYS; + var treatment_earliest_data = now - (ONE_DAY * 8); + + function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); + } + + async.parallel({ + entries: function (callback) { + var q = { find: {"date": {"$gte": earliest_data}} }; + ctx.entries.list(q, function (err, results) { + if (!err && results) { + results.forEach(function (element) { + if (element) { + if (element.mbg) { + d.mbgs.push({ + y: element.mbg, x: element.date, d: element.dateString, device: element.device + }); + } else if (element.sgv) { + d.sgvs.push({ + y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + }); + } + } + }); + } + + //FIXME: sort in mongo + sort(d.mbgs); + sort(d.sgvs); + callback(); + }) + }, cal: function (callback) { + //FIXME: date $gte????? + var cq = { count: 1, find: {"type": "cal"} }; + ctx.entries.list(cq, function (err, results) { + if (!err && results) { + results.forEach(function (element) { + if (element) { + d.cals.push({ + x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope + }); + } + }); + } + callback(); + }); + }, treatments: function (callback) { + var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; + ctx.treatments.list(tq, function (err, results) { + d.treatments = results.map(function (treatment) { + var timestamp = new Date(treatment.timestamp || treatment.created_at); + treatment.x = timestamp.getTime(); + return treatment; + }); + + //FIXME: sort in mongo + d.treatments.sort(function(a, b) { + return a.x - b.x; + }); + + callback(); + }); + }, profile: function (callback) { + ctx.profile.list(function (err, results) { + if (!err && results) { + // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. + results.forEach(function (element, index, array) { + if (element) { + if (element.dia) { + d.profile[0] = element; + } + } + }); + } + callback(); + }); + }, devicestatus: function (callback) { + ctx.devicestatus.last(function (err, result) { + if (!err && result) { + d.devicestatus.uploaderBattery = result.uploaderBattery; + } + callback(); + }) + } + }, done); + + }; + + return ctx.data; + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/entries.js b/lib/entries.js index adfe7dea56b..fe57606e6e9 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -121,7 +121,7 @@ function storage(env, ctx) { }); ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processEntry) plugin.processEntry(doc, ctx, env) + if (plugin.processEntry) plugin.processEntry(doc, ctx, env); }); }); }); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js new file mode 100644 index 00000000000..a0cb2fad1ea --- /dev/null +++ b/lib/plugins/ar2.js @@ -0,0 +1,67 @@ +'use strict'; + +function init() { + + function ar2() { + return ar2; + } + + ar2.label = 'AR2'; + ar2.pluginType = 'forecast'; + + var ONE_HOUR = 3600000; + var ONE_MINUTE = 60000; + var FIVE_MINUTES = 300000; + + ar2.forecast = function forecast(env, ctx) { + + var actual = ctx.data.sgvs; + var actualLength = actual.length - 1; + var lastUpdated = ctx.data.lastUpdated; + + var result = { + predicted: [] + , avgLoss: 0 + }; + + if (actualLength > 1) { + // predict using AR model + var lastValidReadingTime = actual[actualLength].x; + var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; + var BG_REF = 140; + var BG_MIN = 36; + var BG_MAX = 400; + var y = Math.log(actual[actualLength].y / BG_REF); + if (elapsedMins < 5.1) { + y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; + } else { + y = [y, y]; + } + var n = Math.ceil(12 * (1 / 2 + (lastUpdated - lastValidReadingTime) / ONE_HOUR)); + var AR = [-0.723, 1.716]; + var dt = actual[actualLength].x; + for (var i = 0; i <= n; i++) { + y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; + dt = dt + FIVE_MINUTES; + result.predicted[i] = { + x: dt, + y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1])))) + }; + } + + // compute current loss + var size = Math.min(result.predicted.length - 1, 6); + for (var j = 0; j <= size; j++) { + result.avgLoss += 1 / size * Math.pow(log10(result.predicted[j].y / 120), 2); + } + } + + return result; + }; + + return ar2(); +} + +function log10(val) { return Math.log(val) / Math.LN10; } + +module.exports = init; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index c517fc85f9f..7404344816a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,61 +1,34 @@ -var async = require('async'); - -function websocket (env, ctx, server) { -"use strict"; -// CONSTANTS -var ONE_HOUR = 3600000, - ONE_MINUTE = 60000, - FIVE_MINUTES = 300000, - FORTY_MINUTES = 2400000, - ONE_DAY = 86400000, - TWO_DAYS = 172800000; - -var dir2Char = { - 'NONE': '⇼', - 'DoubleUp': '⇈', - 'SingleUp': '↑', - 'FortyFiveUp': '↗', - 'Flat': '→', - 'FortyFiveDown': '↘', - 'SingleDown': '↓', - 'DoubleDown': '⇊', - 'NOT COMPUTABLE': '-', - 'RATE OUT OF RANGE': '↮' -}; +'use strict'; + +var ar2 = require('./plugins/ar2')(); + +function init (server) { + + function websocket ( ) { + return websocket; + } + + var FORTY_MINUTES = 2400000; + + var lastUpdated = 0; + var patientData = []; var io; var watchers = 0; - var now = new Date().getTime(); - - var cgmData = [], - treatmentData = [], - mbgData = [], - calData = [], - profileData = [], - patientData = [], - devicestatusData = {}; function start ( ) { io = require('socket.io').listen(server, { - //these only effect the socket.io.js file that is sent to the client, but better than nothing - 'browser client minification': true, - 'browser client etag': true, - 'browser client gzip': false + //these only effect the socket.io.js file that is sent to the client, but better than nothing + 'browser client minification': true, + 'browser client etag': true, + 'browser client gzip': false }); } - // get data from database and setup to update every minute - function kickstart (fn) { - //TODO: test server to see how data is stored (timestamps, entry values, etc) - //TODO: check server settings to configure alerts, entry units, etc - console.log(now, new Date(now), fn.name); - fn( ); - return fn; - } function emitData ( ) { - console.log('running emitData', now, patientData && patientData.length); if (patientData.length > 0) { - io.sockets.emit("sgv", patientData); + console.log('running websocket.emitData', lastUpdated, patientData[0] && patientData[0].length); + io.sockets.emit('sgv', patientData); } } @@ -65,355 +38,167 @@ var dir2Char = { //TODO: make websockets support an option io.configure(function () { - io.set('transports', ['xhr-polling']); + io.set('transports', ['xhr-polling']); }); } function listeners ( ) { io.sockets.on('connection', function (socket) { - io.sockets.emit("sgv", patientData); - io.sockets.emit("clients", ++watchers); - socket.on('ack', function(alarmType, silenceTime) { - ackAlarm(alarmType, silenceTime); - if (alarmType == "urgent_alarm") { - //also clean normal alarm so we don't get a double alarm as BG comes back into range - ackAlarm("alarm", silenceTime); - } - io.sockets.emit("clear_alarm", true); - console.log("alarm cleared"); - }); - socket.on('disconnect', function () { - io.sockets.emit("clients", --watchers); - }); + io.sockets.emit('sgv', patientData); + io.sockets.emit('clients', ++watchers); + socket.on('ack', function(alarmType, silenceTime) { + ackAlarm(alarmType, silenceTime); + if (alarmType == 'urgent_alarm') { + //also clean normal alarm so we don't get a double alarm as BG comes back into range + ackAlarm('alarm', silenceTime); + } + io.sockets.emit('clear_alarm', true); + console.log('alarm cleared'); + }); + socket.on('disconnect', function () { + io.sockets.emit('clients', --watchers); + }); }); } -/////////////////////////////////////////////////// -// data handling functions -/////////////////////////////////////////////////// - -function directionToChar(direction) { - return dir2Char[direction] || '-'; -} + /////////////////////////////////////////////////// + // data handling functions + /////////////////////////////////////////////////// -var Alarm = function(_typeName, _threshold) { + var Alarm = function(_typeName, _threshold) { this.typeName = _typeName; this.silenceTime = FORTY_MINUTES; this.lastAckTime = 0; this.threshold = _threshold; -}; + }; // list of alarms with their thresholds -var alarms = { - "alarm" : new Alarm("Regular", 0.05), - "urgent_alarm": new Alarm("Urgent", 0.10) -}; + var alarms = { + 'alarm' : new Alarm('Regular', 0.05), + 'urgent_alarm': new Alarm('Urgent', 0.10) + }; -function ackAlarm(alarmType, silenceTime) { + function ackAlarm(alarmType, silenceTime) { var alarm = alarms[alarmType]; if (!alarm) { - console.warn('Got an ack for an unknown alarm time'); - return; + console.warn('Got an ack for an unknown alarm time'); + return; } alarm.lastAckTime = new Date().getTime(); alarm.silenceTime = silenceTime ? silenceTime : FORTY_MINUTES; delete alarm.lastEmitTime; -} + } -//should only be used when auto acking the alarms after going back in range or when an error corrects -//setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes -//since this wasn't ack'd by a user action -function autoAckAlarms() { + //should only be used when auto acking the alarms after going back in range or when an error corrects + //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //since this wasn't ack'd by a user action + function autoAckAlarms() { var sendClear = false; for (var alarmType in alarms) { - if (alarms.hasOwnProperty(alarmType)) { - var alarm = alarms[alarmType]; - if (alarm.lastEmitTime) { - console.info("auto acking " + alarmType); - ackAlarm(alarmType, 1); - sendClear = true; - } + if (alarms.hasOwnProperty(alarmType)) { + var alarm = alarms[alarmType]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + alarmType); + ackAlarm(alarmType, 1); + sendClear = true; } + } } if (sendClear) { - io.sockets.emit("clear_alarm", true); - console.info("emitted clear_alarm to all clients"); + io.sockets.emit('clear_alarm', true); + console.info('emitted clear_alarm to all clients'); } -} + } -function emitAlarm(alarmType) { + function emitAlarm (alarmType) { var alarm = alarms[alarmType]; - if (now > alarm.lastAckTime + alarm.silenceTime) { - io.sockets.emit(alarmType); - alarm.lastEmitTime = now; - console.info("emitted " + alarmType + " to all clients"); + if (lastUpdated > alarm.lastAckTime + alarm.silenceTime) { + io.sockets.emit(alarmType); + alarm.lastEmitTime = lastUpdated; + console.info('emitted ' + alarmType + ' to all clients'); } else { - console.log(alarm.typeName + " alarm is silenced for " + Math.floor((alarm.silenceTime - (now - alarm.lastAckTime)) / 60000) + " minutes more"); + console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); } -} - -function update() { - - console.log('running update'); - now = Date.now(); - - cgmData = []; - treatmentData = []; - mbgData = []; - profileData = []; - devicestatusData = {}; - var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY*8); - - async.parallel({ - entries: function(callback) { - var q = { find: {"date": {"$gte": earliest_data}} }; - ctx.entries.list(q, function (err, results) { - if (!err && results) { - results.forEach(function (element) { - if (element) { - if (element.mbg) { - mbgData.push({ - y: element.mbg - , x: element.date - , d: element.dateString - , device: element.device - }); - } else if (element.sgv) { - cgmData.push({ - y: element.sgv - , x: element.date - , d: element.dateString - , device: element.device - , direction: directionToChar(element.direction) - , filtered: element.filtered - , unfiltered: element.unfiltered - , noise: element.noise - , rssi: element.rssi - }); - } - } - }); - } - callback(); - }) - } - , cal: function(callback) { - var cq = { count: 1, find: {"type": "cal"} }; - ctx.entries.list(cq, function (err, results) { - if (!err && results) { - results.forEach(function (element) { - if (element) { - calData.push({ - x: element.date - , d: element.dateString - , scale: element.scale - , intercept: element.intercept - , slope: element.slope - }); - } - }); - } - callback(); - }); - } - , treatments: function(callback) { - var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; - ctx.treatments.list(tq, function (err, results) { - treatmentData = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); - return treatment; - }); - callback(); - }); - } - , profile: function(callback) { - ctx.profile.list(function (err, results) { - if (!err && results) { - // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. - results.forEach(function (element, index, array) { - if (element) { - if (element.dia) { - profileData[0] = element; - } - } - }); - } - callback(); - }); - } - , devicestatus: function(callback) { - ctx.devicestatus.last(function (err, result) { - if (!err && result) { - devicestatusData = { - uploaderBattery: result.uploaderBattery - }; - } - callback(); - }) - } - }, loadData); - - return update; -} - -function loadData() { - - console.log('running loadData'); - - var actual = [], - actualCurrent, - treatment = [], - mbg = [], - cal = [], - errorCode; - - if (cgmData) { - actual = cgmData.slice(); - actual.sort(function(a, b) { - return a.x - b.x; - }); - - actualCurrent = actual.length > 0 ? actual[actual.length - 1].y : null; - } - - if (treatmentData) { - treatment = treatmentData.slice(); - treatment.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (mbgData) { - mbg = mbgData.slice(); - mbg.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (calData) { - cal = calData.slice(calData.length-200, calData.length); - cal.sort(function(a, b) { - return a.x - b.x; - }); - } - - if (profileData) { - var profile = profileData; - } - - if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent; - - var actualLength = actual.length - 1; - - if (actualLength > 1) { - // predict using AR model - var predicted = []; - var lastValidReadingTime = actual[actualLength].x; - var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; - var BG_REF = 140; - var BG_MIN = 36; - var BG_MAX = 400; - var y = Math.log(actual[actualLength].y / BG_REF); - if (elapsedMins < 5.1) { - y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; - } else { - y = [y, y]; - } - var n = Math.ceil(12 * (1 / 2 + (now - lastValidReadingTime) / ONE_HOUR)); - var AR = [-0.723, 1.716]; - var dt = actual[actualLength].x; - for (var i = 0; i <= n; i++) { - y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; - dt = dt + FIVE_MINUTES; - predicted[i] = { - x: dt, - y: Math.max(BG_MIN, Math.min(BG_MAX, Math.round(BG_REF * Math.exp(y[1])))) - }; - } - - //TODO: need to consider when data being sent has less than the 2 day minimum - - // consolidate and send the data to the client - var shouldEmit = is_different(actual, predicted, mbg, treatment, cal, devicestatusData); - patientData = [actual, predicted, mbg, treatment, profile, cal, devicestatusData]; - console.log('patientData', patientData.length); - if (shouldEmit) { - emitData( ); - } + } - var emitAlarmType = null; - - if (env.alarm_types.indexOf("simple") > -1) { - var lastBG = actual[actualLength].y; - - if (lastBG > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastBG + " > " + env.thresholds.bg_high + " will emmit " + emitAlarmType); - } else if (lastBG > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastBG + " > " + env.thresholds.bg_target_top + " will emmit " + emitAlarmType); - } else if (lastBG < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastBG + " < " + env.thresholds.bg_low + " will emmit " + emitAlarmType); - } else if (lastBG < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastBG + " < " + env.thresholds.bg_target_bottom + " will emmit " + emitAlarmType); - } + websocket.processData = function processData (env, ctx) { + + var d = ctx.data; + lastUpdated = d.lastUpdated; + + console.log('running websocket.loadData'); + + var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + + if (lastSGV) { + var forecast = ar2.forecast(env, ctx); + + // consolidate and send the data to the client + if (is_different(d)) { + patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + emitData(); + } + + var emitAlarmType = null; + + if (env.alarm_types.indexOf('simple') > -1) { + if (lastSGV > env.thresholds.bg_high) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); + } else if (lastSGV > env.thresholds.bg_target_top) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_low) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_target_bottom) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); } - - if (!emitAlarmType && env.alarm_types.indexOf("predict") > -1) { - // compute current loss - var avgLoss = 0; - var size = Math.min(predicted.length - 1, 6); - for (var j = 0; j <= size; j++) { - avgLoss += 1 / size * Math.pow(log10(predicted[j].y / 120), 2); - } - - if (avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info(avgLoss + " < " + alarms['urgent_alarm'].threshold + " will emmit " + emitAlarmType); - } else if (avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info(avgLoss + " < " + alarms['alarm'].threshold + " will emmit " + emitAlarmType); - } + } + + if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { + if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { + emitAlarmType = 'urgent_alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); + } else if (forecast.avgLoss > alarms['alarm'].threshold) { + emitAlarmType = 'alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); } + } - if (errorCode) { - emitAlarmType = 'urgent_alarm'; - } + if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { + emitAlarmType = 'urgent_alarm'; + } - if (emitAlarmType) { - emitAlarm(emitAlarmType); - } else { - autoAckAlarms(); - } + if (emitAlarmType) { + emitAlarm(emitAlarmType); + } else { + autoAckAlarms(); + } } -} + }; - function is_different (actual, predicted, mbg, treatment, cal) { + function is_different (data) { if (patientData && patientData.length < 3) { return true; } var old = { - actual: patientData[0].slice(-1).pop( ) - , predicted: patientData[1].slice(-1).pop( ) + sgv: patientData[0].slice(-1).pop( ) , mbg: patientData[2].slice(-1).pop( ) , treatment: patientData[3].slice(-1).pop( ) - , cal: patientData[4].slice(-1).pop( ) + , cal: patientData[5].slice(-1).pop( ) }; var last = { - actual: actual.slice(-1).pop( ) - , predicted: predicted.slice(-1).pop( ) - , mbg: mbg.slice(-1).pop( ) - , treatment: treatment.slice(-1).pop( ) - , cal: cal.slice(-1).pop( ) + sgv: data.sgvs.slice(-1).pop( ) + , mbg: data.mbgs.slice(-1).pop( ) + , treatment: data.treatments.slice(-1).pop( ) + , cal: data.cals.slice(-1).pop( ) }; // textual diff of objects if (JSON.stringify(old) == JSON.stringify(last)) { - console.info("data isn't different, will not send to clients"); + console.info('data is NOT different, will not send to clients'); return false; } return true; @@ -422,9 +207,8 @@ function loadData() { start( ); configure( ); listeners( ); - setInterval(kickstart(update), ONE_MINUTE); - return io; + return websocket(); } /////////////////////////////////////////////////// @@ -433,4 +217,4 @@ function loadData() { function log10(val) { return Math.log(val) / Math.LN10; } -module.exports = websocket; +module.exports = init; diff --git a/server.js b/server.js index 69ff7da48c2..134493f6124 100644 --- a/server.js +++ b/server.js @@ -58,9 +58,18 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// - var websocket = require('./lib/websocket'); - var io = websocket(env, ctx, server); - }) + var websocket = require('./lib/websocket')(server); + + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick.now); + ctx.data.update(function dataUpdated () { + websocket.processData(env, ctx); + }); + }); + + ctx.heartbeat.uptime( ); + +}) ; /////////////////////////////////////////////////// From d93b77a51534bdbabefd7ba57b515fa7e3d59a74 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 01:31:58 -0700 Subject: [PATCH 063/661] some clean up --- lib/plugins/ar2.js | 19 +++++++++---------- lib/websocket.js | 6 ------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index a0cb2fad1ea..f35b0074b2c 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -15,31 +15,30 @@ function init() { ar2.forecast = function forecast(env, ctx) { - var actual = ctx.data.sgvs; - var actualLength = actual.length - 1; - var lastUpdated = ctx.data.lastUpdated; + var sgvs = ctx.data.sgvs; + var lastIndex = sgvs.length - 1; var result = { predicted: [] , avgLoss: 0 }; - if (actualLength > 1) { + if (lastIndex > 1) { // predict using AR model - var lastValidReadingTime = actual[actualLength].x; - var elapsedMins = (actual[actualLength].x - actual[actualLength - 1].x) / ONE_MINUTE; + var lastValidReadingTime = sgvs[lastIndex].x; + var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; var BG_REF = 140; var BG_MIN = 36; var BG_MAX = 400; - var y = Math.log(actual[actualLength].y / BG_REF); + var y = Math.log(sgvs[lastIndex].y / BG_REF); if (elapsedMins < 5.1) { - y = [Math.log(actual[actualLength - 1].y / BG_REF), y]; + y = [Math.log(sgvs[lastIndex - 1].y / BG_REF), y]; } else { y = [y, y]; } - var n = Math.ceil(12 * (1 / 2 + (lastUpdated - lastValidReadingTime) / ONE_HOUR)); + var n = Math.ceil(12 * (1 / 2 + (Date.now() - lastValidReadingTime) / ONE_HOUR)); var AR = [-0.723, 1.716]; - var dt = actual[actualLength].x; + var dt = sgvs[lastIndex].x; for (var i = 0; i <= n; i++) { y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; dt = dt + FIVE_MINUTES; diff --git a/lib/websocket.js b/lib/websocket.js index 7404344816a..2853633b473 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -211,10 +211,4 @@ function init (server) { return websocket(); } -/////////////////////////////////////////////////// -// helper functions -/////////////////////////////////////////////////// - -function log10(val) { return Math.log(val) / Math.LN10; } - module.exports = init; From 8f9b23eef8363a7de7c5acdd7b76f1939c6a5786 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 18:59:29 +0300 Subject: [PATCH 064/661] Only send recent treatments and svgs with update packets --- lib/data.js | 37 ++++++++- lib/websocket.js | 54 +++++++++---- static/js/client.js | 187 ++++++++++++++++++++++++++++++-------------- 3 files changed, 200 insertions(+), 78 deletions(-) diff --git a/lib/data.js b/lib/data.js index bbea35845d2..b1c91f3026e 100644 --- a/lib/data.js +++ b/lib/data.js @@ -6,7 +6,9 @@ function init (env, ctx) { ctx.data = { sgvs: [] + , recentsgvs: [] , treatments: [] + , recentTreatments: [] , mbgs: [] , cals: [] , profile: [] @@ -16,7 +18,9 @@ function init (env, ctx) { var ONE_DAY = 86400000 , TWO_DAYS = 172800000 - ; + , FORTY_MINUTES = 2400000 + , UPDATE_DELTA = FORTY_MINUTES; + var dir2Char = { 'NONE': '⇼', @@ -54,6 +58,7 @@ function init (env, ctx) { async.parallel({ entries: function (callback) { + var now = new Date(); var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { @@ -64,9 +69,20 @@ function init (env, ctx) { y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - d.sgvs.push({ - y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi - }); + var sgvElement = { + y: element.sgv + , x: element.date + , d: element.dateString + , device: element.device + , direction: directionToChar(element.direction) + , filtered: element.filtered + , unfiltered: element.unfiltered + , noise: element.noise + , rssi: element.rssi + }; + d.sgvs.push(sgvElement); + var sgvDate = new Date(element.date); + if (now-sgvDate < UPDATE_DELTA) d.recentsgvs.push(sgvElement); } } }); @@ -106,6 +122,19 @@ function init (env, ctx) { return a.x - b.x; }); + var now = new Date(); + var length = d.treatments.length; + for (var i = 0; i < length; i++) { + var data = d.treatments[i]; + var date = new Date(data.d); + if (now - date <= UPDATE_DELTA) { + d.recentTreatments.push(data); + }; + + d.recentTreatments.sort(function(a, b) { + return a.x - b.x; + }); + } callback(); }); }, profile: function (callback) { diff --git a/lib/websocket.js b/lib/websocket.js index 2853633b473..400fac5f1d2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -9,9 +9,10 @@ function init (server) { } var FORTY_MINUTES = 2400000; - + var lastUpdated = 0; - var patientData = []; + var patientData = {}; + var patientDataUpdate = {}; var io; var watchers = 0; @@ -26,9 +27,9 @@ function init (server) { } function emitData ( ) { - if (patientData.length > 0) { - console.log('running websocket.emitData', lastUpdated, patientData[0] && patientData[0].length); - io.sockets.emit('sgv', patientData); + if (patientData.cal) { + console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); + io.sockets.emit("sgv", patientDataUpdate); } } @@ -44,7 +45,8 @@ function init (server) { function listeners ( ) { io.sockets.on('connection', function (socket) { - io.sockets.emit('sgv', patientData); + // send all data upon new connection + io.sockets.socket(socket.id).emit('sgv',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ackAlarm(alarmType, silenceTime); @@ -135,7 +137,30 @@ function init (server) { // consolidate and send the data to the client if (is_different(d)) { - patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + + patientDataUpdate = { + 'sgvdataupdate': d.recentsgvs + , 'predicted': forecast.predicted + , 'mbg': d.mbgs + , 'treatmentdataupdate': d.recentTreatments + , 'cal': d.cals + , 'devicestatusData': d.devicestatus + }; + + patientData = { + 'actual': d.sgvs + , 'predicted': forecast.predicted + , 'mbg': d.mbgs + , 'treatment': d.treatments + , 'profile': d.profile + , 'cal': d.cals + , 'devicestatusData': d.devicestatus + }; + + console.log('patientData total sgv records', patientData.actual.length, ' (', JSON.stringify(patientData).length,'bytes)'); + console.log('patientData update sgv records', patientDataUpdate.sgvdataupdate.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); + +// patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; emitData(); } @@ -179,15 +204,16 @@ function init (server) { } }; + function is_different (data) { - if (patientData && patientData.length < 3) { - return true; - } + + if (!patientData.actual) return true; + var old = { - sgv: patientData[0].slice(-1).pop( ) - , mbg: patientData[2].slice(-1).pop( ) - , treatment: patientData[3].slice(-1).pop( ) - , cal: patientData[5].slice(-1).pop( ) + sgv: patientData.actual.slice(-1).pop( ) + , mbg: patientData.mbg.slice(-1).pop( ) + , treatment: patientData.treatment.slice(-1).pop( ) + , cal: patientData.cal.slice(-1).pop( ) }; var last = { sgv: data.sgvs.slice(-1).pop( ) diff --git a/static/js/client.js b/static/js/client.js index b33cc6abd12..eb97ad8d044 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -30,6 +30,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var socket , isInitialData = false + , SGVdata = [] , latestSGV , latestUpdateTime , prevSGV @@ -1656,67 +1657,132 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket = io.connect(); - socket.on('sgv', function (d) { - if (d.length > 1) { - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - if (d[0].length) { - latestUpdateTime = Date.now(); - latestSGV = d[0][d[0].length - 1]; - prevSGV = d[0][d[0].length - 2]; - } + function recordAlreadyStored(array,record) { + var l = array.length -1; + for (var i = 0; i <= l; i++) { + var oldRecord = array[i]; + if (record.x == oldRecord.x) return true; + } + } + + socket.on('sgv', function receivedSGV(d) { + + if (!d) return; + + var dataUpdate = d.actual ? false : true; + + if (!dataUpdate) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.actual; + } else { + var newRecords = []; + + var l = d.sgvdataupdate.length; + for (var i = 0; i < l; i++) { + var record = d.sgvdataupdate[i]; + if (!recordAlreadyStored(SGVdata,record)) { + newRecords.push(record); + } + } + console.log('SGV data updated with', newRecords.length, 'new records'); + SGVdata = SGVdata.concat(newRecords); + + if (newRecords.length > 0) + { + console.log(newRecords); + } + + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); + + } + + console.log('Total CGM data size', SGVdata.length); + + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + // if (if !dataUpdate && d.actual.length) { + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + // } + + if (d.profile) profile = d.profile[0]; + + cal = d.cal[d.cal.length-1]; + devicestatusData = d.devicestatusData; + + var temp1 = [ ]; + if (cal && isRawBGEnabled()) { + temp1 = SGVdata.map(function (entry) { + var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; + if (rawBg > 0) { + return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; + } else { + return null; + } + }).filter(function(entry) { return entry != null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + + //Add MBG's also, pretend they are SGV's + data = data.concat(d.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + + data.forEach(function (d) { + if (d.y < 39) + d.color = 'transparent'; + }); + + // Update treatment data with new if delta + + if (!dataUpdate) { + treatments = d.treatment; + } else { + var newRecords = []; + var l = d.treatmentdataupdate.length; + for (var i = 0; i < l; i++) { + var record = d.treatmentdataupdate[i]; + if (!recordAlreadyStored(treatments,record)) { + newRecods.push(record); + } + } + console.log('treatment data updated with', newRecords.length, 'new records'); + treatments = treatments.concat(newRecords); + treatments.sort(function(a, b) { + return a.x - b.x; + }); + + } + + console.log('Total treatment data size', treatments.length); + + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } - profile = d[4][0]; - cal = d[5][d[5].length-1]; - devicestatusData = d[6]; - - var temp1 = [ ]; - if (cal && isRawBGEnabled()) { - temp1 = d[0].map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry != null; }); - } - var temp2 = d[0].map(function (obj) { - return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - data = data.concat(d[1].map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - - //Add MBG's also, pretend they are SGV's - data = data.concat(d[2].map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; - }); - - treatments = d[3]; - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - //cache the displayBG for each treatment in DISPLAY_UNITS - d.displayBG = displayTreatmentBG(d); - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } - } }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1726,6 +1792,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.log('Client connected to server.') }); + //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a HIGH we can only check if it's >= the bottom of the target function isAlarmForHigh() { From e13290850ef19ea17484a667c88c956828be40a8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 09:25:37 -0700 Subject: [PATCH 065/661] check for err and results when getting treatments --- lib/data.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/data.js b/lib/data.js index bbea35845d2..9b4e0778435 100644 --- a/lib/data.js +++ b/lib/data.js @@ -95,17 +95,18 @@ function init (env, ctx) { }, treatments: function (callback) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { - d.treatments = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); - return treatment; - }); - - //FIXME: sort in mongo - d.treatments.sort(function(a, b) { - return a.x - b.x; - }); + if (!err && results) { + d.treatments = results.map(function (treatment) { + var timestamp = new Date(treatment.timestamp || treatment.created_at); + treatment.x = timestamp.getTime(); + return treatment; + }); + //FIXME: sort in mongo + d.treatments.sort(function(a, b) { + return a.x - b.x; + }); + } callback(); }); }, profile: function (callback) { From 508090ffeb039bd18f0cbc087c82d664dcd5239c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 09:32:59 -0700 Subject: [PATCH 066/661] some data loader clean up --- lib/data.js | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/data.js b/lib/data.js index 9b4e0778435..ad3bdbb271b 100644 --- a/lib/data.js +++ b/lib/data.js @@ -4,7 +4,7 @@ var async = require('async'); function init (env, ctx) { - ctx.data = { + var data = { sgvs: [] , treatments: [] , mbgs: [] @@ -35,16 +35,13 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } - ctx.data.update = function update (done) { - - //save some chars - var d = ctx.data; + data.update = function update (done) { console.log('running data.update'); - var now = d.lastUpdated = Date.now(); + data.lastUpdated = Date.now(); - var earliest_data = now - TWO_DAYS; - var treatment_earliest_data = now - (ONE_DAY * 8); + var earliest_data = data.lastUpdated - TWO_DAYS; + var treatment_earliest_data = data.lastUpdated - (ONE_DAY * 8); function sort (values) { values.sort(function sorter (a, b) { @@ -60,11 +57,11 @@ function init (env, ctx) { results.forEach(function (element) { if (element) { if (element.mbg) { - d.mbgs.push({ + data.mbgs.push({ y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - d.sgvs.push({ + data.sgvs.push({ y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } @@ -73,8 +70,8 @@ function init (env, ctx) { } //FIXME: sort in mongo - sort(d.mbgs); - sort(d.sgvs); + sort(data.mbgs); + sort(data.sgvs); callback(); }) }, cal: function (callback) { @@ -84,7 +81,7 @@ function init (env, ctx) { if (!err && results) { results.forEach(function (element) { if (element) { - d.cals.push({ + data.cals.push({ x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope }); } @@ -96,14 +93,14 @@ function init (env, ctx) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - d.treatments = results.map(function (treatment) { + data.treatments = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); return treatment; }); //FIXME: sort in mongo - d.treatments.sort(function(a, b) { + data.treatments.sort(function(a, b) { return a.x - b.x; }); } @@ -116,7 +113,7 @@ function init (env, ctx) { results.forEach(function (element, index, array) { if (element) { if (element.dia) { - d.profile[0] = element; + data.profile[0] = element; } } }); @@ -126,7 +123,7 @@ function init (env, ctx) { }, devicestatus: function (callback) { ctx.devicestatus.last(function (err, result) { if (!err && result) { - d.devicestatus.uploaderBattery = result.uploaderBattery; + data.devicestatus.uploaderBattery = result.uploaderBattery; } callback(); }) @@ -135,7 +132,7 @@ function init (env, ctx) { }; - return ctx.data; + return data; } From 690f051e6ecb612466087c9c007d72347c35a86a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 21:31:33 +0300 Subject: [PATCH 067/661] calculate actual delta --- lib/data.js | 11 ++--- lib/websocket.js | 113 ++++++++++++++++++++++++++++++++------------ static/js/client.js | 109 ++++++++++++++++++++---------------------- 3 files changed, 135 insertions(+), 98 deletions(-) diff --git a/lib/data.js b/lib/data.js index b1c91f3026e..f1bf48d09d2 100644 --- a/lib/data.js +++ b/lib/data.js @@ -91,6 +91,7 @@ function init (env, ctx) { //FIXME: sort in mongo sort(d.mbgs); sort(d.sgvs); + sort(d.recentsgvs); callback(); }) }, cal: function (callback) { @@ -117,11 +118,6 @@ function init (env, ctx) { return treatment; }); - //FIXME: sort in mongo - d.treatments.sort(function(a, b) { - return a.x - b.x; - }); - var now = new Date(); var length = d.treatments.length; for (var i = 0; i < length; i++) { @@ -131,9 +127,8 @@ function init (env, ctx) { d.recentTreatments.push(data); }; - d.recentTreatments.sort(function(a, b) { - return a.x - b.x; - }); + sort(d.treatments); + sort(d.recentTreatments); } callback(); }); diff --git a/lib/websocket.js b/lib/websocket.js index 400fac5f1d2..cf7971d72e5 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,6 +2,16 @@ var ar2 = require('./plugins/ar2')(); +Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); +}; + +function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); +} + function init (server) { function websocket ( ) { @@ -27,9 +37,9 @@ function init (server) { } function emitData ( ) { - if (patientData.cal) { + if (patientData.cals) { console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.sockets.emit("sgv", patientDataUpdate); + io.sockets.emit('dataUpdate', patientDataUpdate); } } @@ -46,7 +56,7 @@ function init (server) { function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - io.sockets.socket(socket.id).emit('sgv',patientData); + io.sockets.socket(socket.id).emit('dataUpdate',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ackAlarm(alarmType, silenceTime); @@ -135,34 +145,20 @@ function init (server) { if (lastSGV) { var forecast = ar2.forecast(env, ctx); - // consolidate and send the data to the client - if (is_different(d)) { - - patientDataUpdate = { - 'sgvdataupdate': d.recentsgvs - , 'predicted': forecast.predicted - , 'mbg': d.mbgs - , 'treatmentdataupdate': d.recentTreatments - , 'cal': d.cals - , 'devicestatusData': d.devicestatus - }; - - patientData = { - 'actual': d.sgvs - , 'predicted': forecast.predicted - , 'mbg': d.mbgs - , 'treatment': d.treatments - , 'profile': d.profile - , 'cal': d.cals - , 'devicestatusData': d.devicestatus - }; - - console.log('patientData total sgv records', patientData.actual.length, ' (', JSON.stringify(patientData).length,'bytes)'); - console.log('patientData update sgv records', patientDataUpdate.sgvdataupdate.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); - -// patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; - emitData(); - } + if (!patientData.sgvs) { + console.log('First data load, setting patientData'); + patientData = d; + } else { + var delta = calculateDelta(d); + if (delta.delta) { + patientDataUpdate = delta; + console.log('patientData total sgv records', patientData.sgvs.length, ' (', JSON.stringify(patientData).length,'bytes)'); + if (delta.sgvs) console.log('patientData update sgv records', patientDataUpdate.sgvs.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); + emitData(); + } else { + console.log('Delta calculation did not find new data'); + } + } var emitAlarmType = null; @@ -204,7 +200,62 @@ function init (server) { } }; + function calculateDelta(d) { + + var delta = {'delta': true}; + + if (patientData.sgvs) { + + var sgvDelta = patientData.sgvs.diff(d.sgvs); + + if (sgvDelta.length > 0) { + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + }; + + var treatmentDelta = patientData.treatments.diff(d.treatments); + + if (treatmentDelta.length > 0) { + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + }; + + var mbgsDelta = patientData.mbgs.diff(d.mbgs); + + if (mbgsDelta.length > 0) { + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + }; + + var calsDelta = patientData.cals.diff(d.cals); + + if (calsDelta.length > 0) { + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + }; + + if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { + changesFound = true; + delta.devicestatus = d.devicestatus; + }; + if (JSON.stringify(patientData.profile) != JSON.stringify(d.profile)) { + changesFound = true; + delta.profile = d.profile; + }; + + } else { + return delta; + } + + return d; + + } + function is_different (data) { if (!patientData.actual) return true; diff --git a/static/js/client.js b/static/js/client.js index eb97ad8d044..6e528bd86b5 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,6 +1,10 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); +}; + (function () { 'use strict'; @@ -1665,53 +1669,41 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - socket.on('sgv', function receivedSGV(d) { + socket.on('dataUpdate', function receivedSGV(d) { if (!d) return; - var dataUpdate = d.actual ? false : true; + // SGV - if (!dataUpdate) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.actual; - } else { - var newRecords = []; - - var l = d.sgvdataupdate.length; - for (var i = 0; i < l; i++) { - var record = d.sgvdataupdate[i]; - if (!recordAlreadyStored(SGVdata,record)) { - newRecords.push(record); - } - } - console.log('SGV data updated with', newRecords.length, 'new records'); - SGVdata = SGVdata.concat(newRecords); - - if (newRecords.length > 0) - { - console.log(newRecords); - } - - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - + if (d.sgvs) { + + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.sgvs; + } else { + var diff = SGVdata.diff(d.sgvs); + console.log('SGV data updated with', diff.length, 'new records'); + SGVdata = SGVdata.concat(diff); + } + + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; } - console.log('Total CGM data size', SGVdata.length); + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - // if (if !dataUpdate && d.actual.length) { - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; - // } + console.log('Total SGV data size', SGVdata.length); + + // profile, calibration and device status if (d.profile) profile = d.profile[0]; - - cal = d.cal[d.cal.length-1]; - devicestatusData = d.devicestatusData; + if (d.cal) cal = d.cal[d.cal.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatusData; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1735,11 +1727,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - + + if (d.predicted) { + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + } + //Add MBG's also, pretend they are SGV's - data = data.concat(d.mbg.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - + if (d.mbgs) { + data = data.concat(d.mbgs.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + } + data.forEach(function (d) { if (d.y < 39) d.color = 'transparent'; @@ -1747,25 +1744,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Update treatment data with new if delta - if (!dataUpdate) { - treatments = d.treatment; - } else { - var newRecords = []; - var l = d.treatmentdataupdate.length; - for (var i = 0; i < l; i++) { - var record = d.treatmentdataupdate[i]; - if (!recordAlreadyStored(treatments,record)) { - newRecods.push(record); - } + if (d.treatments) { + if (!d.delta) { + treatments = d.treatments; + } else { + var newTreatments = treatments.diff(d.treatments); + console.log('treatment data updated with', newTreatments.length, 'new records'); + treatments = treatments.concat(newRecords); + treatments.sort(function(a, b) { + return a.x - b.x; + }); } - console.log('treatment data updated with', newRecords.length, 'new records'); - treatments = treatments.concat(newRecords); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - + console.log('Total treatment data size', treatments.length); treatments.forEach(function (d) { From 3f68a04dc822459ebce04c61948fe5689f3a840a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 11:56:27 -0700 Subject: [PATCH 068/661] use slice to make a shallow copy of data added to patientData to make is_different work; rename profile to profiles --- lib/data.js | 4 ++-- lib/websocket.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data.js b/lib/data.js index ad3bdbb271b..8fe10c6552c 100644 --- a/lib/data.js +++ b/lib/data.js @@ -9,7 +9,7 @@ function init (env, ctx) { , treatments: [] , mbgs: [] , cals: [] - , profile: [] + , profiles: [] , devicestatus: {} , lastUpdated: 0 }; @@ -113,7 +113,7 @@ function init (env, ctx) { results.forEach(function (element, index, array) { if (element) { if (element.dia) { - data.profile[0] = element; + data.profiles[0] = element; } } }); diff --git a/lib/websocket.js b/lib/websocket.js index 2853633b473..f3dee45e191 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -135,7 +135,7 @@ function init (server) { // consolidate and send the data to the client if (is_different(d)) { - patientData = [d.sgvs, forecast.predicted, d.mbgs, d.treatments, d.profile, d.cals, d.devicestatus]; + patientData = [d.sgvs.slice(), forecast.predicted, d.mbgs.slice(), d.treatments.slice(), d.profiles.slice(), d.cals.slice(), d.devicestatus]; emitData(); } From f35745f64fdc462230ab912a33cc7a3c5d9e4939 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 23:20:05 +0300 Subject: [PATCH 069/661] Send deltas from server to client --- lib/data.js | 28 +++++++----------- lib/websocket.js | 71 ++++++++++++++++++++------------------------- static/js/client.js | 16 +++++----- 3 files changed, 50 insertions(+), 65 deletions(-) diff --git a/lib/data.js b/lib/data.js index f1bf48d09d2..8948dccf5f4 100644 --- a/lib/data.js +++ b/lib/data.js @@ -2,13 +2,18 @@ var async = require('async'); +function uniq(a) { + var seen = {}; + return a.filter(function(item) { + return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); + }); +} + function init (env, ctx) { ctx.data = { sgvs: [] - , recentsgvs: [] , treatments: [] - , recentTreatments: [] , mbgs: [] , cals: [] , profile: [] @@ -81,17 +86,16 @@ function init (env, ctx) { , rssi: element.rssi }; d.sgvs.push(sgvElement); - var sgvDate = new Date(element.date); - if (now-sgvDate < UPDATE_DELTA) d.recentsgvs.push(sgvElement); } } }); } - //FIXME: sort in mongo + uniq(d.mbgs); sort(d.mbgs); + uniq(d.sgvs); sort(d.sgvs); - sort(d.recentsgvs); + callback(); }) }, cal: function (callback) { @@ -118,18 +122,6 @@ function init (env, ctx) { return treatment; }); - var now = new Date(); - var length = d.treatments.length; - for (var i = 0; i < length; i++) { - var data = d.treatments[i]; - var date = new Date(data.d); - if (now - date <= UPDATE_DELTA) { - d.recentTreatments.push(data); - }; - - sort(d.treatments); - sort(d.recentTreatments); - } callback(); }); }, profile: function (callback) { diff --git a/lib/websocket.js b/lib/websocket.js index cf7971d72e5..e201dc3e7fe 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,9 +2,23 @@ var ar2 = require('./plugins/ar2')(); -Array.prototype.diff = function(a) { - return this.filter(function(i) {return a.indexOf(i) < 0;}); -}; +Array.prototype.nsDiff = function(a) { + var seen = {}; + var l = this.length; + for (var i = 0; i < l; i++) { seen[this[i].x] = true }; + var result = []; + l = a.length; + for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(a[i].x)) result.push(a[i])}; + return result; +} + +function uniq(a) { + var seen = {}; + return a.filter(function(item) { + return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); + }); +} + function sort (values) { values.sort(function sorter (a, b) { @@ -148,10 +162,12 @@ function init (server) { if (!patientData.sgvs) { console.log('First data load, setting patientData'); patientData = d; + patientData.predicted = forecast.predicted; } else { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; + patientDataUpdate.predicted = forecast.predicted; console.log('patientData total sgv records', patientData.sgvs.length, ' (', JSON.stringify(patientData).length,'bytes)'); if (delta.sgvs) console.log('patientData update sgv records', patientDataUpdate.sgvs.length, ' (', JSON.stringify(patientDataUpdate).length,'bytes)'); emitData(); @@ -203,10 +219,12 @@ function init (server) { function calculateDelta(d) { var delta = {'delta': true}; - - if (patientData.sgvs) { + var changesFound = false; - var sgvDelta = patientData.sgvs.diff(d.sgvs); + // if there's no updates done so far, just return the full set + if (!patientData.sgvs) return d; + + var sgvDelta = patientData.sgvs.nsDiff(d.sgvs); if (sgvDelta.length > 0) { changesFound = true; @@ -214,7 +232,7 @@ function init (server) { delta.sgvs = sgvDelta; }; - var treatmentDelta = patientData.treatments.diff(d.treatments); + var treatmentDelta = patientData.treatments.nsDiff(d.treatments); if (treatmentDelta.length > 0) { changesFound = true; @@ -222,7 +240,7 @@ function init (server) { delta.treatments = treatmentDelta; }; - var mbgsDelta = patientData.mbgs.diff(d.mbgs); + var mbgsDelta = patientData.mbgs.nsDiff(d.mbgs); if (mbgsDelta.length > 0) { changesFound = true; @@ -230,7 +248,7 @@ function init (server) { delta.mbgs = mbgsDelta; }; - var calsDelta = patientData.cals.diff(d.cals); + var calsDelta = patientData.cals.nsDiff(d.cals); if (calsDelta.length > 0) { changesFound = true; @@ -247,40 +265,13 @@ function init (server) { changesFound = true; delta.profile = d.profile; }; - - } else { - return delta; - } - - return d; + + if (changesFound) return delta; + + return d; } - function is_different (data) { - - if (!patientData.actual) return true; - - var old = { - sgv: patientData.actual.slice(-1).pop( ) - , mbg: patientData.mbg.slice(-1).pop( ) - , treatment: patientData.treatment.slice(-1).pop( ) - , cal: patientData.cal.slice(-1).pop( ) - }; - var last = { - sgv: data.sgvs.slice(-1).pop( ) - , mbg: data.mbgs.slice(-1).pop( ) - , treatment: data.treatments.slice(-1).pop( ) - , cal: data.cals.slice(-1).pop( ) - }; - - // textual diff of objects - if (JSON.stringify(old) == JSON.stringify(last)) { - console.info('data is NOT different, will not send to clients'); - return false; - } - return true; - } - start( ); configure( ); listeners( ); diff --git a/static/js/client.js b/static/js/client.js index 6e528bd86b5..c8c9d76c657 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1686,6 +1686,10 @@ Array.prototype.diff = function(a) { console.log('SGV data updated with', diff.length, 'new records'); SGVdata = SGVdata.concat(diff); } + + SGVdata.sort(function(a, b) { + return a.x - b.x; + }); // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) latestUpdateTime = Date.now(); @@ -1693,17 +1697,15 @@ Array.prototype.diff = function(a) { prevSGV = SGVdata[SGVdata.length - 2]; } - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - console.log('Total SGV data size', SGVdata.length); + console.log('Latest SGV', latestSGV.date); + console.log('prevSGV', prevSGV.date); // profile, calibration and device status if (d.profile) profile = d.profile[0]; - if (d.cal) cal = d.cal[d.cal.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatusData; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1750,7 +1752,7 @@ Array.prototype.diff = function(a) { } else { var newTreatments = treatments.diff(d.treatments); console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newRecords); + treatments = treatments.concat(newTreatments); treatments.sort(function(a, b) { return a.x - b.x; }); From 7987562d8ab7b4a6927d4bdb179d440aa3ec7886 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 13:24:11 -0700 Subject: [PATCH 070/661] replace data fields when loading, instead of appending forever --- lib/data.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8fe10c6552c..c80cbd400c5 100644 --- a/lib/data.js +++ b/lib/data.js @@ -54,24 +54,28 @@ function init (env, ctx) { var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { + var mbgs = []; + var sgvs = []; results.forEach(function (element) { if (element) { if (element.mbg) { - data.mbgs.push({ + mbgs.push({ y: element.mbg, x: element.date, d: element.dateString, device: element.device }); } else if (element.sgv) { - data.sgvs.push({ + sgvs.push({ y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } }); - } - //FIXME: sort in mongo - sort(data.mbgs); - sort(data.sgvs); + //FIXME: sort in mongo + sort(mbgs); + sort(sgvs); + data.mbgs = mbgs; + data.sgvs = sgvs; + } callback(); }) }, cal: function (callback) { @@ -79,13 +83,15 @@ function init (env, ctx) { var cq = { count: 1, find: {"type": "cal"} }; ctx.entries.list(cq, function (err, results) { if (!err && results) { + var cals = []; results.forEach(function (element) { if (element) { - data.cals.push({ + cals.push({ x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope }); } }); + data.cals = cals; } callback(); }); @@ -93,16 +99,19 @@ function init (env, ctx) { var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - data.treatments = results.map(function (treatment) { + var treatments = []; + treatments = results.map(function (treatment) { var timestamp = new Date(treatment.timestamp || treatment.created_at); treatment.x = timestamp.getTime(); return treatment; }); //FIXME: sort in mongo - data.treatments.sort(function(a, b) { + treatments.sort(function(a, b) { return a.x - b.x; }); + + data.treatments = treatments; } callback(); }); @@ -110,13 +119,15 @@ function init (env, ctx) { ctx.profile.list(function (err, results) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. + var profiles = []; results.forEach(function (element, index, array) { if (element) { if (element.dia) { - data.profiles[0] = element; + profiles[0] = element; } } }); + data.profiles = profiles; } callback(); }); From 524084bf5e5a0691022ae9d9f567b91fb466b1f1 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 6 Jun 2015 23:47:02 +0300 Subject: [PATCH 071/661] Don't mess with Array prototype --- lib/data.js | 4 +--- lib/websocket.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8948dccf5f4..abc9cbb4f93 100644 --- a/lib/data.js +++ b/lib/data.js @@ -22,9 +22,7 @@ function init (env, ctx) { }; var ONE_DAY = 86400000 - , TWO_DAYS = 172800000 - , FORTY_MINUTES = 2400000 - , UPDATE_DELTA = FORTY_MINUTES; + , TWO_DAYS = 172800000; var dir2Char = { diff --git a/lib/websocket.js b/lib/websocket.js index e201dc3e7fe..d80538ece10 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,13 +2,13 @@ var ar2 = require('./plugins/ar2')(); -Array.prototype.nsDiff = function(a) { +function nsArrayDiff(oldArray, newArray) { var seen = {}; - var l = this.length; - for (var i = 0; i < l; i++) { seen[this[i].x] = true }; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; var result = []; - l = a.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(a[i].x)) result.push(a[i])}; + l = newArray.length; + for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; return result; } @@ -224,7 +224,7 @@ function init (server) { // if there's no updates done so far, just return the full set if (!patientData.sgvs) return d; - var sgvDelta = patientData.sgvs.nsDiff(d.sgvs); + var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); if (sgvDelta.length > 0) { changesFound = true; @@ -232,7 +232,7 @@ function init (server) { delta.sgvs = sgvDelta; }; - var treatmentDelta = patientData.treatments.nsDiff(d.treatments); + var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); if (treatmentDelta.length > 0) { changesFound = true; @@ -240,7 +240,7 @@ function init (server) { delta.treatments = treatmentDelta; }; - var mbgsDelta = patientData.mbgs.nsDiff(d.mbgs); + var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); if (mbgsDelta.length > 0) { changesFound = true; @@ -248,7 +248,7 @@ function init (server) { delta.mbgs = mbgsDelta; }; - var calsDelta = patientData.cals.nsDiff(d.cals); + var calsDelta = nsArrayDiff(patientData.cals,d.cals); if (calsDelta.length > 0) { changesFound = true; From 23509d11e6942cbe31bff9539f764193baa95997 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 00:59:25 +0300 Subject: [PATCH 072/661] Fix mbg data updates --- static/js/client.js | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 161292b481b..078c337b144 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -41,6 +41,7 @@ function nsArrayDiff(oldArray, newArray) { var socket , isInitialData = false , SGVdata = [] + , MBGdata = [] , latestSGV , latestUpdateTime , prevSGV @@ -1693,7 +1694,7 @@ function nsArrayDiff(oldArray, newArray) { SGVdata = SGVdata.concat(diff); } - SGVdata.sort(function(a, b) { + SGVdata.sort(function(a, b) { return a.x - b.x; }); @@ -1742,7 +1743,22 @@ function nsArrayDiff(oldArray, newArray) { //Add MBG's also, pretend they are SGV's if (d.mbgs) { - data = data.concat(d.mbgs.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + MBGdata = d.mbgs; + } else { + var diff = nsArrayDiff(MBGdata,d.mbgs); + console.log('MBG data updated with', diff.length, 'new records'); + MBGdata = MBGdata.concat(diff); + } + + MBGdata.sort(function(a, b) { + return a.x - b.x; + }); + + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); } data.forEach(function (d) { From 7fc8042201983913bb5d8259f71837aa24cde724 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 01:15:28 +0300 Subject: [PATCH 073/661] Now actually fixed the MBGs --- static/js/client.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 078c337b144..31627ac35e8 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1705,8 +1705,6 @@ function nsArrayDiff(oldArray, newArray) { } console.log('Total SGV data size', SGVdata.length); - console.log('Latest SGV', latestSGV.date); - console.log('prevSGV', prevSGV.date); // profile, calibration and device status @@ -1741,26 +1739,21 @@ function nsArrayDiff(oldArray, newArray) { data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); } - //Add MBG's also, pretend they are SGV's + //Add MBG's also, pretend they are MBG's if (d.mbgs) { - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); + // replace all locally stored MBG data + console.log('Replacing all local MBG records'); MBGdata = d.mbgs; } else { var diff = nsArrayDiff(MBGdata,d.mbgs); console.log('MBG data updated with', diff.length, 'new records'); MBGdata = MBGdata.concat(diff); - } - - MBGdata.sort(function(a, b) { - return a.x - b.x; - }); - - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + } } + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); + data.forEach(function (d) { if (d.y < 39) d.color = 'transparent'; From ad27d8f6b130076a4d078413ddec50a0b2b2a806 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 15:27:14 -0700 Subject: [PATCH 074/661] changed indent of client.js to 2 spaces like everything else --- static/js/client.js | 3374 +++++++++++++++++++++---------------------- 1 file changed, 1687 insertions(+), 1687 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 31627ac35e8..fbb8fabfc61 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -7,1884 +7,1884 @@ function nsArrayDiff(oldArray, newArray) { for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; var result = []; l = newArray.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } return result; } (function () { - 'use strict'; - - var BRUSH_TIMEOUT = 300000 // 5 minutes in ms - , DEBOUNCE_MS = 10 - , TOOLTIP_TRANS_MS = 200 // milliseconds - , UPDATE_TRANS_MS = 750 // milliseconds - , ONE_MIN_IN_MS = 60000 - , FIVE_MINS_IN_MS = 300000 - , SIX_MINS_IN_MS = 360000 - , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 - , TWENTY_FIVE_MINS_IN_MS = 1500000 - , THIRTY_MINS_IN_MS = 1800000 - , SIXTY_MINS_IN_MS = 3600000 - , FORMAT_TIME_12 = '%-I:%M %p' - , FORMAT_TIME_12_COMAPCT = '%-I:%M' - , FORMAT_TIME_24 = '%H:%M%' - , FORMAT_TIME_12_SCALE = '%-I %p' - , FORMAT_TIME_24_SCALE = '%H' - , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800 - , MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400 - , WEEK_IN_SECS = 604800; - - var socket - , isInitialData = false - , SGVdata = [] - , MBGdata = [] - , latestSGV - , latestUpdateTime - , prevSGV - , treatments - , profile - , cal - , devicestatusData - , padding = { top: 0, right: 10, bottom: 30, left: 10 } - , opacity = {current: 1, DAY: 1, NIGHT: 0.5} - , now = Date.now() - , data = [] - , foucusRangeMS = THREE_HOURS_MS - , clientAlarms = {} - , audio = document.getElementById('audio') - , alarmInProgress = false - , currentAlarmType = null - , alarmSound = 'alarm.mp3' - , urgentAlarmSound = 'alarm2.mp3'; - - var jqWindow - , tooltip - , tickValues - , charts - , futureOpacity - , focus - , context - , xScale, xScale2, yScale, yScale2 - , xAxis, yAxis, xAxis2, yAxis2 - , prevChartWidth = 0 - , prevChartHeight = 0 - , focusHeight - , contextHeight - , dateFn = function (d) { return new Date(d.date) } - , documentHidden = false - , brush - , brushTimer - , brushInProgress = false - , clip; - - function formatTime(time, compact) { - var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); - if (!isTimeFormat24()) { - time = time.toLowerCase(); - } - return time; + 'use strict'; + + var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 + , TOOLTIP_TRANS_MS = 200 // milliseconds + , UPDATE_TRANS_MS = 750 // milliseconds + , ONE_MIN_IN_MS = 60000 + , FIVE_MINS_IN_MS = 300000 + , SIX_MINS_IN_MS = 360000 + , THREE_HOURS_MS = 3 * 60 * 60 * 1000 + , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 + , TWENTY_FIVE_MINS_IN_MS = 1500000 + , THIRTY_MINS_IN_MS = 1800000 + , SIXTY_MINS_IN_MS = 3600000 + , FORMAT_TIME_12 = '%-I:%M %p' + , FORMAT_TIME_12_COMAPCT = '%-I:%M' + , FORMAT_TIME_24 = '%H:%M%' + , FORMAT_TIME_12_SCALE = '%-I %p' + , FORMAT_TIME_24_SCALE = '%H' + , WIDTH_SMALL_DOTS = 400 + , WIDTH_BIG_DOTS = 800 + , MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400 + , WEEK_IN_SECS = 604800; + + var socket + , isInitialData = false + , SGVdata = [] + , MBGdata = [] + , latestSGV + , latestUpdateTime + , prevSGV + , treatments + , profile + , cal + , devicestatusData + , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , opacity = {current: 1, DAY: 1, NIGHT: 0.5} + , now = Date.now() + , data = [] + , foucusRangeMS = THREE_HOURS_MS + , clientAlarms = {} + , audio = document.getElementById('audio') + , alarmInProgress = false + , currentAlarmType = null + , alarmSound = 'alarm.mp3' + , urgentAlarmSound = 'alarm2.mp3'; + + var jqWindow + , tooltip + , tickValues + , charts + , futureOpacity + , focus + , context + , xScale, xScale2, yScale, yScale2 + , xAxis, yAxis, xAxis2, yAxis2 + , prevChartWidth = 0 + , prevChartHeight = 0 + , focusHeight + , contextHeight + , dateFn = function (d) { return new Date(d.date) } + , documentHidden = false + , brush + , brushTimer + , brushInProgress = false + , clip; + + function formatTime(time, compact) { + var timeFormat = getTimeFormat(false, compact); + time = d3.time.format(timeFormat)(time); + if (!isTimeFormat24()) { + time = time.toLowerCase(); } - - function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + return time; + } + + function isTimeFormat24() { + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + } + + function getTimeFormat(isForScale, compact) { + var timeFormat = FORMAT_TIME_12; + if (isTimeFormat24()) { + timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; + } else { + timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); } - function getTimeFormat(isForScale, compact) { - var timeFormat = FORMAT_TIME_12; - if (isTimeFormat24()) { - timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; - } else { - timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); - } + return timeFormat; + } - return timeFormat; + // lixgbg: Convert mg/dL BG value to metric mmol + function scaleBg(bg) { + if (browserSettings.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; } + } + + //see http://stackoverflow.com/a/9609450 + var decodeEntities = (function() { + // this prevents any overhead from creating the object each time + var element = document.createElement('div'); + + function decodeHTMLEntities (str) { + if(str && typeof str === 'string') { + // strip script/html tags + str = str.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); + str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, ''); + element.innerHTML = str; + str = element.textContent; + element.textContent = ''; + } - // lixgbg: Convert mg/dL BG value to metric mmol - function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } + return str; } - //see http://stackoverflow.com/a/9609450 - var decodeEntities = (function() { - // this prevents any overhead from creating the object each time - var element = document.createElement('div'); - - function decodeHTMLEntities (str) { - if(str && typeof str === 'string') { - // strip script/html tags - str = str.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); - str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gmi, ''); - element.innerHTML = str; - str = element.textContent; - element.textContent = ''; - } - - return str; - } + return decodeHTMLEntities; + })(); - return decodeHTMLEntities; - })(); + function updateTitle() { - function updateTitle() { + var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) + , ago = timeAgo(time); - var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + var bg_title = browserSettings.customTitle || ''; - var bg_title = browserSettings.customTitle || ''; + function s(value, sep) { return value ? value + ' ' : sep || ''; } - function s(value, sep) { return value ? value + ' ' : sep || ''; } + if (ago && ago.status !== 'current') { + bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; + } else if (latestSGV) { + var currentMgdl = latestSGV.y; - if (ago && ago.status !== 'current') { - bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; - } else if (latestSGV) { - var currentMgdl = latestSGV.y; + if (currentMgdl < 39) { + bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; + } else { + var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); + bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; + } + } - if (currentMgdl < 39) { - bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; - } else { - var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); - bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; - } - } + $(document).attr('title', bg_title); + } + + function calcDeltaDisplay(prevEntry, currentEntry) { + var delta = null + , prevMgdl = prevEntry && prevEntry.y + , currentMgdl = currentEntry && currentEntry.y; + + if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { + //TODO consider using raw data here + delta = null; + } else { + delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); + if (browserSettings.units == 'mmol') { + delta = delta.toFixed(1); + } - $(document).attr('title', bg_title); + delta = (delta >= 0 ? '+' : '') + delta; } - function calcDeltaDisplay(prevEntry, currentEntry) { - var delta = null - , prevMgdl = prevEntry && prevEntry.y - , currentMgdl = currentEntry && currentEntry.y; - - if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { - //TODO consider using raw data here - delta = null; + return delta; + } + + function isRawBGEnabled() { + return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + } + + function showRawBGs(sgv, noise, cal) { + return cal + && isRawBGEnabled() + && (browserSettings.showRawbg == 'always' + || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + ); + } + + function noiseCodeToDisplay(sgv, noise) { + var display; + switch (parseInt(noise)) { + case 0: display = '---'; break; + case 1: display = 'Clean'; break; + case 2: display = 'Light'; break; + case 3: display = 'Medium'; break; + case 4: display = 'Heavy'; break; + default: + if (sgv < 40) { + display = 'Heavy'; } else { - delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); - if (browserSettings.units == 'mmol') { - delta = delta.toFixed(1); - } - - delta = (delta >= 0 ? '+' : '') + delta; + display = '~~~'; } - - return delta; + break; } - function isRawBGEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; - } + return display; + } - function showRawBGs(sgv, noise, cal) { - return cal - && isRawBGEnabled() - && (browserSettings.showRawbg == 'always' - || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) - ); - } + function rawIsigToRawBg(entry, cal) { + + var raw = 0 + , unfiltered = parseInt(entry.unfiltered) || 0 + , filtered = parseInt(entry.filtered) || 0 + , sgv = entry.y + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; - function noiseCodeToDisplay(sgv, noise) { - var display; - switch (parseInt(noise)) { - case 0: display = '---'; break; - case 1: display = 'Clean'; break; - case 2: display = 'Light'; break; - case 3: display = 'Medium'; break; - case 4: display = 'Heavy'; break; - default: - if (sgv < 40) { - display = 'Heavy'; - } else { - display = '~~~'; - } - break; - } - return display; + if (slope == 0 || unfiltered == 0 || scale == 0) { + raw = 0; + } else if (filtered == 0 || sgv < 40) { + raw = scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + raw = scale * ( unfiltered - intercept) / slope / ratio; } - function rawIsigToRawBg(entry, cal) { + return Math.round(raw); + } + + // initial setup of chart when data is first made available + function initializeCharts() { + + // define the parts of the axis that aren't dependent on width or height + xScale = d3.time.scale() + .domain(d3.extent(data, function (d) { return d.date; })); + + yScale = d3.scale.log() + .domain([scaleBg(30), scaleBg(510)]); + + xScale2 = d3.time.scale() + .domain(d3.extent(data, function (d) { return d.date; })); + + yScale2 = d3.scale.log() + .domain([scaleBg(36), scaleBg(420)]); + + var tickFormat = d3.time.format.multi( [ + ['.%L', function(d) { return d.getMilliseconds(); }], + [':%S', function(d) { return d.getSeconds(); }], + ['%I:%M', function(d) { return d.getMinutes(); }], + [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], + ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], + ['%b %d', function(d) { return d.getDate() != 1; }], + ['%B', function(d) { return d.getMonth(); }], + ['%Y', function() { return true; }] + ]); + + xAxis = d3.svg.axis() + .scale(xScale) + .tickFormat(tickFormat) + .ticks(4) + .orient('bottom'); + + yAxis = d3.svg.axis() + .scale(yScale) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); + + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(tickFormat) + .ticks(6) + .orient('bottom'); + + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('right'); + + // setup a brush + brush = d3.svg.brush() + .x(xScale2) + .on('brushstart', brushStarted) + .on('brush', brushed) + .on('brushend', brushEnded); + + updateChart(true); + } + + // get the desired opacity for context chart based on the brush extent + function highlightBrushPoints(data) { + if (data.date.getTime() >= brush.extent()[0].getTime() && data.date.getTime() <= brush.extent()[1].getTime()) { + return futureOpacity(data.date.getTime() - latestSGV.x); + } else { + return 0.5; + } + } - var raw = 0 - , unfiltered = parseInt(entry.unfiltered) || 0 - , filtered = parseInt(entry.filtered) || 0 - , sgv = entry.y - , scale = parseFloat(cal.scale) || 0 - , intercept = parseFloat(cal.intercept) || 0 - , slope = parseFloat(cal.slope) || 0; + // clears the current user brush and resets to the current real time data + var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + // get current time range + var dataRange = d3.extent(data, dateFn); - if (slope == 0 || unfiltered == 0 || scale == 0) { - raw = 0; - } else if (filtered == 0 || sgv < 40) { - raw = scale * (unfiltered - intercept) / slope; - } else { - var ratio = scale * (filtered - intercept) / slope / sgv; - raw = scale * ( unfiltered - intercept) / slope / ratio; - } + // update brush and focus chart with recent data + d3.select('.brush') + .transition() + .duration(UPDATE_TRANS_MS) + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - return Math.round(raw); - } + if (!skipBrushing) { + brushed(true); - // initial setup of chart when data is first made available - function initializeCharts() { - - // define the parts of the axis that aren't dependent on width or height - xScale = d3.time.scale() - .domain(d3.extent(data, function (d) { return d.date; })); - - yScale = d3.scale.log() - .domain([scaleBg(30), scaleBg(510)]); - - xScale2 = d3.time.scale() - .domain(d3.extent(data, function (d) { return d.date; })); - - yScale2 = d3.scale.log() - .domain([scaleBg(36), scaleBg(420)]); - - var tickFormat = d3.time.format.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - ['%I:%M', function(d) { return d.getMinutes(); }], - [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], - ['%b %d', function(d) { return d.getDate() != 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); - - xAxis = d3.svg.axis() - .scale(xScale) - .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); - - yAxis = d3.svg.axis() - .scale(yScale) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); - - xAxis2 = d3.svg.axis() - .scale(xScale2) - .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); - - yAxis2 = d3.svg.axis() - .scale(yScale2) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); - - // setup a brush - brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) - .on('brush', brushed) - .on('brushend', brushEnded); - - updateChart(true); + // clear user brush tracking + brushInProgress = false; + } + }, DEBOUNCE_MS); + + function brushStarted() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return 1; }); + } + + function brushEnded() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return highlightBrushPoints(d) }); + } + + function alarmingNow() { + return $('#container').hasClass('alarming'); + } + + function inRetroMode() { + if (!brush) return false; + + var time = brush.extent()[1].getTime(); + + return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; + } + + function errorCodeToDisplay(errorCode) { + var errorDisplay; + + switch (parseInt(errorCode)) { + case 0: errorDisplay = '??0'; break; //None + case 1: errorDisplay = '?SN'; break; //SENSOR_NOT_ACTIVE + case 2: errorDisplay = '??2'; break; //MINIMAL_DEVIATION + case 3: errorDisplay = '?NA'; break; //NO_ANTENNA + case 5: errorDisplay = '?NC'; break; //SENSOR_NOT_CALIBRATED + case 6: errorDisplay = '?CD'; break; //COUNTS_DEVIATION + case 7: errorDisplay = '??7'; break; //? + case 8: errorDisplay = '??8'; break; //? + case 9: errorDisplay = '?HG'; break; //ABSOLUTE_DEVIATION + case 10: errorDisplay = '???'; break; //POWER_DEVIATION + case 12: errorDisplay = '?RF'; break; //BAD_RF + default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; } - // get the desired opacity for context chart based on the brush extent - function highlightBrushPoints(data) { - if (data.date.getTime() >= brush.extent()[0].getTime() && data.date.getTime() <= brush.extent()[1].getTime()) { - return futureOpacity(data.date.getTime() - latestSGV.x); - } else { - return 0.5; - } + return errorDisplay; + } + + var brushed = _.debounce(function brushed(skipTimer) { + + if (!skipTimer) { + // set a timer to reset focus chart to real-time data + clearTimeout(brushTimer); + brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); + brushInProgress = true; } - // clears the current user brush and resets to the current real time data - var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + var brushExtent = brush.extent(); - // get current time range - var dataRange = d3.extent(data, dateFn); + // ensure that brush extent is fixed at 3.5 hours + if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { - // update brush and focus chart with recent data + // ensure that brush updating is with the time range + if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { + brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } else { + brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } + } - if (!skipBrushing) { - brushed(true); + var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + var bgButton = $('.bgButton') + , bgStatus = $('.bgStatus') + , currentBG = $('.bgStatus .currentBG') + , currentDirection = $('.bgStatus .currentDirection') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') + , rawNoise = bgButton.find('.rawnoise') + , rawbg = rawNoise.find('em') + , noiseLevel = rawNoise.find('label') + , lastEntry = $('#lastEntry'); + + + function updateCurrentSGV(entry) { + var value = entry.y + , time = new Date(entry.x).getTime() + , ago = timeAgo(time) + , isCurrent = ago.status === 'current'; + + if (value == 9) { + currentBG.text(''); + } else if (value < 39) { + currentBG.html(errorCodeToDisplay(value)); + } else if (value < 40) { + currentBG.text('LOW'); + } else if (value > 400) { + currentBG.text('HIGH'); + } else { + currentBG.text(scaleBg(value)); + } - // clear user brush tracking - brushInProgress = false; + bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); + if (!alarmingNow()) { + bgButton.removeClass('urgent warning inrange'); + if (isCurrent && !inRetroMode()) { + bgButton.addClass(sgvToColoredRange(value)); } - }, DEBOUNCE_MS); + } - function brushStarted() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return 1; }); - } + if (showRawBGs(entry.y, entry.noise, cal)) { + rawNoise.css('display', 'inline-block'); + rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); + noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); + } else { + rawNoise.hide(); + } - function brushEnded() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return highlightBrushPoints(d) }); - } - function alarmingNow() { - return $('#container').hasClass('alarming'); + currentBG.toggleClass('icon-hourglass', value == 9); + currentBG.toggleClass('error-code', value < 39); + currentBG.toggleClass('bg-limit', value == 39 || value > 400); + + $('.container').removeClass('loading'); + } - function inRetroMode() { - if (!brush) return false; + function updateBGDelta(prevEntry, currentEntry) { + + var pill = majorPills.find('span.pill.bgdelta'); + if (!pill || pill.length == 0) { + pill = $(''); + majorPills.append(pill); + } + + var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); - var time = brush.extent()[1].getTime(); + if (deltaDisplay == null) { + pill.children('em').hide(); + } else { + pill.children('em').text(deltaDisplay).show(); + } + + if (browserSettings.units == 'mmol') { + pill.children('label').text('mmol/L'); + } else { + pill.children('label').text('mg/dL'); + } - return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; } - function errorCodeToDisplay(errorCode) { - var errorDisplay; - - switch (parseInt(errorCode)) { - case 0: errorDisplay = '??0'; break; //None - case 1: errorDisplay = '?SN'; break; //SENSOR_NOT_ACTIVE - case 2: errorDisplay = '??2'; break; //MINIMAL_DEVIATION - case 3: errorDisplay = '?NA'; break; //NO_ANTENNA - case 5: errorDisplay = '?NC'; break; //SENSOR_NOT_CALIBRATED - case 6: errorDisplay = '?CD'; break; //COUNTS_DEVIATION - case 7: errorDisplay = '??7'; break; //? - case 8: errorDisplay = '??8'; break; //? - case 9: errorDisplay = '?HG'; break; //ABSOLUTE_DEVIATION - case 10: errorDisplay = '???'; break; //POWER_DEVIATION - case 12: errorDisplay = '?RF'; break; //BAD_RF - default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; + function updatePlugins(sgv, time) { + var env = {}; + env.profile = profile; + env.majorPills = majorPills; + env.minorPills = minorPills; + env.sgv = Number(sgv); + env.treatments = treatments; + env.time = time; + env.tooltip = tooltip; + + //all enabled plugins get a chance to add data, even if they aren't shown + Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { + // Update the env through data provider plugins + plugin.setEnv(env); + + // check if the plugin implements processing data + if (plugin.getData) { + env[plugin.name] = plugin.getData(); } + }); - return errorDisplay; + // update data for all the plugins, before updating visualisations + Nightscout.plugins.setEnvs(env); + + //only shown plugins get a chance to update visualisations + Nightscout.plugins.updateVisualisations(browserSettings); } - var brushed = _.debounce(function brushed(skipTimer) { + // predict for retrospective data + // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m + // of data instead of 5, which eliminates the incorrect and misleading predictions generated when + // the dexcom switches from unfiltered to filtered at the start of a rapid rise or fall, while preserving + // almost identical predications at other times. + var lookback = 2; - if (!skipTimer) { - // set a timer to reset focus chart to real-time data - clearTimeout(brushTimer); - brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); - brushInProgress = true; - } + var nowData = data.filter(function(d) { + return d.type == 'sgv'; + }); - var brushExtent = brush.extent(); - - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { - - // ensure that brush updating is with the time range - if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { - brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } else { - brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } + if (inRetroMode()) { + var retroTime = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + // filter data for -12 and +5 minutes from reference time for retrospective focus data prediction + var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; + nowData = nowData.filter(function(d) { + return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && + d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS + }); + + // sometimes nowData contains duplicates. uniq it. + var lastDate = new Date('1/1/1970'); + nowData = nowData.filter(function(d) { + var ok = (lastDate.getTime() + ONE_MIN_IN_MS) < d.date.getTime(); + lastDate = d.date; + return ok; + }); + + var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; + if (focusPoint) { + updateCurrentSGV(focusPoint); + currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); + + var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; + if (prevfocusPoint) { + updateBGDelta(prevfocusPoint, focusPoint); + } else { + updateBGDelta(); } + } else { + updateBGDelta(); + currentBG.text('---'); + currentDirection.text('-'); + rawNoise.hide(); + bgButton.removeClass('urgent warning inrange'); + } - var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - var bgButton = $('.bgButton') - , bgStatus = $('.bgStatus') - , currentBG = $('.bgStatus .currentBG') - , currentDirection = $('.bgStatus .currentDirection') - , majorPills = $('.bgStatus .majorPills') - , minorPills = $('.bgStatus .minorPills') - , rawNoise = bgButton.find('.rawnoise') - , rawbg = rawNoise.find('em') - , noiseLevel = rawNoise.find('label') - , lastEntry = $('#lastEntry'); - - - function updateCurrentSGV(entry) { - var value = entry.y - , time = new Date(entry.x).getTime() - , ago = timeAgo(time) - , isCurrent = ago.status === 'current'; - - if (value == 9) { - currentBG.text(''); - } else if (value < 39) { - currentBG.html(errorCodeToDisplay(value)); - } else if (value < 40) { - currentBG.text('LOW'); - } else if (value > 400) { - currentBG.text('HIGH'); - } else { - currentBG.text(scaleBg(value)); - } - - bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); - if (!alarmingNow()) { - bgButton.removeClass('urgent warning inrange'); - if (isCurrent && !inRetroMode()) { - bgButton.addClass(sgvToColoredRange(value)); - } - } - - if (showRawBGs(entry.y, entry.noise, cal)) { - rawNoise.css('display', 'inline-block'); - rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); - noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); - } else { - rawNoise.hide(); - } - - - currentBG.toggleClass('icon-hourglass', value == 9); - currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value == 39 || value > 400); - - $('.container').removeClass('loading'); + updatePlugins(focusPoint && focusPoint.y, retroTime); + + $('#currentTime') + .text(formatTime(retroTime, true)) + .css('text-decoration','line-through'); + + updateTimeAgo(); + } else { + // if the brush comes back into the current time range then it should reset to the current time and sg + nowData = nowData.slice(nowData.length - 1 - lookback, nowData.length); + nowDate = new Date(now); + + updateCurrentSGV(latestSGV); + updateClockDisplay(); + updateTimeAgo(); + + var battery = devicestatusData && devicestatusData.uploaderBattery; + if (battery) { + $('#uploaderBattery em').text(battery + '%'); + $('#uploaderBattery label') + .toggleClass('icon-battery-100', battery >= 95) + .toggleClass('icon-battery-75', battery < 95 && battery >= 55) + .toggleClass('icon-battery-50', battery < 55 && battery >= 30) + .toggleClass('icon-battery-25', battery < 30); + + $('#uploaderBattery') + .show() + .toggleClass('warn', battery <= 30 && battery > 20) + .toggleClass('urgent', battery <= 20); + } else { + $('#uploaderBattery').hide(); + } - } + updateBGDelta(prevSGV, latestSGV); - function updateBGDelta(prevEntry, currentEntry) { + updatePlugins(latestSGV.y, nowDate); - var pill = majorPills.find('span.pill.bgdelta'); - if (!pill || pill.length == 0) { - pill = $(''); - majorPills.append(pill); - } + currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); + } - var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); + xScale.domain(brush.extent()); - if (deltaDisplay == null) { - pill.children('em').hide(); - } else { - pill.children('em').text(deltaDisplay).show(); - } + // get slice of data so that concatenation of predictions do not interfere with subsequent updates + var focusData = data.slice(); + if (nowData.length > lookback) { + focusData = focusData.concat(predictAR(nowData, lookback)); + } - if (browserSettings.units == 'mmol') { - pill.children('label').text('mmol/L'); - } else { - pill.children('label').text('mg/dL'); - } + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - } + var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); - function updatePlugins(sgv, time) { - var env = {}; - env.profile = profile; - env.majorPills = majorPills; - env.minorPills = minorPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - env.tooltip = tooltip; - - //all enabled plugins get a chance to add data, even if they aren't shown - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { - // Update the env through data provider plugins - plugin.setEnv(env); - - // check if the plugin implements processing data - if (plugin.getData) { - env[plugin.name] = plugin.getData(); - } - }); - - // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnvs(env); - - //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(browserSettings); - } + var dotRadius = function(type) { + var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + if (type == 'mbg') radius *= 2; + else if (type == 'rawbg') radius = Math.min(2, radius - 1); - // predict for retrospective data - // by changing lookback from 1 to 2, we modify the AR algorithm to determine its initial slope from 10m - // of data instead of 5, which eliminates the incorrect and misleading predictions generated when - // the dexcom switches from unfiltered to filtered at the start of a rapid rise or fall, while preserving - // almost identical predications at other times. - var lookback = 2; + return radius / focusRangeAdjustment; + }; - var nowData = data.filter(function(d) { - return d.type == 'sgv'; - }); + function isDexcom(device) { + return device && device.toLowerCase().indexOf('dexcom') == 0; + } - if (inRetroMode()) { - var retroTime = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - // filter data for -12 and +5 minutes from reference time for retrospective focus data prediction - var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; - nowData = nowData.filter(function(d) { - return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && - d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - }); - - // sometimes nowData contains duplicates. uniq it. - var lastDate = new Date('1/1/1970'); - nowData = nowData.filter(function(d) { - var ok = (lastDate.getTime() + ONE_MIN_IN_MS) < d.date.getTime(); - lastDate = d.date; - return ok; - }); - - var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; - if (focusPoint) { - updateCurrentSGV(focusPoint); - currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); - - var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; - if (prevfocusPoint) { - updateBGDelta(prevfocusPoint, focusPoint); - } else { - updateBGDelta(); - } - } else { - updateBGDelta(); - currentBG.text('---'); - currentDirection.text('-'); - rawNoise.hide(); - bgButton.removeClass('urgent warning inrange'); - } - - updatePlugins(focusPoint && focusPoint.y, retroTime); - - $('#currentTime') - .text(formatTime(retroTime, true)) - .css('text-decoration','line-through'); - - updateTimeAgo(); - } else { - // if the brush comes back into the current time range then it should reset to the current time and sg - nowData = nowData.slice(nowData.length - 1 - lookback, nowData.length); - nowDate = new Date(now); - - updateCurrentSGV(latestSGV); - updateClockDisplay(); - updateTimeAgo(); - - var battery = devicestatusData && devicestatusData.uploaderBattery; - if (battery) { - $('#uploaderBattery em').text(battery + '%'); - $('#uploaderBattery label') - .toggleClass('icon-battery-100', battery >= 95) - .toggleClass('icon-battery-75', battery < 95 && battery >= 55) - .toggleClass('icon-battery-50', battery < 55 && battery >= 30) - .toggleClass('icon-battery-25', battery < 30); - - $('#uploaderBattery') - .show() - .toggleClass('warn', battery <= 30 && battery > 20) - .toggleClass('urgent', battery <= 20); - } else { - $('#uploaderBattery').hide(); - } - - updateBGDelta(prevSGV, latestSGV); - - updatePlugins(latestSGV.y, nowDate); - - currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); - } + function prepareFocusCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale(d.date); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale(scaleBg(450)); + } else { + return yScale(d.sgv); + } + }) + .attr('fill', function (d) { return d.color; }) + .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) + .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { + return (isDexcom(d.device) ? 'white' : '#0099ff'); + }) + .attr('r', function (d) { return dotRadius(d.type); }); + + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - xScale.domain(brush.extent()); + // if new circle then just display + prepareFocusCircles(focusCircles.enter().append('circle')) + .on('mouseover', function (d) { + if (d.type != 'sgv' && d.type != 'mbg') return; - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = data.slice(); - if (nowData.length > lookback) { - focusData = focusData.concat(predictAR(nowData, lookback)); + var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawBG = 0 + , noiseLabel = ''; + + if (d.type == 'sgv') { + if (showRawBGs(d.y, d.noise, cal)) { + rawBG = scaleBg(rawIsigToRawBg(d, cal)); + } + noiseLabel = noiseCodeToDisplay(d.y, d.noise); } - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = focus.selectAll('circle').data(focusData, dateFn); + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + d.sgv + + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (rawBG ? '
    Raw BG: ' + rawBG : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(d.date)) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function (d) { + if (d.type != 'sgv' && d.type != 'mbg') return; + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + focusCircles.exit() + .remove(); + + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location + d3.selectAll('.path').remove(); + + // add treatment bubbles + // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) + var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; + + focus.selectAll('circle') + .data(treatments) + .each(function (d) { drawTreatment(d, bubbleScale, true) }); + + // transition open-top line to correct location + focus.select('.open-top') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[0])) + .attr('y2', prevChartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .attr('x1', xScale2(brush.extent()[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', prevChartHeight); + + focus.select('.now-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(nowDate)) + .attr('y1', yScale(scaleBg(36))) + .attr('x2', xScale(nowDate)) + .attr('y2', yScale(scaleBg(420))); + + context.select('.now-line') + .transition() + .attr('x1', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) + .attr('y2', yScale2(scaleBg(420))); + + // update x axis + focus.select('.x.axis') + .call(xAxis); + + // add clipping path so that data stays within axis + focusCircles.attr('clip-path', 'url(#clip)'); + + function prepareTreatCircles(sel) { + sel.attr('cx', function (d) { return xScale(d.created_at); }) + .attr('cy', function (d) { return yScale(d.displayBG); }) + .attr('r', function () { return dotRadius('mbg'); }) + .attr('stroke-width', 2) + .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) + .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); + + return sel; + } - var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + + (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + + (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + + (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + + (d.notes ? 'Notes: ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + }, DEBOUNCE_MS); + + // called for initial update and updates for resize + var updateChart = _.debounce(function updateChart(init) { + + if (documentHidden && !init) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + // get current data range + var dataRange = d3.extent(data, dateFn); - var dotRadius = function(type) { - var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') radius *= 2; - else if (type == 'rawbg') radius = Math.min(2, radius - 1); + // get the entire container height and width subtracting the padding + var chartWidth = (document.getElementById('chartContainer') + .getBoundingClientRect().width) - padding.left - padding.right; - return radius / focusRangeAdjustment; - }; + var chartHeight = (document.getElementById('chartContainer') + .getBoundingClientRect().height) - padding.top - padding.bottom; - function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') == 0; - } + // get the height of each chart based on its container size ratio + focusHeight = chartHeight * .7; + contextHeight = chartHeight * .2; - function prepareFocusCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale(d.date); }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale(scaleBg(450)); - } else { - return yScale(d.sgv); - } - }) - .attr('fill', function (d) { return d.color; }) - .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : '#0099ff'); - }) - .attr('r', function (d) { return dotRadius(d.type); }); - - if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); - } - - return sel; - } + // get current brush extent + var currentBrushExtent = brush.extent(); - // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareFocusCircles(focusCircles.enter().append('circle')) - .on('mouseover', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - - var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawBG = 0 - , noiseLabel = ''; - - if (d.type == 'sgv') { - if (showRawBGs(d.y, d.noise, cal)) { - rawBG = scaleBg(rawIsigToRawBg(d, cal)); - } - noiseLabel = noiseCodeToDisplay(d.y, d.noise); - } - - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawBG ? '
    Raw BG: ' + rawBG : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(d.date)) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - focusCircles.exit() - .remove(); - - // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location - d3.selectAll('.path').remove(); - - // add treatment bubbles - // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; - - focus.selectAll('circle') - .data(treatments) - .each(function (d) { drawTreatment(d, bubbleScale, true) }); + // only redraw chart if chart size has changed + if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { + + prevChartWidth = chartWidth; + prevChartHeight = chartHeight; + + //set the width and height of the SVG element + charts.attr('width', chartWidth + padding.left + padding.right) + .attr('height', chartHeight + padding.top + padding.bottom); + + // ranges are based on the width and height available so reset + xScale.range([0, chartWidth]); + xScale2.range([0, chartWidth]); + yScale.range([focusHeight, 0]); + yScale2.range([chartHeight, chartHeight - contextHeight]); + + if (init) { + + // if first run then just display axis with no transition + focus.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focus.select('.y') + .attr('transform', 'translate(' + chartWidth + ',0)') + .call(yAxis); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + context.append('g') + .attr('class', 'x brush') + .call(d3.svg.brush().x(xScale2).on('brush', brushed)) + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // disable resizing of brush + d3.select('.x.brush').select('.background').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + + // create a clipPath for when brushing + clip = charts.append('defs') + .append('clipPath') + .attr('id', 'clip') + .append('rect') + .attr('height', chartHeight) + .attr('width', chartWidth); + + // add a line that marks the current time + focus.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'target-top-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'target-bottom-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that opens up the brush extent from the context to the focus + focus.append('line') + .attr('class', 'open-top') + .attr('stroke', 'black') + .attr('stroke-width', 2); + + // add a x-axis line that closes the the brush container on left side + focus.append('line') + .attr('class', 'open-left') + .attr('stroke', 'white'); + + // add a x-axis line that closes the the brush container on right side + focus.append('line') + .attr('class', 'open-right') + .attr('stroke', 'white'); + + // add a line that marks the current time + context.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale2(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + context.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + } else { + + // for subsequent updates use a transition to animate the axis to the new position + var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); + + focusTransition.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focusTransition.select('.y') + .attr('transform', 'translate(' + chartWidth + ', 0)') + .call(yAxis); + + var contextTransition = context.transition().duration(UPDATE_TRANS_MS); + + contextTransition.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + + // reset brush location + context.select('.x.brush') + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // clear current brush + d3.select('.brush').call(brush.clear()); + + // redraw old brush with new dimensions + d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); + + // transition lines to correct location + focus.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); + + focus.select('.target-top-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); + + focus.select('.target-bottom-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); + + focus.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); // transition open-top line to correct location focus.select('.open-top') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', yScale(scaleBg(30))); + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(30))); // transition open-left line to correct location focus.select('.open-left') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[0])) - .attr('y2', prevChartHeight); + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[0])) + .attr('y2', chartHeight); // transition open-right line to correct location focus.select('.open-right') - .attr('x1', xScale2(brush.extent()[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', prevChartHeight); - - focus.select('.now-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(nowDate)) - .attr('y1', yScale(scaleBg(36))) - .attr('x2', xScale(nowDate)) - .attr('y2', yScale(scaleBg(420))); - - context.select('.now-line') - .transition() - .attr('x1', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale2(new Date(brush.extent()[1]- THIRTY_MINS_IN_MS))) - .attr('y2', yScale2(scaleBg(420))); - - // update x axis - focus.select('.x.axis') - .call(xAxis); - - // add clipping path so that data stays within axis - focusCircles.attr('clip-path', 'url(#clip)'); - - function prepareTreatCircles(sel) { - sel.attr('cx', function (d) { return xScale(d.created_at); }) - .attr('cy', function (d) { return yScale(d.displayBG); }) - .attr('r', function () { return dotRadius('mbg'); }) - .attr('stroke-width', 2) - .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) - .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); - - return sel; - } - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(d.created_at) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - }, DEBOUNCE_MS); - - // called for initial update and updates for resize - var updateChart = _.debounce(function updateChart(init) { - - if (documentHidden && !init) { - console.info('Document Hidden, not updating - ' + (new Date())); - return; - } - // get current data range - var dataRange = d3.extent(data, dateFn); - - // get the entire container height and width subtracting the padding - var chartWidth = (document.getElementById('chartContainer') - .getBoundingClientRect().width) - padding.left - padding.right; - - var chartHeight = (document.getElementById('chartContainer') - .getBoundingClientRect().height) - padding.top - padding.bottom; - - // get the height of each chart based on its container size ratio - focusHeight = chartHeight * .7; - contextHeight = chartHeight * .2; - - // get current brush extent - var currentBrushExtent = brush.extent(); - - // only redraw chart if chart size has changed - if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { - - prevChartWidth = chartWidth; - prevChartHeight = chartHeight; - - //set the width and height of the SVG element - charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); - - // ranges are based on the width and height available so reset - xScale.range([0, chartWidth]); - xScale2.range([0, chartWidth]); - yScale.range([focusHeight, 0]); - yScale2.range([chartHeight, chartHeight - contextHeight]); - - if (init) { - - // if first run then just display axis with no transition - focus.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focus.select('.y') - .attr('transform', 'translate(' + chartWidth + ',0)') - .call(yAxis); - - // if first run then just display axis with no transition - context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - context.append('g') - .attr('class', 'x brush') - .call(d3.svg.brush().x(xScale2).on('brush', brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); - - // create a clipPath for when brushing - clip = charts.append('defs') - .append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('height', chartHeight) - .attr('width', chartWidth); - - // add a line that marks the current time - focus.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'target-top-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'target-bottom-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that opens up the brush extent from the context to the focus - focus.append('line') - .attr('class', 'open-top') - .attr('stroke', 'black') - .attr('stroke-width', 2); - - // add a x-axis line that closes the the brush container on left side - focus.append('line') - .attr('class', 'open-left') - .attr('stroke', 'white'); - - // add a x-axis line that closes the the brush container on right side - focus.append('line') - .attr('class', 'open-right') - .attr('stroke', 'white'); - - // add a line that marks the current time - context.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale2(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - context.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - context.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - } else { - - // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); - - focusTransition.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focusTransition.select('.y') - .attr('transform', 'translate(' + chartWidth + ', 0)') - .call(yAxis); - - var contextTransition = context.transition().duration(UPDATE_TRANS_MS); - - contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); - - // reset brush location - context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // clear current brush - d3.select('.brush').call(brush.clear()); - - // redraw old brush with new dimensions - d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); - - // transition lines to correct location - focus.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); - - focus.select('.target-top-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); - - focus.select('.target-bottom-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); - - focus.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); - - // transition open-top line to correct location - focus.select('.open-top') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); - - // transition high line to correct location - context.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); - - // transition low line to correct location - context.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); - } - } + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', chartHeight); + + // transition high line to correct location + context.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); + + // transition low line to correct location + context.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); + } + } - // update domain - xScale2.domain(dataRange); + // update domain + xScale2.domain(dataRange); + + // only if a user brush is not active, update brush and focus chart with recent data + // else, just transition brush + var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); + if (!brushInProgress) { + updateBrush + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + brushed(true); + } else { + updateBrush + .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); + brushed(true); + } - // only if a user brush is not active, update brush and focus chart with recent data - // else, just transition brush - var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); - if (!brushInProgress) { - updateBrush - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - brushed(true); - } else { - updateBrush - .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); - brushed(true); - } + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale2(d.date); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + badData.push(d); + return yScale2(scaleBg(450)); + } else { + return yScale2(d.sgv); + } + }) + .attr('fill', function (d) { return d.color; }) + .style('opacity', function (d) { return highlightBrushPoints(d) }) + .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke', function (d) { return 'white'; }) + .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); + + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data); - - function prepareContextCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale2(d.date); }) - .attr('cy', function (d) { - if (isNaN(d.sgv)) { - badData.push(d); - return yScale2(scaleBg(450)); - } else { - return yScale2(d.sgv); - } - }) - .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'white'; }) - .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); - - if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); - } - - return sel; - } + return sel; + } - // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); + // if already existing then transition each circle to its new position + prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); - // if new circle then just display - prepareContextCircles(contextCircles.enter().append('circle')); + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); - contextCircles.exit() - .remove(); + contextCircles.exit() + .remove(); - // update x axis domain - context.select('.x') - .call(xAxis2); - - }, DEBOUNCE_MS); - - function sgvToColor(sgv) { - var color = 'grey'; - - if (browserSettings.theme == 'colors') { - if (sgv > app.thresholds.bg_high) { - color = 'red'; - } else if (sgv > app.thresholds.bg_target_top) { - color = 'yellow'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { - color = '#4cff00'; - } else if (sgv < app.thresholds.bg_low) { - color = 'red'; - } else if (sgv < app.thresholds.bg_target_bottom) { - color = 'yellow'; - } - } + // update x axis domain + context.select('.x') + .call(xAxis2); - return color; - } + }, DEBOUNCE_MS); - function sgvToColoredRange(sgv) { - var range = ''; - - if (browserSettings.theme == 'colors') { - if (sgv > app.thresholds.bg_high) { - range = 'urgent'; - } else if (sgv > app.thresholds.bg_target_top) { - range = 'warning'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { - range = 'inrange'; - } else if (sgv < app.thresholds.bg_low) { - range = 'urgent'; - } else if (sgv < app.thresholds.bg_target_bottom) { - range = 'warning'; - } - } + function sgvToColor(sgv) { + var color = 'grey'; - return range; + if (browserSettings.theme == 'colors') { + if (sgv > app.thresholds.bg_high) { + color = 'red'; + } else if (sgv > app.thresholds.bg_target_top) { + color = 'yellow'; + } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + color = '#4cff00'; + } else if (sgv < app.thresholds.bg_low) { + color = 'red'; + } else if (sgv < app.thresholds.bg_target_bottom) { + color = 'yellow'; + } } - - function generateAlarm(file) { - alarmInProgress = true; - var selector = '.audio.alarms audio.' + file; - d3.select(selector).each(function (d, i) { - var audio = this; - playAlarm(audio); - $(this).addClass('playing'); - }); - $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); - $('#container').addClass('alarming'); + return color; + } + + function sgvToColoredRange(sgv) { + var range = ''; + + if (browserSettings.theme == 'colors') { + if (sgv > app.thresholds.bg_high) { + range = 'urgent'; + } else if (sgv > app.thresholds.bg_target_top) { + range = 'warning'; + } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + range = 'inrange'; + } else if (sgv < app.thresholds.bg_low) { + range = 'urgent'; + } else if (sgv < app.thresholds.bg_target_bottom) { + range = 'warning'; + } } - function playAlarm(audio) { - // ?mute=true disables alarms to testers. - if (querystring.mute != 'true') { - audio.play(); - } else { - showNotification('Alarm was muted (?mute=true)'); - } - } + return range; + } - function stopAlarm(isClient, silenceTime) { - alarmInProgress = false; - $('.bgButton').removeClass('urgent warning'); - d3.selectAll('audio.playing').each(function () { - var audio = this; - audio.pause(); - $(this).removeClass('playing'); - }); - closeNotification(); - $('#container').removeClass('alarming'); - - // only emit ack if client invoke by button press - if (isClient) { - if (isTimeAgoAlarmType(currentAlarmType)) { - $('#container').removeClass('alarming-timeago'); - var alarm = getClientAlarm(currentAlarmType); - alarm.lastAckTime = Date.now(); - alarm.silenceTime = silenceTime; - console.info('time ago alarm (' + currentAlarmType + ', not acking to server'); - } else { - socket.emit('ack', currentAlarmType || 'alarm', silenceTime); - } - } - - brushed(false); + function generateAlarm(file) { + alarmInProgress = true; + var selector = '.audio.alarms audio.' + file; + d3.select(selector).each(function (d, i) { + var audio = this; + playAlarm(audio); + $(this).addClass('playing'); + }); + $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); + $('#container').addClass('alarming'); + } + + function playAlarm(audio) { + // ?mute=true disables alarms to testers. + if (querystring.mute != 'true') { + audio.play(); + } else { + showNotification('Alarm was muted (?mute=true)'); } + } + + function stopAlarm(isClient, silenceTime) { + alarmInProgress = false; + $('.bgButton').removeClass('urgent warning'); + d3.selectAll('audio.playing').each(function () { + var audio = this; + audio.pause(); + $(this).removeClass('playing'); + }); - function timeAgo(time) { - - var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 - , parts = {}; - - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; - - if (offset > DAY_IN_SECS * 7) { - parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { - parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { - parts.status = 'warn'; - } else { - parts.status = 'current'; - } - - return parts; + closeNotification(); + $('#container').removeClass('alarming'); + + // only emit ack if client invoke by button press + if (isClient) { + if (isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); + var alarm = getClientAlarm(currentAlarmType); + alarm.lastAckTime = Date.now(); + alarm.silenceTime = silenceTime; + console.info('time ago alarm (' + currentAlarmType + ', not acking to server'); + } else { + socket.emit('ack', currentAlarmType || 'alarm', silenceTime); + } + } + brushed(false); + } + + function timeAgo(time) { + + var now = Date.now() + , offset = time == -1 ? -1 : (now - time) / 1000 + , parts = {}; + + if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; + else if (offset == -1) parts = { label: 'time ago' }; + else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; + else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; + else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; + else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + else parts = { value: 'long ago' }; + + if (offset > DAY_IN_SECS * 7) { + parts.status = 'warn'; + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + parts.status = 'urgent'; + } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + parts.status = 'warn'; + } else { + parts.status = 'current'; } - function displayTreatmentBG(treatment) { + return parts; - function calcBGByTime(time) { - var withBGs = _.filter(data, function(d) { - return d.y && d.type == 'sgv'; - }); + } - var beforeTreatment = _.findLast(withBGs, function (d) { - return d.date.getTime() <= time; - }); - var afterTreatment = _.find(withBGs, function (d) { - return d.date.getTime() >= time; - }); + function displayTreatmentBG(treatment) { - var calcedBG = 0; - if (beforeTreatment && afterTreatment) { - calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; - } else if (beforeTreatment) { - calcedBG = Number(beforeTreatment.y); - } else if (afterTreatment) { - calcedBG = Number(afterTreatment.y); - } + function calcBGByTime(time) { + var withBGs = _.filter(data, function(d) { + return d.y && d.type == 'sgv'; + }); + + var beforeTreatment = _.findLast(withBGs, function (d) { + return d.date.getTime() <= time; + }); + var afterTreatment = _.find(withBGs, function (d) { + return d.date.getTime() >= time; + }); - return calcedBG || 400; + var calcedBG = 0; + if (beforeTreatment && afterTreatment) { + calcedBG = (Number(beforeTreatment.y) + Number(afterTreatment.y)) / 2; + } else if (beforeTreatment) { + calcedBG = Number(beforeTreatment.y); + } else if (afterTreatment) { + calcedBG = Number(afterTreatment.y); } - var treatmentGlucose = null; + return calcedBG || 400; + } - if (treatment.glucose && isNaN(treatment.glucose)) { - console.warn('found an invalid glucose value', treatment); - } else { - if (treatment.glucose && treatment.units && browserSettings.units) { - if (treatment.units != browserSettings.units) { - console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); - if (treatment.units == 'mmol') { - //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) - } else { - //BG is in mg/dl and display in mmol - treatmentGlucose = scaleBg(treatment.glucose); - } + var treatmentGlucose = null; + + if (treatment.glucose && isNaN(treatment.glucose)) { + console.warn('found an invalid glucose value', treatment); + } else { + if (treatment.glucose && treatment.units && browserSettings.units) { + if (treatment.units != browserSettings.units) { + console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); + if (treatment.units == 'mmol') { + //BG is in mmol and display in mg/dl + treatmentGlucose = Math.round(treatment.glucose * 18) } else { - treatmentGlucose = treatment.glucose; + //BG is in mg/dl and display in mmol + treatmentGlucose = scaleBg(treatment.glucose); } - } else if (treatment.glucose) { - //no units, assume everything is the same - console.warn('found a glucose value without any units, maybe from an old version?', treatment); + } else { treatmentGlucose = treatment.glucose; } + } else if (treatment.glucose) { + //no units, assume everything is the same + console.warn('found a glucose value without any units, maybe from an old version?', treatment); + treatmentGlucose = treatment.glucose; } - - return treatmentGlucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //draw a compact visualization of a treatment (carbs, insulin) - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function drawTreatment(treatment, scale, showValues) { + return treatmentGlucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); + } - if (!treatment.carbs && !treatment.insulin) return; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + //draw a compact visualization of a treatment (carbs, insulin) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function drawTreatment(treatment, scale, showValues) { - // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + if (!treatment.carbs && !treatment.insulin) return; - var CR = treatment.CR || 20; - var carbs = treatment.carbs || CR; - var insulin = treatment.insulin || 1; + // don't render the treatment if it's not visible + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; - var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, - R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, - R3 = R2 + 8 / scale; + var CR = treatment.CR || 20; + var carbs = treatment.carbs || CR; + var insulin = treatment.insulin || 1; - if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn("Bad Data: Found isNaN value in treatment", treatment); - return; - } + var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, + R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, + R3 = R2 + 8 / scale; - var arc_data = [ - { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, - { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } - ]; - - arc_data[0].outlineOnly = !treatment.carbs; - arc_data[2].outlineOnly = !treatment.insulin; - - if (treatment.carbs > 0) arc_data[1].element = Math.round(treatment.carbs) + ' g'; - if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; - - var arc = d3.svg.arc() - .innerRadius(function (d) { return 5 * d.inner; }) - .outerRadius(function (d) { return 5 * d.outer; }) - .endAngle(function (d) { return d.start; }) - .startAngle(function (d) { return d.end; }); - - var treatmentDots = focus.selectAll('treatment-dot') - .data(arc_data) - .enter() - .append('g') - .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') - .on('mouseover', function () { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + - (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + - (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? 'Notes: ' + treatment.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - var arcs = treatmentDots.append('path') - .attr('class', 'path') - .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) - .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) - .attr('stroke', function (d) { return d.color; }) - .attr('id', function (d, i) { return 's' + i; }) - .attr('d', arc); - - - // labels for carbs and insulin - if (showValues) { - var label = treatmentDots.append('g') - .attr('class', 'path') - .attr('id', 'label') - .style('fill', 'white'); - label.append('text') - .style('font-size', 40 / scale) - .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') - .attr('text-anchor', 'middle') - .attr('dy', '.35em') - .attr('transform', function (d) { - d.outerRadius = d.outerRadius * 2.1; - d.innerRadius = d.outerRadius * 2.1; - return 'translate(' + arc.centroid(d) + ')'; - }) - .text(function (d) { return d.element; }); - } + if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { + console.warn("Bad Data: Found isNaN value in treatment", treatment); + return; } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // function to predict - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function predictAR(actual, lookback) { - var ONE_MINUTE = 60 * 1000; - var FIVE_MINUTES = 5 * ONE_MINUTE; - var predicted = []; - var BG_REF = scaleBg(140); - var BG_MIN = scaleBg(36); - var BG_MAX = scaleBg(400); - - function roundByUnits(value) { - if (browserSettings.units == 'mmol') { - return value.toFixed(1); - } else { - return Math.round(value); - } - } + var arc_data = [ + { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, + { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } + ]; + + arc_data[0].outlineOnly = !treatment.carbs; + arc_data[2].outlineOnly = !treatment.insulin; + + if (treatment.carbs > 0) arc_data[1].element = Math.round(treatment.carbs) + ' g'; + if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + + var arc = d3.svg.arc() + .innerRadius(function (d) { return 5 * d.inner; }) + .outerRadius(function (d) { return 5 * d.outer; }) + .endAngle(function (d) { return d.start; }) + .startAngle(function (d) { return d.end; }); + + var treatmentDots = focus.selectAll('treatment-dot') + .data(arc_data) + .enter() + .append('g') + .attr('transform', 'translate(' + xScale(treatment.created_at.getTime()) + ', ' + yScale(treatment.displayBG) + ')') + .on('mouseover', function () { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('Time: ' + formatTime(treatment.created_at) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + + (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + + (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? 'Notes: ' + treatment.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + var arcs = treatmentDots.append('path') + .attr('class', 'path') + .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) + .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) + .attr('stroke', function (d) { return d.color; }) + .attr('id', function (d, i) { return 's' + i; }) + .attr('d', arc); + + + // labels for carbs and insulin + if (showValues) { + var label = treatmentDots.append('g') + .attr('class', 'path') + .attr('id', 'label') + .style('fill', 'white'); + label.append('text') + .style('font-size', 40 / scale) + .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', function (d) { + d.outerRadius = d.outerRadius * 2.1; + d.innerRadius = d.outerRadius * 2.1; + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(function (d) { return d.element; }); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // function to predict + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function predictAR(actual, lookback) { + var ONE_MINUTE = 60 * 1000; + var FIVE_MINUTES = 5 * ONE_MINUTE; + var predicted = []; + var BG_REF = scaleBg(140); + var BG_MIN = scaleBg(36); + var BG_MAX = scaleBg(400); + + function roundByUnits(value) { + if (browserSettings.units == 'mmol') { + return value.toFixed(1); + } else { + return Math.round(value); + } + } - // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) - var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; - // these are modified to make the cone much blunter - //var CONE = [0.030, 0.060, 0.090, 0.120, 0.140, 0.150, 0.160, 0.170, 0.180, 0.185, 0.190, 0.195, 0.200]; - // for testing - //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - if (actual.length < lookback+1) { - var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; - } else { - var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; - // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval - var lookbackSgvChange = actual[lookback].sgv-actual[0].sgv; - var fiveMinAgoSgv = actual[lookback].sgv - lookbackSgvChange/elapsedMins*5; - y = [Math.log(fiveMinAgoSgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - /* - if (elapsedMins < lookback * 5.1) { - y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - } else { - y = [Math.log(actual[lookback].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; - } - */ - } - var AR = [-0.723, 1.716]; - var dt = actual[lookback].date.getTime(); - var predictedColor = 'blue'; - if (browserSettings.theme == 'colors') { - predictedColor = 'cyan'; - } - for (var i = 0; i < CONE.length; i++) { - y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; - dt = dt + FIVE_MINUTES; - // Add 2000 ms so not same point as SG - predicted[i * 2] = { - date: new Date(dt + 2000), - sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), - color: predictedColor - }; - // Add 4000 ms so not same point as SG - predicted[i * 2 + 1] = { - date: new Date(dt + 4000), - sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), - color: predictedColor - }; - predicted.forEach(function (d) { - d.type = 'forecast'; - if (d.sgv < BG_MIN) - d.color = 'transparent'; - }) - } - return predicted; + // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) + var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; + // these are modified to make the cone much blunter + //var CONE = [0.030, 0.060, 0.090, 0.120, 0.140, 0.150, 0.160, 0.170, 0.180, 0.185, 0.190, 0.195, 0.200]; + // for testing + //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + if (actual.length < lookback+1) { + var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; + } else { + var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; + // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval + var lookbackSgvChange = actual[lookback].sgv-actual[0].sgv; + var fiveMinAgoSgv = actual[lookback].sgv - lookbackSgvChange/elapsedMins*5; + y = [Math.log(fiveMinAgoSgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + /* + if (elapsedMins < lookback * 5.1) { + y = [Math.log(actual[0].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + } else { + y = [Math.log(actual[lookback].sgv / BG_REF), Math.log(actual[lookback].sgv / BG_REF)]; + } + */ + } + var AR = [-0.723, 1.716]; + var dt = actual[lookback].date.getTime(); + var predictedColor = 'blue'; + if (browserSettings.theme == 'colors') { + predictedColor = 'cyan'; } + for (var i = 0; i < CONE.length; i++) { + y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; + dt = dt + FIVE_MINUTES; + // Add 2000 ms so not same point as SG + predicted[i * 2] = { + date: new Date(dt + 2000), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), + color: predictedColor + }; + // Add 4000 ms so not same point as SG + predicted[i * 2 + 1] = { + date: new Date(dt + 4000), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), + color: predictedColor + }; + predicted.forEach(function (d) { + d.type = 'forecast'; + if (d.sgv < BG_MIN) + d.color = 'transparent'; + }) + } + return predicted; + } - function updateClock() { - updateClockDisplay(); - var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(updateClock,interval); - - updateTimeAgo(); - - // Dim the screen by reducing the opacity when at nighttime - if (browserSettings.nightMode) { - var dateTime = new Date(); - if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { - $('body').css({ 'opacity': opacity.NIGHT }); - } else { - $('body').css({ 'opacity': opacity.DAY }); - } - } + function updateClock() { + updateClockDisplay(); + var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; + setTimeout(updateClock,interval); + + updateTimeAgo(); + + // Dim the screen by reducing the opacity when at nighttime + if (browserSettings.nightMode) { + var dateTime = new Date(); + if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + $('body').css({ 'opacity': opacity.NIGHT }); + } else { + $('body').css({ 'opacity': opacity.DAY }); + } + } + } + + function updateClockDisplay() { + if (inRetroMode()) return; + now = Date.now(); + var dateTime = new Date(now); + $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); + } + + function getClientAlarm(type) { + var alarm = clientAlarms[type]; + if (!alarm) { + alarm = { type: type }; + clientAlarms[type] = alarm; } + return alarm; + } + + function isTimeAgoAlarmType(alarmType) { + return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + } + + function checkTimeAgoAlarm(ago) { + var level = ago.status + , alarm = getClientAlarm(level + 'TimeAgo'); + + if (!alarmingNow() && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + currentAlarmType = alarm.type; + console.info('generating timeAgoAlarm', alarm.type); + $('#container').addClass('alarming-timeago'); + if (level == 'warn') { + generateAlarm(alarmSound); + } else { + generateAlarm(urgentAlarmSound); + } + } + } + + function updateTimeAgo() { + var lastEntry = $('#lastEntry') + , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 + , ago = timeAgo(time) + , retroMode = inRetroMode(); - function updateClockDisplay() { - if (inRetroMode()) return; - now = Date.now(); - var dateTime = new Date(now); - $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); + + if (ago.status !== 'current') { + updateTitle(); } - function getClientAlarm(type) { - var alarm = clientAlarms[type]; - if (!alarm) { - alarm = { type: type }; - clientAlarms[type] = alarm; - } - return alarm; + if ( + (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { + checkTimeAgoAlarm(ago); } - function isTimeAgoAlarmType(alarmType) { - return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + $('#container').removeClass('alarming-timeago'); + stopAlarm(true, ONE_MIN_IN_MS); } - function checkTimeAgoAlarm(ago) { - var level = ago.status - , alarm = getClientAlarm(level + 'TimeAgo'); - - if (!alarmingNow() && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { - currentAlarmType = alarm.type; - console.info('generating timeAgoAlarm', alarm.type); - $('#container').addClass('alarming-timeago'); - if (level == 'warn') { - generateAlarm(alarmSound); - } else { - generateAlarm(urgentAlarmSound); - } - } + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); } - function updateTimeAgo() { - var lastEntry = $('#lastEntry') - , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) - , retroMode = inRetroMode(); + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + + function init() { + + jqWindow = $(window); + + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + + // Tick Values + if (browserSettings.units == 'mmol') { + tickValues = [ + 2.0 + , Math.round(scaleBg(app.thresholds.bg_low)) + , Math.round(scaleBg(app.thresholds.bg_target_bottom)) + , 6.0 + , Math.round(scaleBg(app.thresholds.bg_target_top)) + , Math.round(scaleBg(app.thresholds.bg_high)) + , 22.0 + ]; + } else { + tickValues = [ + 40 + , app.thresholds.bg_low + , app.thresholds.bg_target_bottom + , 120 + , app.thresholds.bg_target_top + , app.thresholds.bg_high + , 400 + ]; + } - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); + futureOpacity = d3.scale.linear( ) + .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) + .range([0.8, 0.1]); - if (ago.status !== 'current') { - updateTitle(); - } + // create svg and g to contain the chart contents + charts = d3.select('#chartContainer').append('svg') + .append('g') + .attr('class', 'chartContainer') + .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); - if ( - (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { - checkTimeAgoAlarm(ago); - } + focus = charts.append('g'); - if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { - $('#container').removeClass('alarming-timeago'); - stopAlarm(true, ONE_MIN_IN_MS); - } + // create the x axis container + focus.append('g') + .attr('class', 'x axis'); - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } + // create the y axis container + focus.append('g') + .attr('class', 'y axis'); - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } + context = charts.append('g'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + //updateBrushToNow and updateChart are both _.debounced + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); } - function init() { - - jqWindow = $(window); - - tooltip = d3.select('body').append('div') - .attr('class', 'tooltip') - .style('opacity', 0); - - // Tick Values - if (browserSettings.units == 'mmol') { - tickValues = [ - 2.0 - , Math.round(scaleBg(app.thresholds.bg_low)) - , Math.round(scaleBg(app.thresholds.bg_target_bottom)) - , 6.0 - , Math.round(scaleBg(app.thresholds.bg_target_top)) - , Math.round(scaleBg(app.thresholds.bg_high)) - , 22.0 - ]; - } else { - tickValues = [ - 40 - , app.thresholds.bg_low - , app.thresholds.bg_target_bottom - , 120 - , app.thresholds.bg_target_top - , app.thresholds.bg_high - , 400 - ]; - } + function visibilityChanged() { + var prevHidden = documentHidden; + documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); - futureOpacity = d3.scale.linear( ) - .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) - .range([0.8, 0.1]); + if (prevHidden && !documentHidden) { + console.info('Document now visible, updating - ' + (new Date())); + refreshChart(true); + } + } - // create svg and g to contain the chart contents - charts = d3.select('#chartContainer').append('svg') - .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + window.onresize = refreshChart; - focus = charts.append('g'); + document.addEventListener('webkitvisibilitychange', visibilityChanged); - // create the x axis container - focus.append('g') - .attr('class', 'x axis'); - // create the y axis container - focus.append('g') - .attr('class', 'y axis'); + updateClock(); - context = charts.append('g'); + var silenceDropdown = new Dropdown('.dropdown-menu'); - // create the x axis container - context.append('g') - .attr('class', 'x axis'); + $('.bgButton').click(function (e) { + if (alarmingNow()) silenceDropdown.open(e); + }); - // create the y axis container - context.append('g') - .attr('class', 'y axis'); - - //updateBrushToNow and updateChart are both _.debounced - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - } + $('#silenceBtn').find('a').click(function (e) { + stopAlarm(true, $(this).data('snooze-time')); + e.preventDefault(); + }); - function visibilityChanged() { - var prevHidden = documentHidden; - documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); + $('.focus-range li').click(function(e) { + var li = $(e.target); + $('.focus-range li').removeClass('selected'); + li.addClass('selected'); + var hours = Number(li.data('hours')); + foucusRangeMS = hours * 60 * 60 * 1000; + refreshChart(); + }); - if (prevHidden && !documentHidden) { - console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); - } - } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Client-side code to connect to server and handle incoming data + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket = io.connect(); - window.onresize = refreshChart; + function recordAlreadyStored(array,record) { + var l = array.length -1; + for (var i = 0; i <= l; i++) { + var oldRecord = array[i]; + if (record.x == oldRecord.x) return true; + } + } - document.addEventListener('webkitvisibilitychange', visibilityChanged); + socket.on('dataUpdate', function receivedSGV(d) { + if (!d) return; - updateClock(); + // SGV - var silenceDropdown = new Dropdown('.dropdown-menu'); + if (d.sgvs) { - $('.bgButton').click(function (e) { - if (alarmingNow()) silenceDropdown.open(e); - }); + if (!d.delta) { + // replace all locally stored SGV data + console.log('Replacing all local sgv records'); + SGVdata = d.sgvs; + } else { + var diff = nsArrayDiff(SGVdata,d.sgvs); + console.log('SGV data updated with', diff.length, 'new records'); + SGVdata = SGVdata.concat(diff); + } - $('#silenceBtn').find('a').click(function (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); + SGVdata.sort(function(a, b) { + return a.x - b.x; }); - $('.focus-range li').click(function(e) { - var li = $(e.target); - $('.focus-range li').removeClass('selected'); - li.addClass('selected'); - var hours = Number(li.data('hours')); - foucusRangeMS = hours * 60 * 60 * 1000; - refreshChart(); - }); + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Client-side code to connect to server and handle incoming data - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket = io.connect(); - - function recordAlreadyStored(array,record) { - var l = array.length -1; - for (var i = 0; i <= l; i++) { - var oldRecord = array[i]; - if (record.x == oldRecord.x) return true; - } - } - - socket.on('dataUpdate', function receivedSGV(d) { - - if (!d) return; - - // SGV - - if (d.sgvs) { - - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.sgvs; + console.log('Total SGV data size', SGVdata.length); + + // profile, calibration and device status + + if (d.profiles) profile = d.profiles[0]; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; + + var temp1 = [ ]; + if (cal && isRawBGEnabled()) { + temp1 = SGVdata.map(function (entry) { + var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; + if (rawBg > 0) { + return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; } else { - var diff = nsArrayDiff(SGVdata,d.sgvs); - console.log('SGV data updated with', diff.length, 'new records'); - SGVdata = SGVdata.concat(diff); + return null; } - - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); - - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; + }).filter(function(entry) { return entry != null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + + if (d.predicted) { + data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + } + + //Add MBG's also, pretend they are MBG's + if (d.mbgs) { + if (!d.delta) { + // replace all locally stored MBG data + console.log('Replacing all local MBG records'); + MBGdata = d.mbgs; + } else { + var diff = nsArrayDiff(MBGdata,d.mbgs); + console.log('MBG data updated with', diff.length, 'new records'); + MBGdata = MBGdata.concat(diff); } - - console.log('Total SGV data size', SGVdata.length); - - // profile, calibration and device status - - if (d.profiles) profile = d.profiles[0]; - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; - - var temp1 = [ ]; - if (cal && isRawBGEnabled()) { - temp1 = SGVdata.map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry != null; }); - } - var temp2 = SGVdata.map(function (obj) { - return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - - if (d.predicted) { - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); - } - - //Add MBG's also, pretend they are MBG's - if (d.mbgs) { - if (!d.delta) { - // replace all locally stored MBG data - console.log('Replacing all local MBG records'); - MBGdata = d.mbgs; - } else { - var diff = nsArrayDiff(MBGdata,d.mbgs); - console.log('MBG data updated with', diff.length, 'new records'); - MBGdata = MBGdata.concat(diff); - } - } - - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; - }); - - // Update treatment data with new if delta - - if (d.treatments) { - if (!d.delta) { - treatments = d.treatments; - } else { - var newTreatments = nsArrayDiff(treatments,d.treatments); - console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newTreatments); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - } - - console.log('Total treatment data size', treatments.length); - - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - //cache the displayBG for each treatment in DISPLAY_UNITS - d.displayBG = displayTreatmentBG(d); - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } + } - }); + data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Alarms and Text handling - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket.on('connect', function () { - console.log('Client connected to server.') - }); + data.forEach(function (d) { + if (d.y < 39) + d.color = 'transparent'; + }); + // Update treatment data with new if delta - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a HIGH we can only check if it's >= the bottom of the target - function isAlarmForHigh() { - return latestSGV.y >= app.thresholds.bg_target_bottom; + if (d.treatments) { + if (!d.delta) { + treatments = d.treatments; + } else { + var newTreatments = nsArrayDiff(treatments,d.treatments); + console.log('treatment data updated with', newTreatments.length, 'new records'); + treatments = treatments.concat(newTreatments); + treatments.sort(function(a, b) { + return a.x - b.x; + }); } + } - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a LOW we can only check if it's <= the top of the target - function isAlarmForLow() { - return latestSGV.y <= app.thresholds.bg_target_top; - } + console.log('Total treatment data size', treatments.length); - socket.on('alarm', function () { - console.info('alarm received from server'); - var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); - if (enabled) { - console.log('Alarm raised!'); - currentAlarmType = 'alarm'; - generateAlarm(alarmSound); - } else { - console.info('alarm was disabled locally', latestSGV.y, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('urgent_alarm', function () { - console.info('urgent alarm received from server'); - var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); - if (enabled) { - console.log('Urgent alarm raised!'); - currentAlarmType = 'urgent_alarm'; - generateAlarm(urgentAlarmSound); - } else { - console.info('urgent alarm was disabled locally', latestSGV.y, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('clear_alarm', function () { - if (alarmInProgress) { - console.log('clearing alarm'); - stopAlarm(); - } - }); + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + //cache the displayBG for each treatment in DISPLAY_UNITS + d.displayBG = displayTreatmentBG(d); + }); + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } - $('#testAlarms').click(function(event) { - d3.selectAll('.audio.alarms audio').each(function () { - var audio = this; - playAlarm(audio); - setTimeout(function() { - audio.pause(); - }, 4000); - }); - event.preventDefault(); - }); + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Alarms and Text handling + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket.on('connect', function () { + console.log('Client connected to server.') + }); + + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a HIGH we can only check if it's >= the bottom of the target + function isAlarmForHigh() { + return latestSGV.y >= app.thresholds.bg_target_bottom; } - $.ajax('/api/v1/status.json', { - success: function (xhr) { - app = { name: xhr.name - , version: xhr.version - , head: xhr.head - , apiEnabled: xhr.apiEnabled - , enabledOptions: xhr.enabledOptions || '' - , thresholds: xhr.thresholds - , alarm_types: xhr.alarm_types - , units: xhr.units - , careportalEnabled: xhr.careportalEnabled - , defaults: xhr.defaults - }; - } - }).done(function() { - $('.appName').text(app.name); - $('.version').text(app.version); - $('.head').text(app.head); - if (app.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.init(app); - browserSettings = getBrowserSettings(browserStorage); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - init(); + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a LOW we can only check if it's <= the top of the target + function isAlarmForLow() { + return latestSGV.y <= app.thresholds.bg_target_top; + } + + socket.on('alarm', function () { + console.info('alarm received from server'); + var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); + if (enabled) { + console.log('Alarm raised!'); + currentAlarmType = 'alarm'; + generateAlarm(alarmSound); + } else { + console.info('alarm was disabled locally', latestSGV.y, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('urgent_alarm', function () { + console.info('urgent alarm received from server'); + var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); + if (enabled) { + console.log('Urgent alarm raised!'); + currentAlarmType = 'urgent_alarm'; + generateAlarm(urgentAlarmSound); + } else { + console.info('urgent alarm was disabled locally', latestSGV.y, browserSettings); + } + brushInProgress = false; + updateChart(false); }); + socket.on('clear_alarm', function () { + if (alarmInProgress) { + console.log('clearing alarm'); + stopAlarm(); + } + }); + + + $('#testAlarms').click(function(event) { + d3.selectAll('.audio.alarms audio').each(function () { + var audio = this; + playAlarm(audio); + setTimeout(function() { + audio.pause(); + }, 4000); + }); + event.preventDefault(); + }); + } + + $.ajax('/api/v1/status.json', { + success: function (xhr) { + app = { name: xhr.name + , version: xhr.version + , head: xhr.head + , apiEnabled: xhr.apiEnabled + , enabledOptions: xhr.enabledOptions || '' + , thresholds: xhr.thresholds + , alarm_types: xhr.alarm_types + , units: xhr.units + , careportalEnabled: xhr.careportalEnabled + , defaults: xhr.defaults + }; + } + }).done(function() { + $('.appName').text(app.name); + $('.version').text(app.version); + $('.head').text(app.head); + if (app.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(app.careportalEnabled); + Nightscout.plugins.init(app); + browserSettings = getBrowserSettings(browserStorage); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); + }); })(); From 4f66f47959185c4d78a669a51a8dea39c0805b62 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 01:30:23 +0300 Subject: [PATCH 075/661] Changed to using Iodash _.cloneDeep() --- lib/websocket.js | 55 ++---------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 079d2ba8960..e322c1eb0a5 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,7 @@ 'use strict'; var ar2 = require('./plugins/ar2')(); +var _ = require('lodash'); function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -12,58 +13,6 @@ function nsArrayDiff(oldArray, newArray) { return result; } -function clone(src) { - function mixin(dest, source, copyFunc) { - var name, s, i, empty = {}; - for(name in source){ - // the (!(name in empty) || empty[name] !== s) condition avoids copying properties in "source" - // inherited from Object.prototype. For example, if dest has a custom toString() method, - // don't overwrite it with the toString() method that source inherited from Object.prototype - s = source[name]; - if(!(name in dest) || (dest[name] !== s && (!(name in empty) || empty[name] !== s))){ - dest[name] = copyFunc ? copyFunc(s) : s; - } - } - return dest; - } - - if(!src || typeof src != "object" || Object.prototype.toString.call(src) === "[object Function]"){ - // null, undefined, any non-object, or function - return src; // anything - } - if(src.nodeType && "cloneNode" in src){ - // DOM Node - return src.cloneNode(true); // Node - } - if(src instanceof Date){ - // Date - return new Date(src.getTime()); // Date - } - if(src instanceof RegExp){ - // RegExp - return new RegExp(src); // RegExp - } - var r, i, l; - if(src instanceof Array){ - // array - r = []; - for(i = 0, l = src.length; i < l; ++i){ - if(i in src){ - r.push(clone(src[i])); - } - } - // we don't clone functions for performance reasons - // }else if(d.isFunction(src)){ - // // function - // r = function(){ return src.apply(this, arguments); }; - }else{ - // generic objects - r = src.constructor ? new src.constructor() : {}; - } - return mixin(r, src, clone); - -} - function uniq(a) { var seen = {}; return a.filter(function(item) { @@ -222,7 +171,7 @@ function init (server) { } else { console.log('delta calculation indicates no new data is present'); } } - patientData = clone(d); + patientData = _.cloneDeep(d); patientData.predicted = forecast.predicted; var emitAlarmType = null; From 02c47ee8a4f75aa2aa67eab9a31ed9d37a52796c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 15:55:26 -0700 Subject: [PATCH 076/661] a little clean up --- lib/websocket.js | 104 ++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index e322c1eb0a5..1d5e7422291 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -6,21 +6,13 @@ var _ = require('lodash'); function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } var result = []; l = newArray.length; - for (var i = 0; i < l; i++) { if (!seen.hasOwnProperty(newArray[i].x)) { result.push(newArray[i]); console.log('delta data found'); } }; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } return result; } -function uniq(a) { - var seen = {}; - return a.filter(function(item) { - return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); - }); -} - - function sort (values) { values.sort(function sorter (a, b) { return a.x - b.x; @@ -34,7 +26,7 @@ function init (server) { } var FORTY_MINUTES = 2400000; - + var lastUpdated = 0; var patientData = {}; var patientDataUpdate = {}; @@ -160,7 +152,7 @@ function init (server) { if (lastSGV) { var forecast = ar2.forecast(env, ctx); - if (patientData.sgvs) { + if (patientData.sgvs) { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; @@ -173,7 +165,7 @@ function init (server) { patientData = _.cloneDeep(d); patientData.predicted = forecast.predicted; - + var emitAlarmType = null; if (env.alarm_types.indexOf('simple') > -1) { @@ -215,72 +207,72 @@ function init (server) { }; function calculateDelta(d) { - + var delta = {'delta': true}; var changesFound = false; - + // if there's no updates done so far, just return the full set - if (!patientData.sgvs) return d; - + if (!patientData.sgvs) return d; + console.log('patientData.sgvs last record time', patientData.sgvs[patientData.sgvs.length-1].x); console.log('d.sgvslast record time', d.sgvs[d.sgvs.length-1].x); - - var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); - - if (sgvDelta.length > 0) { + + var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); + + if (sgvDelta.length > 0) { console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - }; - + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + } + var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); - - if (treatmentDelta.length > 0) { + + if (treatmentDelta.length > 0) { console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - }; + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + } var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); - - if (mbgsDelta.length > 0) { + + if (mbgsDelta.length > 0) { console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - }; + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + } var calsDelta = nsArrayDiff(patientData.cals,d.cals); - - if (calsDelta.length > 0) { + + if (calsDelta.length > 0) { console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - }; + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + } if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = d.devicestatus; - }; + changesFound = true; + delta.devicestatus = d.devicestatus; + } if (JSON.stringify(patientData.profiles) != JSON.stringify(d.profiles)) { console.log('profile changes found'); - changesFound = true; - delta.profiles = d.profiles; - }; + changesFound = true; + delta.profiles = d.profiles; + } - if (changesFound) { - console.log('changes found'); + if (changesFound) { + console.log('changes found'); return delta; - } - return d; - + } + return d; + } - + start( ); configure( ); listeners( ); From 125ff5bc498382749f49eabb89672d1d2775aca9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 16:06:06 -0700 Subject: [PATCH 077/661] fix issue with cloneDeep and mongo ObjectId's --- lib/websocket.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 1d5e7422291..729615c47d2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -2,6 +2,7 @@ var ar2 = require('./plugins/ar2')(); var _ = require('lodash'); +var ObjectID = require('mongodb').ObjectID; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -163,7 +164,13 @@ function init (server) { } else { console.log('delta calculation indicates no new data is present'); } } - patientData = _.cloneDeep(d); + //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 + patientData = _.cloneDeep(d, function (value) { + if (value instanceof ObjectID) { + return value.toString(); + } + }); + patientData.predicted = forecast.predicted; var emitAlarmType = null; From e78a8cb1353dedb402b38eb1e5f51831f294bfd0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 22:47:48 -0700 Subject: [PATCH 078/661] less debouncing --- static/js/client.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fbb8fabfc61..4130f43f3db 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -306,7 +306,7 @@ function nsArrayDiff(oldArray, newArray) { } // clears the current user brush and resets to the current real time data - var updateBrushToNow = _.debounce(function updateBrushToNow(skipBrushing) { + function updateBrushToNow(skipBrushing) { // get current time range var dataRange = d3.extent(data, dateFn); @@ -323,7 +323,7 @@ function nsArrayDiff(oldArray, newArray) { // clear user brush tracking brushInProgress = false; } - }, DEBOUNCE_MS); + } function brushStarted() { // update the opacity of the context data points to brush extent @@ -372,7 +372,7 @@ function nsArrayDiff(oldArray, newArray) { return errorDisplay; } - var brushed = _.debounce(function brushed(skipTimer) { + function brushed(skipTimer) { if (!skipTimer) { // set a timer to reset focus chart to real-time data @@ -779,7 +779,7 @@ function nsArrayDiff(oldArray, newArray) { }); treatCircles.attr('clip-path', 'url(#clip)'); - }, DEBOUNCE_MS); + }; // called for initial update and updates for resize var updateChart = _.debounce(function updateChart(init) { @@ -1618,7 +1618,7 @@ function nsArrayDiff(oldArray, newArray) { context.append('g') .attr('class', 'y axis'); - //updateBrushToNow and updateChart are both _.debounced + //updateChart is _.debounce'd function refreshChart(updateToNow) { if (updateToNow) { updateBrushToNow(); From 6fdcf6fcc34a58f410dbf6c62fd1aa973a8da7d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 6 Jun 2015 23:41:04 -0700 Subject: [PATCH 079/661] stop sending unused predicted values, still a kludge but not wasting bytes --- lib/websocket.js | 3 --- static/js/client.js | 9 +++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 729615c47d2..565886168a8 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -157,7 +157,6 @@ function init (server) { var delta = calculateDelta(d); if (delta.delta) { patientDataUpdate = delta; - patientDataUpdate.predicted = forecast.predicted; console.log('patientData full size', JSON.stringify(patientData).length,'bytes'); if (delta.sgvs) console.log('patientData update size', JSON.stringify(patientDataUpdate).length,'bytes'); emitData(); @@ -171,8 +170,6 @@ function init (server) { } }); - patientData.predicted = forecast.predicted; - var emitAlarmType = null; if (env.alarm_types.indexOf('simple') > -1) { diff --git a/static/js/client.js b/static/js/client.js index 4130f43f3db..197cd0b5a60 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1730,13 +1730,14 @@ function nsArrayDiff(oldArray, newArray) { data = data.concat(temp1, temp2); // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't have to be sent and can be fixed by using xScale.domain([x0,x1]) function with + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - - if (d.predicted) { - data = data.concat(d.predicted.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'none', type: 'server-forecast'} })); + for (var i = 1; i <= 12; i++) { + data.push({ + date: new Date(Date.now() + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + }); } //Add MBG's also, pretend they are MBG's From 669f48175d03ebae78f5b352c3f1568567cc6e5e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:02:36 -0700 Subject: [PATCH 080/661] less fake padding points, and timed to last data --- static/js/client.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 197cd0b5a60..585cb6c31d9 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1734,9 +1734,10 @@ function nsArrayDiff(oldArray, newArray) { // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - for (var i = 1; i <= 12; i++) { + var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + for (var i = 1; i <= 8; i++) { data.push({ - date: new Date(Date.now() + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); } From 811324edfa451c4aa028367e8760fde1fcf2ee7f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:43:36 -0700 Subject: [PATCH 081/661] add new event when data is loaded and use that for updating websockets instead of tick --- lib/bootevent.js | 9 +++++++++ server.js | 9 ++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index f3d6a450265..cdf7d751e7b 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -34,6 +34,15 @@ function boot (env) { ctx.data = require('./data')(env, ctx); + ctx.heartbeat.on('tick', function(tick) { + console.info('tick', tick.now); + ctx.data.update(function dataUpdated () { + ctx.heartbeat.emit('data-loaded'); + }); + }); + + ctx.heartbeat.uptime( ); + next( ); }) ; diff --git a/server.js b/server.js index 134493f6124..678bb5f5c19 100644 --- a/server.js +++ b/server.js @@ -60,15 +60,10 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(server); - ctx.heartbeat.on('tick', function(tick) { - console.info('tick', tick.now); - ctx.data.update(function dataUpdated () { - websocket.processData(env, ctx); - }); + ctx.heartbeat.on('data-loaded', function() { + websocket.processData(env, ctx); }); - ctx.heartbeat.uptime( ); - }) ; From 9a7d3e87807343e8fb173ba9de766b6716149a67 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 00:59:23 -0700 Subject: [PATCH 082/661] 1 less fake point so position of now doesn't shift when scrolling back --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 585cb6c31d9..687b9ef0183 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1735,7 +1735,7 @@ function nsArrayDiff(oldArray, newArray) { // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - for (var i = 1; i <= 8; i++) { + for (var i = 1; i <= 7; i++) { data.push({ date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); From f52e80a0eaf66be5fa8dac6aab6c925b2e7bd993 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 01:54:15 -0700 Subject: [PATCH 083/661] refactored websocket alarms into notifications.js --- lib/bootevent.js | 5 ++ lib/notifications.js | 124 +++++++++++++++++++++++++++++++++++++++++++ lib/websocket.js | 122 +++++------------------------------------- server.js | 8 ++- 4 files changed, 149 insertions(+), 110 deletions(-) create mode 100644 lib/notifications.js diff --git a/lib/bootevent.js b/lib/bootevent.js index cdf7d751e7b..3005a1f70ec 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -33,6 +33,7 @@ function boot (env) { ctx.heartbeat = require('./ticker')(env, ctx); ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); ctx.heartbeat.on('tick', function(tick) { console.info('tick', tick.now); @@ -41,6 +42,10 @@ function boot (env) { }); }); + ctx.heartbeat.on('data-loaded', function() { + ctx.notifications.processData(env, ctx); + }); + ctx.heartbeat.uptime( ); next( ); diff --git a/lib/notifications.js b/lib/notifications.js new file mode 100644 index 00000000000..e4ad3e66645 --- /dev/null +++ b/lib/notifications.js @@ -0,0 +1,124 @@ +'use strict'; + +var ar2 = require('./plugins/ar2')(); + +var THIRTY_MINUTES = 30 * 60 * 1000; + +var Alarm = function(_typeName, _threshold) { + this.typeName = _typeName; + this.silenceTime = THIRTY_MINUTES; + this.lastAckTime = 0; + this.threshold = _threshold; +}; + +// list of alarms with their thresholds +var alarms = { + 'alarm' : new Alarm('Regular', 0.05), + 'urgent_alarm': new Alarm('Urgent', 0.10) +}; + +function init (env, ctx) { + function notifications () { + return notifications; + } + + //should only be used when auto acking the alarms after going back in range or when an error corrects + //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //since this wasn't ack'd by a user action + function autoAckAlarms() { + var sendClear = false; + for (var type in alarms) { + if (alarms.hasOwnProperty(type)) { + var alarm = alarms[type]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + type); + notifications.ack(type, 1); + sendClear = true; + } + } + } + if (sendClear) { + ctx.heartbeat.emit('notification', {clear: true}); + console.info('emitted notification clear'); + } + } + + function emitAlarm (type) { + var alarm = alarms[type]; + if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { + ctx.heartbeat.emit('notification', {type: type}); + alarm.lastEmitTime = ctx.data.lastUpdated; + console.info('emitted notification:' + type); + } else { + console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); + } + } + + notifications.processData = function processData ( ) { + var d = ctx.data; + + console.log('running notifications.processData'); + + var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + + if (lastSGV) { + var forecast = ar2.forecast(env, ctx); + + var emitAlarmType = null; + + if (env.alarm_types.indexOf('simple') > -1) { + if (lastSGV > env.thresholds.bg_high) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); + } else if (lastSGV > env.thresholds.bg_target_top) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_low) { + emitAlarmType = 'urgent_alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); + } else if (lastSGV < env.thresholds.bg_target_bottom) { + emitAlarmType = 'alarm'; + console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); + } + } + + if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { + if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { + emitAlarmType = 'urgent_alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); + } else if (forecast.avgLoss > alarms['alarm'].threshold) { + emitAlarmType = 'alarm'; + console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); + } + } + + if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { + emitAlarmType = 'urgent_alarm'; + } + + if (emitAlarmType) { + emitAlarm(emitAlarmType); + } else { + autoAckAlarms(); + } + } + }; + + notifications.ack = function ack (type, time) { + var alarm = alarms[type]; + if (alarm) { + console.info('Got an ack for: ', alarm, 'time: ' + time); + } else { + console.warn('Got an ack for an unknown alarm time'); + return; + } + alarm.lastAckTime = new Date().getTime(); + alarm.silenceTime = time ? time : THIRTY_MINUTES; + delete alarm.lastEmitTime; + + }; + + return notifications(); +} + +module.exports = init; \ No newline at end of file diff --git a/lib/websocket.js b/lib/websocket.js index 565886168a8..941209b220b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,5 @@ 'use strict'; -var ar2 = require('./plugins/ar2')(); var _ = require('lodash'); var ObjectID = require('mongodb').ObjectID; @@ -20,14 +19,12 @@ function sort (values) { }); } -function init (server) { +function init (env, ctx, server) { function websocket ( ) { return websocket; } - var FORTY_MINUTES = 2400000; - var lastUpdated = 0; var patientData = {}; var patientDataUpdate = {}; @@ -67,13 +64,11 @@ function init (server) { io.sockets.socket(socket.id).emit('dataUpdate',patientData); io.sockets.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - ackAlarm(alarmType, silenceTime); + ctx.notifications.ack(alarmType, silenceTime); if (alarmType == 'urgent_alarm') { //also clean normal alarm so we don't get a double alarm as BG comes back into range - ackAlarm('alarm', silenceTime); + ctx.notifications.ack('alarm', silenceTime); } - io.sockets.emit('clear_alarm', true); - console.log('alarm cleared'); }); socket.on('disconnect', function () { io.sockets.emit('clients', --watchers); @@ -81,78 +76,16 @@ function init (server) { }); } - /////////////////////////////////////////////////// - // data handling functions - /////////////////////////////////////////////////// - - var Alarm = function(_typeName, _threshold) { - this.typeName = _typeName; - this.silenceTime = FORTY_MINUTES; - this.lastAckTime = 0; - this.threshold = _threshold; - }; - -// list of alarms with their thresholds - var alarms = { - 'alarm' : new Alarm('Regular', 0.05), - 'urgent_alarm': new Alarm('Urgent', 0.10) - }; - - function ackAlarm(alarmType, silenceTime) { - var alarm = alarms[alarmType]; - if (!alarm) { - console.warn('Got an ack for an unknown alarm time'); - return; - } - alarm.lastAckTime = new Date().getTime(); - alarm.silenceTime = silenceTime ? silenceTime : FORTY_MINUTES; - delete alarm.lastEmitTime; - } - - //should only be used when auto acking the alarms after going back in range or when an error corrects - //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes - //since this wasn't ack'd by a user action - function autoAckAlarms() { - var sendClear = false; - for (var alarmType in alarms) { - if (alarms.hasOwnProperty(alarmType)) { - var alarm = alarms[alarmType]; - if (alarm.lastEmitTime) { - console.info('auto acking ' + alarmType); - ackAlarm(alarmType, 1); - sendClear = true; - } - } - } - if (sendClear) { - io.sockets.emit('clear_alarm', true); - console.info('emitted clear_alarm to all clients'); - } - } - - function emitAlarm (alarmType) { - var alarm = alarms[alarmType]; - if (lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - io.sockets.emit(alarmType); - alarm.lastEmitTime = lastUpdated; - console.info('emitted ' + alarmType + ' to all clients'); - } else { - console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); - } - } - - websocket.processData = function processData (env, ctx) { + websocket.processData = function processData ( ) { var d = ctx.data; lastUpdated = d.lastUpdated; - console.log('running websocket.loadData'); + console.log('running websocket.processData'); var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; if (lastSGV) { - var forecast = ar2.forecast(env, ctx); - if (patientData.sgvs) { var delta = calculateDelta(d); if (delta.delta) { @@ -170,43 +103,16 @@ function init (server) { } }); - var emitAlarmType = null; - - if (env.alarm_types.indexOf('simple') > -1) { - if (lastSGV > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); - } else if (lastSGV > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); - } - } - - if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { - if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); - } else if (forecast.avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); - } - } - - if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { - emitAlarmType = 'urgent_alarm'; - } + } + }; - if (emitAlarmType) { - emitAlarm(emitAlarmType); - } else { - autoAckAlarms(); - } + websocket.emitNotification = function emitNotification (info) { + if (info.clear) { + io.sockets.emit('clear_alarm', true); + console.info('emitted clear_alarm to all clients'); + } else if (info.type) { + io.sockets.emit(info.type); + console.info('emitted ' + info.type + ' to all clients'); } }; diff --git a/server.js b/server.js index 678bb5f5c19..6a2025b8722 100644 --- a/server.js +++ b/server.js @@ -58,10 +58,14 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// - var websocket = require('./lib/websocket')(server); + var websocket = require('./lib/websocket')(env, ctx, server); ctx.heartbeat.on('data-loaded', function() { - websocket.processData(env, ctx); + websocket.processData(); + }); + + ctx.heartbeat.on('notification', function(info) { + websocket.emitNotification(info); }); }) From d0eefc18ed15d2b1906d3726d44c3df4e82e5aa3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:02:35 +0300 Subject: [PATCH 084/661] moved timeAgo to nsUtils nsUtils is now in the bundle, mapped to Nightscout namespace cannula age now considers the point of time being visualized in the timeline small prettification on client.js --- bundle/bundle.source.js | 1 + lib/nsutils.js | 37 ++++++++++++++++++++++++- lib/plugins/cannulaage.js | 4 +-- static/js/client.js | 57 +++++++++------------------------------ 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index c37363c2c4a..6e9beca2f1e 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,6 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), + nsUtils: require('../lib/nsUtils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/lib/nsutils.js b/lib/nsutils.js index c96f859575b..ad2a4fc3c1b 100644 --- a/lib/nsutils.js +++ b/lib/nsutils.js @@ -6,7 +6,11 @@ function init() { return nsutils; } - nsutils.toFixed = function toFixed(value) { + var MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400; + + nsutils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -15,6 +19,37 @@ function init() { } }; + nsutils.timeAgo = function timeAgo(time) { + + var now = Date.now() + , offset = time == -1 ? -1 : (now - time) / 1000 + , parts = {}; + + if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; + else if (offset == -1) parts = { label: 'time ago' }; + else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; + else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; + else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; + else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + else parts = { value: 'long ago' }; + + if (offset > DAY_IN_SECS * 7) { + parts.status = 'warn'; + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + parts.status = 'urgent'; + } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + parts.status = 'warn'; + } else { + parts.status = 'current'; + } + + return parts; + + } + + return nsutils(); } diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 636fe0d5892..a90cb7135d4 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -17,14 +17,14 @@ function init() { var found = false; var treatmentDate = null; var message = ''; - + for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { var treatment = this.env.treatments[t]; if (treatment.eventType == "Site Change") { treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(new Date() - treatmentDate) / 36e5); + var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); if (!found) { found = true; diff --git a/static/js/client.js b/static/js/client.js index 687b9ef0183..c4d0cf5a167 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,10 +1,12 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; +var nsUtils = Nightscout.nsUtils; + function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true }; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } var result = []; l = newArray.length; for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } @@ -20,9 +22,7 @@ function nsArrayDiff(oldArray, newArray) { , UPDATE_TRANS_MS = 750 // milliseconds , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 - , SIX_MINS_IN_MS = 360000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWELVE_HOURS_MS = 12 * 60 * 60 * 1000 , TWENTY_FIVE_MINS_IN_MS = 1500000 , THIRTY_MINS_IN_MS = 1800000 , SIXTY_MINS_IN_MS = 3600000 @@ -32,11 +32,7 @@ function nsArrayDiff(oldArray, newArray) { , FORMAT_TIME_12_SCALE = '%-I %p' , FORMAT_TIME_24_SCALE = '%H' , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800 - , MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400 - , WEEK_IN_SECS = 604800; + , WIDTH_BIG_DOTS = 800; var socket , isInitialData = false @@ -138,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + , ago = nsUtils.timeAgo(time); var bg_title = browserSettings.customTitle || ''; @@ -413,10 +409,11 @@ function nsArrayDiff(oldArray, newArray) { function updateCurrentSGV(entry) { - var value = entry.y - , time = new Date(entry.x).getTime() - , ago = timeAgo(time) - , isCurrent = ago.status === 'current'; + var value, time, ago, isCurrent; + value = entry.y; + time = new Date(entry.x).getTime(); + ago = nsUtils.timeAgo(time); + isCurrent = ago.status === 'current'; if (value == 9) { currentBG.text(''); @@ -779,7 +776,7 @@ function nsArrayDiff(oldArray, newArray) { }); treatCircles.attr('clip-path', 'url(#clip)'); - }; + } // called for initial update and updates for resize var updateChart = _.debounce(function updateChart(init) { @@ -1218,36 +1215,6 @@ function nsArrayDiff(oldArray, newArray) { brushed(false); } - function timeAgo(time) { - - var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 - , parts = {}; - - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; - - if (offset > DAY_IN_SECS * 7) { - parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { - parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { - parts.status = 'warn'; - } else { - parts.status = 'current'; - } - - return parts; - - } - function displayTreatmentBG(treatment) { function calcBGByTime(time) { @@ -1523,7 +1490,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) + , ago = nsUtils.timeAgo(time) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); From 241fcbeb849e93692b1f3b3d2686da62338ed746 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:22:52 +0300 Subject: [PATCH 085/661] Clean up and remove code duplication from the data merging code upon delta --- static/js/client.js | 79 ++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 51 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index c4d0cf5a167..9faf47ed104 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1643,41 +1643,47 @@ function nsArrayDiff(oldArray, newArray) { } } + function mergeDataUpdate(isDelta, cachedDataArray, reveivedDataArray) { + + // If there was no delta data, just return the original data + if (!reveivedDataArray) return cachedDataArray; + + // If this is not a delta update, replace all data + if (!isDelta) return reveivedDataArray; + + // If this is delta, calculate the difference, merge and sort + var diff = nsArrayDiff(cachedDataArray,receivedDataArray); + return cachedDataArray.concat(diff).sort(function(a, b) { + return a.x - b.x; + }); + } + socket.on('dataUpdate', function receivedSGV(d) { if (!d) return; - // SGV + // Calculate the diff to existing data and replace as needed - if (d.sgvs) { + SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); + MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); + treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + if (d.profiles) profile = d.profiles[0]; + if (d.cals) cal = d.cals[d.cals.length-1]; + if (d.devicestatus) devicestatusData = d.devicestatus; - if (!d.delta) { - // replace all locally stored SGV data - console.log('Replacing all local sgv records'); - SGVdata = d.sgvs; - } else { - var diff = nsArrayDiff(SGVdata,d.sgvs); - console.log('SGV data updated with', diff.length, 'new records'); - SGVdata = SGVdata.concat(diff); - } + // Do some reporting on the console + console.log('Total SGV data size', SGVdata.length); + console.log('Total treatment data size', treatments.length); - SGVdata.sort(function(a, b) { - return a.x - b.x; - }); + // Post processing after data is in + if (d.sgvs) { // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) latestUpdateTime = Date.now(); latestSGV = SGVdata[SGVdata.length - 1]; prevSGV = SGVdata[SGVdata.length - 2]; } - console.log('Total SGV data size', SGVdata.length); - - // profile, calibration and device status - - if (d.profiles) profile = d.profiles[0]; - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; var temp1 = [ ]; if (cal && isRawBGEnabled()) { @@ -1708,19 +1714,6 @@ function nsArrayDiff(oldArray, newArray) { }); } - //Add MBG's also, pretend they are MBG's - if (d.mbgs) { - if (!d.delta) { - // replace all locally stored MBG data - console.log('Replacing all local MBG records'); - MBGdata = d.mbgs; - } else { - var diff = nsArrayDiff(MBGdata,d.mbgs); - console.log('MBG data updated with', diff.length, 'new records'); - MBGdata = MBGdata.concat(diff); - } - } - data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); data.forEach(function (d) { @@ -1728,23 +1721,7 @@ function nsArrayDiff(oldArray, newArray) { d.color = 'transparent'; }); - // Update treatment data with new if delta - - if (d.treatments) { - if (!d.delta) { - treatments = d.treatments; - } else { - var newTreatments = nsArrayDiff(treatments,d.treatments); - console.log('treatment data updated with', newTreatments.length, 'new records'); - treatments = treatments.concat(newTreatments); - treatments.sort(function(a, b) { - return a.x - b.x; - }); - } - } - - console.log('Total treatment data size', treatments.length); - + // OPTIMIZATION: precalculate treatment location in timeline treatments.forEach(function (d) { d.created_at = new Date(d.created_at); //cache the displayBG for each treatment in DISPLAY_UNITS From f053920e28f6a76761b865999b5c8ebbdd6aa981 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 13:24:07 +0300 Subject: [PATCH 086/661] Reformat the plugin code --- lib/plugins/cannulaage.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index a90cb7135d4..30877d05e67 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -17,7 +17,7 @@ function init() { var found = false; var treatmentDate = null; var message = ''; - + for (var t in this.env.treatments) { if (this.env.treatments.hasOwnProperty(t)) { var treatment = this.env.treatments[t]; @@ -32,7 +32,9 @@ function init() { } else { if (hours < age) { age = hours; - if (treatment.notes) { message = treatment.notes; } + if (treatment.notes) { + message = treatment.notes; + } } } } From 27c12fd5e6a2d4b123d10c6ca7873fa06150cedd Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 7 Jun 2015 11:41:44 -0700 Subject: [PATCH 087/661] wait an extra 5mins before going into retro mode --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 9faf47ed104..5af00f2319b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -344,7 +344,7 @@ function nsArrayDiff(oldArray, newArray) { var time = brush.extent()[1].getTime(); - return !alarmingNow() && time - THIRTY_MINS_IN_MS < now; + return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; } function errorCodeToDisplay(errorCode) { From 117f38e6deae54be657547f91eeaa5049fbc052d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 7 Jun 2015 22:42:46 +0300 Subject: [PATCH 088/661] Socket.io upgraded to 1.3.5 --- lib/websocket.js | 28 ++++++++++------------------ package.json | 2 +- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 941209b220b..6502dbeaf2b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -33,7 +33,9 @@ function init (env, ctx, server) { var watchers = 0; function start ( ) { - io = require('socket.io').listen(server, { + io = require('socket.io')({ + 'transports': ['xhr-polling'], 'log level': 0 + }).listen(server, { //these only effect the socket.io.js file that is sent to the client, but better than nothing 'browser client minification': true, 'browser client etag': true, @@ -44,25 +46,16 @@ function init (env, ctx, server) { function emitData ( ) { if (patientData.cals) { console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.sockets.emit('dataUpdate', patientDataUpdate); + io.emit('dataUpdate', patientDataUpdate); } } - function configure ( ) { - // reduce logging - io.set('log level', 0); - - //TODO: make websockets support an option - io.configure(function () { - io.set('transports', ['xhr-polling']); - }); - } - function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - io.sockets.socket(socket.id).emit('dataUpdate',patientData); - io.sockets.emit('clients', ++watchers); + socket.emit('dataUpdate',patientData); + + io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ctx.notifications.ack(alarmType, silenceTime); if (alarmType == 'urgent_alarm') { @@ -71,7 +64,7 @@ function init (env, ctx, server) { } }); socket.on('disconnect', function () { - io.sockets.emit('clients', --watchers); + io.emit('clients', --watchers); }); }); } @@ -108,10 +101,10 @@ function init (env, ctx, server) { websocket.emitNotification = function emitNotification (info) { if (info.clear) { - io.sockets.emit('clear_alarm', true); + io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); } else if (info.type) { - io.sockets.emit(info.type); + io.emit(info.type); console.info('emitted ' + info.type + ' to all clients'); } }; @@ -184,7 +177,6 @@ function init (env, ctx, server) { } start( ); - configure( ); listeners( ); return websocket(); diff --git a/package.json b/package.json index 605edb83489..f3714e54091 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "mqtt": "~0.3.11", "pushover-notifications": "0.2.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "socket.io": "^0.9.17" + "socket.io": "^1.3.5" }, "devDependencies": { "istanbul": "~0.3.5", From ff787b8a45dd39b220242e7438d1703f9cba25f4 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 8 Jun 2015 00:06:52 +0300 Subject: [PATCH 089/661] Doh, typo in received data delta processing. No idea how I didn't notice before. --- static/js/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 5af00f2319b..edd5017f636 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1643,13 +1643,13 @@ function nsArrayDiff(oldArray, newArray) { } } - function mergeDataUpdate(isDelta, cachedDataArray, reveivedDataArray) { + function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { // If there was no delta data, just return the original data - if (!reveivedDataArray) return cachedDataArray; + if (!receivedDataArray) return cachedDataArray; // If this is not a delta update, replace all data - if (!isDelta) return reveivedDataArray; + if (!isDelta) return receivedDataArray; // If this is delta, calculate the difference, merge and sort var diff = nsArrayDiff(cachedDataArray,receivedDataArray); From 1e10ff4f0a2b6025df9e72bdd73376f655e5bf5b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 00:00:06 -0700 Subject: [PATCH 090/661] require is case-sensitive on Linux, some renaming --- bundle/bundle.source.js | 2 +- static/js/client.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 6e9beca2f1e..0cfbc5ca00a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,7 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), - nsUtils: require('../lib/nsUtils')(), + utils: require('../lib/nsutils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/static/js/client.js b/static/js/client.js index edd5017f636..2f76a9d2bad 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,7 +1,7 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -var nsUtils = Nightscout.nsUtils; +var timeAgo = Nightscout.utils.timeAgo; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -134,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = nsUtils.timeAgo(time); + , ago = timeAgo(time); var bg_title = browserSettings.customTitle || ''; @@ -412,7 +412,7 @@ function nsArrayDiff(oldArray, newArray) { var value, time, ago, isCurrent; value = entry.y; time = new Date(entry.x).getTime(); - ago = nsUtils.timeAgo(time); + ago = timeAgo(time); isCurrent = ago.status === 'current'; if (value == 9) { @@ -1490,7 +1490,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = nsUtils.timeAgo(time) + , ago = timeAgo(time) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); From 69a09a520b51310f228683ee9bd8a4b2abc64239 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 00:08:20 -0700 Subject: [PATCH 091/661] stop rounding cals sent via /pebble --- lib/pebble.js | 6 +++--- tests/pebble.test.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 4a2910dbc52..74b918ebd98 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -101,9 +101,9 @@ function pebble (req, res) { results.forEach(function (element) { if (element) { calData.push({ - slope: Math.round(element.slope) - , intercept: Math.round(element.intercept) - , scale: Math.round(element.scale) + slope: element.slope + , intercept: element.intercept + , scale: element.scale }); } }); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 030be48a222..04e0d6a4900 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -202,8 +202,8 @@ describe('Pebble Endpoint with Raw', function ( ) { res.body.cals.length.should.equal(1); var cal = res.body.cals[0]; - cal.slope.should.equal(896); - cal.intercept.should.equal(34281); + cal.slope.toFixed(3).should.equal('895.857'); + cal.intercept.toFixed(3).should.equal('34281.069'); cal.scale.should.equal(1); done( ); }); From ebdcf596e9e29d621fe6e8b23f2063d9d14c9aad Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 8 Jun 2015 18:32:43 +0300 Subject: [PATCH 092/661] Corrected bug regarding cannula age if the timeline contained a cannula switch --- lib/plugins/cannulaage.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 30877d05e67..20d3fc731b9 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -24,16 +24,18 @@ function init() { if (treatment.eventType == "Site Change") { treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); + if (treatmentDate <= this.env.time) { + var hours = Math.round((this.env.time - treatmentDate) / 36e5); - if (!found) { - found = true; - age = hours; - } else { - if (hours < age) { + if (!found) { + found = true; age = hours; - if (treatment.notes) { - message = treatment.notes; + } else { + if (hours < age) { + age = hours; + if (treatment.notes) { + message = treatment.notes; + } } } } From 3e5c0b8da03722009ad475bd22f65c7c904e4179 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 22:25:47 -0700 Subject: [PATCH 093/661] nsutils => utils --- bundle/bundle.source.js | 2 +- lib/plugins/iob.js | 4 ++-- lib/{nsutils.js => utils.js} | 10 +++++----- static/js/client.js | 10 ++++++---- 4 files changed, 14 insertions(+), 12 deletions(-) rename lib/{nsutils.js => utils.js} (91%) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 0cfbc5ca00a..a3780cdd42d 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,7 +5,7 @@ window.Nightscout = { units: require('../lib/units')(), - utils: require('../lib/nsutils')(), + utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), plugins: require('../lib/plugins/')().registerClientDefaults() }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 8202565502b..9b57b865b2d 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -2,7 +2,7 @@ var _ = require('lodash') , moment = require('moment') - , nsutils = require('../nsutils')(); + , utils = require('../utils')(); function init() { @@ -48,7 +48,7 @@ function init() { return { iob: totalIOB, - display: nsutils.toFixed(totalIOB), + display: utils.toFixed(totalIOB), activity: totalActivity, lastBolus: lastBolus }; diff --git a/lib/nsutils.js b/lib/utils.js similarity index 91% rename from lib/nsutils.js rename to lib/utils.js index ad2a4fc3c1b..e2240758c09 100644 --- a/lib/nsutils.js +++ b/lib/utils.js @@ -2,15 +2,15 @@ function init() { - function nsutils() { - return nsutils; + function utils() { + return utils; } var MINUTE_IN_SECS = 60 , HOUR_IN_SECS = 3600 , DAY_IN_SECS = 86400; - nsutils.toFixed = function toFixed(value) { + utils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -19,7 +19,7 @@ function init() { } }; - nsutils.timeAgo = function timeAgo(time) { + utils.timeAgo = function timeAgo(time) { var now = Date.now() , offset = time == -1 ? -1 : (now - time) / 1000 @@ -50,7 +50,7 @@ function init() { } - return nsutils(); + return utils(); } module.exports = init; \ No newline at end of file diff --git a/static/js/client.js b/static/js/client.js index 2f76a9d2bad..f43d3d81a56 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -969,10 +969,12 @@ function nsArrayDiff(oldArray, newArray) { .attr('transform', 'translate(0,' + chartHeight + ')') .call(xAxis2); - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); + if (clip) { + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + } // reset brush location context.select('.x.brush') From 709b143c2e1b10efce6776aa203c99211092e769 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 8 Jun 2015 23:28:59 -0700 Subject: [PATCH 094/661] moved delta calculation out of websockets.js --- lib/data.js | 99 +++++++++++++++++++++++++++++++++++- lib/utils.js | 11 ++-- lib/websocket.js | 129 +++++------------------------------------------ 3 files changed, 117 insertions(+), 122 deletions(-) diff --git a/lib/data.js b/lib/data.js index e158d01e168..ddecf300c0b 100644 --- a/lib/data.js +++ b/lib/data.js @@ -1,6 +1,9 @@ 'use strict'; +var _ = require('lodash'); var async = require('async'); +var utils = require('./utils')(); +var ObjectID = require('mongodb').ObjectID; function uniq(a) { var seen = {}; @@ -42,6 +45,16 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } + data.clone = function clone ( ) { + return _.cloneDeep(data, function (value) { + //special handling of mongo ObjectID's + //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 + if (value instanceof ObjectID) { + return value.toString(); + } + }); + }; + data.update = function update (done) { console.log('running data.update'); @@ -58,7 +71,6 @@ function init (env, ctx) { async.parallel({ entries: function (callback) { - var now = new Date(); var q = { find: {"date": {"$gte": earliest_data}} }; ctx.entries.list(q, function (err, results) { if (!err && results) { @@ -151,6 +163,91 @@ function init (env, ctx) { }; + data.calculateDelta = function calculateDelta (lastData) { + + var delta = {'delta': true}; + var changesFound = false; + + // if there's no updates done so far, just return the full set + if (!lastData.sgvs) return data; + + console.log('lastData.sgvs last record time', lastData.sgvs[lastData.sgvs.length-1].x); + console.log('d.sgvslast record time', data.sgvs[data.sgvs.length-1].x); + + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + + function sort (values) { + values.sort(function sorter (a, b) { + return a.x - b.x; + }); + } + + var sgvDelta = nsArrayDiff(lastData.sgvs, data.sgvs); + + if (sgvDelta.length > 0) { + console.log('sgv changes found'); + changesFound = true; + sort(sgvDelta); + delta.sgvs = sgvDelta; + } + + var treatmentDelta = nsArrayDiff(lastData.treatments, data.treatments); + + if (treatmentDelta.length > 0) { + console.log('treatment changes found'); + changesFound = true; + sort(treatmentDelta); + delta.treatments = treatmentDelta; + } + + var mbgsDelta = nsArrayDiff(lastData.mbgs, data.mbgs); + + if (mbgsDelta.length > 0) { + console.log('mbgs changes found'); + changesFound = true; + sort(mbgsDelta); + delta.mbgs = mbgsDelta; + } + + var calsDelta = nsArrayDiff(lastData.cals, data.cals); + + if (calsDelta.length > 0) { + console.log('cals changes found'); + changesFound = true; + sort(calsDelta); + delta.cals = calsDelta; + } + + if (JSON.stringify(lastData.devicestatus) != JSON.stringify(data.devicestatus)) { + console.log('devicestatus changes found'); + changesFound = true; + delta.devicestatus = data.devicestatus; + } + + if (JSON.stringify(lastData.profiles) != JSON.stringify(data.profiles)) { + console.log('profile changes found'); + changesFound = true; + delta.profiles = data.profiles; + } + + if (changesFound) { + console.log('changes found'); + return delta; + } + + return data; + + }; + + return data; } diff --git a/lib/utils.js b/lib/utils.js index e2240758c09..6bc24037801 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,11 +6,11 @@ function init() { return utils; } - var MINUTE_IN_SECS = 60 - , HOUR_IN_SECS = 3600 - , DAY_IN_SECS = 86400; + var MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400; - utils.toFixed = function toFixed(value) { + utils.toFixed = function toFixed(value) { if (value === 0) { return '0'; } else { @@ -47,8 +47,7 @@ function init() { return parts; - } - + }; return utils(); } diff --git a/lib/websocket.js b/lib/websocket.js index 6502dbeaf2b..fe09ac8dbbb 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,23 +1,7 @@ 'use strict'; var _ = require('lodash'); -var ObjectID = require('mongodb').ObjectID; - -function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; -} - -function sort (values) { - values.sort(function sorter (a, b) { - return a.x - b.x; - }); -} +var utils = require('./utils')(); function init (env, ctx, server) { @@ -25,12 +9,9 @@ function init (env, ctx, server) { return websocket; } - var lastUpdated = 0; - var patientData = {}; - var patientDataUpdate = {}; - var io; var watchers = 0; + var lastData = {}; function start ( ) { io = require('socket.io')({ @@ -43,18 +24,17 @@ function init (env, ctx, server) { }); } - function emitData ( ) { - if (patientData.cals) { - console.log('running websocket.emitData', lastUpdated, patientDataUpdate.recentsgvs && patientDataUpdate.sgvdataupdate.length); - io.emit('dataUpdate', patientDataUpdate); + function emitData (delta) { + if (lastData.cals) { + console.log('running websocket.emitData', ctx.data.lastUpdated, delta.recentsgvs && delta.sgvdataupdate.length); + io.emit('dataUpdate', delta); } } function listeners ( ) { io.sockets.on('connection', function (socket) { // send all data upon new connection - socket.emit('dataUpdate',patientData); - + socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { ctx.notifications.ack(alarmType, silenceTime); @@ -70,32 +50,18 @@ function init (env, ctx, server) { } websocket.processData = function processData ( ) { - - var d = ctx.data; - lastUpdated = d.lastUpdated; - console.log('running websocket.processData'); - - var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; - + var lastSGV = ctx.data.sgvs.length > 0 ? ctx.data.sgvs[ctx.data.sgvs.length - 1].y : null; if (lastSGV) { - if (patientData.sgvs) { - var delta = calculateDelta(d); + if (lastData.sgvs) { + var delta = ctx.data.calculateDelta(lastData); if (delta.delta) { - patientDataUpdate = delta; - console.log('patientData full size', JSON.stringify(patientData).length,'bytes'); - if (delta.sgvs) console.log('patientData update size', JSON.stringify(patientDataUpdate).length,'bytes'); - emitData(); + console.log('lastData full size', JSON.stringify(lastData).length,'bytes'); + if (delta.sgvs) console.log('patientData update size', JSON.stringify(delta).length,'bytes'); + emitData(delta); } else { console.log('delta calculation indicates no new data is present'); } } - - //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 - patientData = _.cloneDeep(d, function (value) { - if (value instanceof ObjectID) { - return value.toString(); - } - }); - + lastData = ctx.data.clone(); } }; @@ -109,73 +75,6 @@ function init (env, ctx, server) { } }; - function calculateDelta(d) { - - var delta = {'delta': true}; - var changesFound = false; - - // if there's no updates done so far, just return the full set - if (!patientData.sgvs) return d; - - console.log('patientData.sgvs last record time', patientData.sgvs[patientData.sgvs.length-1].x); - console.log('d.sgvslast record time', d.sgvs[d.sgvs.length-1].x); - - var sgvDelta = nsArrayDiff(patientData.sgvs,d.sgvs); - - if (sgvDelta.length > 0) { - console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - } - - var treatmentDelta = nsArrayDiff(patientData.treatments,d.treatments); - - if (treatmentDelta.length > 0) { - console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - } - - var mbgsDelta = nsArrayDiff(patientData.mbgs,d.mbgs); - - if (mbgsDelta.length > 0) { - console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - } - - var calsDelta = nsArrayDiff(patientData.cals,d.cals); - - if (calsDelta.length > 0) { - console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - } - - if (JSON.stringify(patientData.devicestatus) != JSON.stringify(d.devicestatus)) { - console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = d.devicestatus; - } - - if (JSON.stringify(patientData.profiles) != JSON.stringify(d.profiles)) { - console.log('profile changes found'); - changesFound = true; - delta.profiles = d.profiles; - } - - if (changesFound) { - console.log('changes found'); - return delta; - } - return d; - - } - start( ); listeners( ); From acfe73aed9b8025d0a527f40b52cac0e746d16ab Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:07:28 -0700 Subject: [PATCH 095/661] introduce sandbox (sbx) to expose state and ui to plugins also changed the way pluginBase is used, still needs some work --- bundle/bundle.source.js | 3 +- env.js | 3 + lib/plugins/boluswizardpreview.js | 42 +++++---- lib/plugins/cannulaage.js | 34 ++++--- lib/plugins/cob.js | 37 ++++---- lib/plugins/index.js | 30 +++---- lib/plugins/iob.js | 19 ++-- lib/plugins/pluginbase.js | 144 ++++++++++++++---------------- static/js/client.js | 37 +++----- 9 files changed, 175 insertions(+), 174 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index a3780cdd42d..81693b6e81a 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -7,7 +7,8 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), - plugins: require('../lib/plugins/')().registerClientDefaults() + plugins: require('../lib/plugins/')().registerClientDefaults(), + sandbox: require('../lib/sandbox')() }; console.info("Nightscout bundle ready", window.Nightscout); diff --git a/env.js b/env.js index c455e62bbba..d16ce6ef9c0 100644 --- a/env.js +++ b/env.js @@ -168,6 +168,9 @@ function config ( ) { // For pushing notifications to Pushover. env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); + if (env.pushover_api_token && env.pushover_user_key) { + env.alarm_types.push('pushover'); + } // TODO: clean up a bit // Some people prefer to use a json configuration file instead. diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index e330258b881..14b6392a802 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function init() { function bwp() { @@ -9,41 +11,49 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; - bwp.updateVisualisation = function updateVisualisation() { + bwp.updateVisualisation = function updateVisualisation (sbx) { + + var sgv = _.last(sbx.data.sgvs); + //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field + sgv = sgv && (sgv.y || sgv.sgv); - var sgv = this.env.sgv; + if (sgv == undefined || !sbx.properties.iob) return; + + var profile = sbx.data.profile; var bolusEstimate = 0.0; // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion - sgv = this.scaleBg(sgv); + sgv = sbx.pluginBase.scaleBg(sgv, sbx); + + var iob = sbx.properties.iob.iob; - var effect = this.iob.iob * this.profile.sens; + var effect = iob * profile.sens; var outcome = sgv - effect; var delta = 0; - if (outcome > this.profile.target_high) { - delta = outcome - this.profile.target_high; - bolusEstimate = delta / this.profile.sens; + if (outcome > profile.target_high) { + delta = outcome - profile.target_high; + bolusEstimate = delta / profile.sens; } - if (outcome < this.profile.target_low) { - delta = Math.abs(outcome - this.profile.target_low); - bolusEstimate = delta / this.profile.sens * -1; + if (outcome < profile.target_low) { + delta = Math.abs(outcome - profile.target_low); + bolusEstimate = delta / profile.sens * -1; } - bolusEstimate = this.roundInsulinForDisplayFormat(bolusEstimate); - outcome = this.roundBGToDisplayFormat(outcome); - var displayIOB = this.roundInsulinForDisplayFormat(this.iob.iob); + bolusEstimate = sbx.pluginBase.roundInsulinForDisplayFormat(bolusEstimate, sbx); + outcome = sbx.pluginBase.roundBGToDisplayFormat(outcome, sbx); + var displayIOB = sbx.pluginBase.roundInsulinForDisplayFormat(iob, sbx); // display text var info = [ {label: 'Insulin on Board', value: displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + this.roundBGToDisplayFormat(effect) + ' ' + this.getBGUnits()} - , {label: 'Expected outcome', value: outcome + ' ' + this.getBGUnits()} + , {label: 'Expected effect', value: '-' + sbx.pluginBase.roundBGToDisplayFormat(effect, sbx) + ' ' + sbx.pluginBase.getBGUnits(sbx)} + , {label: 'Expected outcome', value: outcome + ' ' + sbx.pluginBase.getBGUnits(sbx)} ]; - this.updatePillText(bolusEstimate + 'U', 'BWP', info); + sbx.pluginBase.updatePillText(bwp, bolusEstimate + 'U', 'BWP', info); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 30877d05e67..1d6e0c2400a 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -1,5 +1,6 @@ 'use strict'; +var _ = require('lodash'); var moment = require('moment'); function init() { @@ -11,40 +12,35 @@ function init() { cage.label = 'Cannula Age'; cage.pluginType = 'pill-minor'; - cage.updateVisualisation = function updateVisualisation() { - + cage.updateVisualisation = function updateVisualisation (sbx) { var age = 0; var found = false; var treatmentDate = null; var message = ''; - for (var t in this.env.treatments) { - if (this.env.treatments.hasOwnProperty(t)) { - var treatment = this.env.treatments[t]; - - if (treatment.eventType == "Site Change") { - treatmentDate = new Date(treatment.created_at); - var hours = Math.round(Math.abs(this.env.time - treatmentDate) / 36e5); + _.forEach(sbx.data.treatments, function eachTreatment (treatment) { + if (treatment.eventType == "Site Change") { + treatmentDate = new Date(treatment.created_at); + var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); - if (!found) { - found = true; + if (!found) { + found = true; + age = hours; + } else { + if (hours < age) { age = hours; - } else { - if (hours < age) { - age = hours; - if (treatment.notes) { - message = treatment.notes; - } + if (treatment.notes) { + message = treatment.notes; } } } } - } + }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; if (message != '') info.push({label: 'Notes:', value: message}); - this.updatePillText(age + 'h', 'CAGE', info); + sbx.pluginBase.updatePillText(cage, age + 'h', 'CAGE', info); }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index b7aad7fc673..eab827c483c 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -13,14 +13,17 @@ function init() { cob.label = 'Carbs-on-Board'; cob.pluginType = 'pill-minor'; - cob.getData = function getData() { - return cob.cobTotal(this.env.treatments, this.env.time); + + cob.setProperties = function setProperties(sbx) { + sbx.offerProperty('cob', function setCOB ( ) { + return cob.cobTotal(sbx.data.treatments, sbx.data.profile, sbx.time); + }); }; - cob.cobTotal = function cobTotal(treatments, time) { + cob.cobTotal = function cobTotal(treatments, profile, time) { var liverSensRatio = 1; - var sens = this.profile.sens; - var carbratio = this.profile.carbratio; + var sens = profile.sens; + var carbratio = profile.carbratio; var totalCOB = 0; var lastCarbs = null; if (!treatments) return {}; @@ -30,12 +33,12 @@ function init() { var isDecaying = 0; var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = this.profile.carbs_hr; + var carbs_hr = profile.carbs_hr; _.forEach(treatments, function eachTreatment(treatment) { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; - var cCalc = cob.cobCalc(treatment, lastDecayedBy, time); + var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; @@ -90,9 +93,9 @@ function init() { } }; - cob.cobCalc = function cobCalc(treatment, lastDecayedBy, time) { + cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { - var carbs_hr = this.profile.carbs_hr; + var carbs_hr = profile.carbs_hr; var delay = 20; var carbs_min = carbs_hr / 60; var isDecaying = 0; @@ -130,17 +133,21 @@ function init() { } }; - cob.updateVisualisation = function updateVisualisation() { - var displayCob = Math.round(this.env.cob.cob * 10) / 10; + cob.updateVisualisation = function updateVisualisation(sbx) { + + var prop = sbx.properties.cob.cob; + if (prop == undefined) return; + + var displayCob = Math.round(prop * 10) / 10; var info = null; - if (this.env.cob.lastCarbs) { - var when = moment(new Date(this.env.cob.lastCarbs.created_at)).format('lll'); - var amount = this.env.cob.lastCarbs.carbs + 'g'; + if (prop.lastCarbs) { + var when = moment(new Date(prop.lastCarbs.created_at)).format('lll'); + var amount = prop.lastCarbs.carbs + 'g'; info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] } - this.updatePillText(displayCob + " g", 'COB', info); + sbx.pluginBase.updatePillText(sbx, displayCob + " g", 'COB', info); }; return cob(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 29f3c4e082d..62905cc1a71 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -1,7 +1,6 @@ 'use strict'; -var _ = require('lodash') - , PluginBase = require('./pluginbase')(); // Define any shared functionality in this class +var _ = require('lodash'); function init() { @@ -12,6 +11,8 @@ function init() { return plugins; } + plugins.base = require('./pluginbase'); + plugins.registerServerDefaults = function registerServerDefaults() { plugins.register([ require('./pushnotify')() ]); return plugins; @@ -29,7 +30,6 @@ function init() { plugins.register = function register(all) { _.forEach(all, function eachPlugin(plugin) { - _.extend(plugin, PluginBase); allPlugins.push(plugin); }); }; @@ -59,31 +59,31 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.shownPlugins = function(clientSettings) { + plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return clientSettings && clientSettings.showPlugins && clientSettings.showPlugins.indexOf(plugin.name) > -1; + return sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1; }); }; - plugins.eachShownPlugins = function eachShownPlugins(clientSettings, f) { - _.forEach(plugins.shownPlugins(clientSettings), f); + plugins.eachShownPlugins = function eachShownPlugins(sbx, f) { + _.forEach(plugins.shownPlugins(sbx), f); }; - plugins.hasShownType = function hasShownType(pluginType, clientSettings) { - return _.find(plugins.shownPlugins(clientSettings), function findWithType(plugin) { + plugins.hasShownType = function hasShownType(pluginType, sbx) { + return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) { return plugin.pluginType == pluginType; }) != undefined; }; - plugins.setEnvs = function setEnvs(env) { - plugins.eachEnabledPlugin(function eachPlugin(plugin) { - plugin.setEnv(env); + plugins.setProperties = function setProperties(sbx) { + plugins.eachEnabledPlugin( function eachPlugin (plugin) { + plugin.setProperties && plugin.setProperties(sbx); }); }; - plugins.updateVisualisations = function updateVisualisations(clientSettings) { - plugins.eachShownPlugins(clientSettings, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(); + plugins.updateVisualisations = function updateVisualisations(sbx) { + plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { + plugin.updateVisualisation && plugin.updateVisualisation(sbx); }); }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 9b57b865b2d..30748e51cfd 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -13,8 +13,10 @@ function init() { iob.label = 'Insulin-on-Board'; iob.pluginType = 'pill-major'; - iob.getData = function getData() { - return iob.calcTotal(this.env.treatments, this.env.profile, this.env.time); + iob.setProperties = function setProperties(sbx) { + sbx.offerProperty('iob', function setIOB ( ) { + return iob.calcTotal(sbx.data.treatments, sbx.data.profile, sbx.time); + }); }; iob.calcTotal = function calcTotal(treatments, profile, time) { @@ -86,16 +88,19 @@ function init() { }; - iob.updateVisualisation = function updateVisualisation() { + iob.updateVisualisation = function updateVisualisation(sbx) { var info = null; - if (this.iob.lastBolus) { - var when = moment(new Date(this.iob.lastBolus.created_at)).format('lll'); - var amount = this.roundInsulinForDisplayFormat(Number(this.iob.lastBolus.insulin)) + 'U'; + var prop = sbx.properties.iob; + + if (prop && prop.lastBolus) { + var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); + var amount = sbx.pluginBase.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin), sbx) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - this.updatePillText(this.roundInsulinForDisplayFormat(this.iob.display) + 'U', 'IOB', info, true); + sbx.pluginBase.updatePillText(iob, sbx.pluginBase.roundInsulinForDisplayFormat(prop.display, sbx) + 'U', 'IOB', info); + }; return iob(); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index abedce5ffb1..09c342399c7 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,101 +2,91 @@ var _ = require('lodash'); -function setEnv(env) { - this.profile = env.profile; - this.majorPills = env.majorPills; - this.minorPills = env.minorPills; - this.iob = env.iob; - - // TODO: clean! - this.env = env; -} +function init (majorPills, minorPills, tooltip) { -function updatePillText(updatedText, label, info, major) { + function pluginBase ( ) { + return pluginBase; + } - var self = this; + pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { - var pillName = "span.pill." + this.name; + var pillName = "span.pill." + plugin.name; - var container = this.pluginType == 'pill-major' ? this.majorPills : this.minorPills; + var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; - var pill = container.find(pillName); + var pill = container.find(pillName); - if (!pill || pill.length == 0) { - pill = $(''); - container.append(pill); - } + if (!pill || pill.length == 0) { + pill = $(''); + container.append(pill); + } - pill.find('em').text(updatedText); + pill.find('em').text(updatedText); - if (info) { + if (info) { - var html = _.map(info, function mapInfo(i) { - return '' + i.label + ' ' + i.value; - }).join('
    \n'); + var html = _.map(info, function mapInfo (i) { + return '' + i.label + ' ' + i.value; + }).join('
    \n'); - pill.mouseover(function pillMouseover(event) { - self.env.tooltip.transition().duration(200).style('opacity', .9); - self.env.tooltip.html(html) - .style('left', (event.pageX) + 'px') - .style('top', (event.pageY + 15) + 'px'); - }); + pill.mouseover(function pillMouseover (event) { + tooltip.transition().duration(200).style('opacity', .9); + tooltip.html(html) + .style('left', (event.pageX) + 'px') + .style('top', (event.pageY + 15) + 'px'); + }); - pill.mouseout(function pillMouseout() { - self.env.tooltip.transition() - .duration(200) - .style('opacity', 0); - }); - } -} + pill.mouseout(function pillMouseout ( ) { + tooltip.transition() + .duration(200) + .style('opacity', 0); + }); + } + }; -function roundInsulinForDisplayFormat(iob, roundingStyle) { + pluginBase.roundInsulinForDisplayFormat = function roundInsulinForDisplayFormat (iob, sbx) { - if (iob == 0) return 0; + if (iob == 0) return 0; - if (roundingStyle === undefined) roundingStyle = 'generic'; - - if (roundingStyle == 'medtronic') { - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { denominator = 0.05; digits = 2;} - if (iob <= 0.5) { denominator = 0.025; digits = 3;} - return (Math.floor(iob / denominator) * denominator).toFixed(digits); - } - - return (Math.floor(iob / 0.01) * 0.01).toFixed(2); - -} + if (sbx.properties.roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (iob > 0.5 && iob < 1) { + denominator = 0.05; + digits = 2; + } + if (iob <= 0.5) { + denominator = 0.025; + digits = 3; + } + return (Math.floor(iob / denominator) * denominator).toFixed(digits); + } -function getBGUnits() { - if (browserSettings.units == 'mmol') return 'mmol/L'; - return "mg/dl"; -} + return (Math.floor(iob / 0.01) * 0.01).toFixed(2); -function roundBGToDisplayFormat(bg) { - if (browserSettings.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); -} + }; -function scaleBg(bg) { - if (browserSettings.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } -} + pluginBase.getBGUnits = function getBGUnits (sbx) { + if (sbx.units == 'mmol') return 'mmol/L'; + return "mg/dl"; + }; + + pluginBase.roundBGToDisplayFormat = function roundBGToDisplayFormat (bg, sbx) { + if (sbx.units == 'mmol') { + return Math.round(bg * 10) / 10; + } + return Math.round(bg); + }; -function PluginBase() { - return { - setEnv: setEnv, - scaleBg: scaleBg, - updatePillText: updatePillText, - roundBGToDisplayFormat: roundBGToDisplayFormat, - roundInsulinForDisplayFormat: roundInsulinForDisplayFormat, - getBGUnits: getBGUnits + pluginBase.scaleBg = function scaleBg (bg, sbx) { + if (sbx.units == 'mmol') { + return Nightscout.units.mgdlToMMOL(bg); + } else { + return bg; + } }; + + return pluginBase(); } -module.exports = PluginBase; +module.exports = init; diff --git a/static/js/client.js b/static/js/client.js index f43d3d81a56..97b950ed57b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -476,32 +476,21 @@ function nsArrayDiff(oldArray, newArray) { } - function updatePlugins(sgv, time) { - var env = {}; - env.profile = profile; - env.majorPills = majorPills; - env.minorPills = minorPills; - env.sgv = Number(sgv); - env.treatments = treatments; - env.time = time; - env.tooltip = tooltip; - - //all enabled plugins get a chance to add data, even if they aren't shown - Nightscout.plugins.eachEnabledPlugin(function updateEachPlugin(plugin) { - // Update the env through data provider plugins - plugin.setEnv(env); - - // check if the plugin implements processing data - if (plugin.getData) { - env[plugin.name] = plugin.getData(); - } + function updatePlugins(sgvs, time) { + + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); + + var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sgvs: sgvs + , treatments: treatments + , profile: profile }); - // update data for all the plugins, before updating visualisations - Nightscout.plugins.setEnvs(env); + //all enabled plugins get a chance to set properties, even if they aren't shown + Nightscout.plugins.setProperties(sbx); //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(browserSettings); + Nightscout.plugins.updateVisualisations(sbx); } // predict for retrospective data @@ -552,7 +541,7 @@ function nsArrayDiff(oldArray, newArray) { bgButton.removeClass('urgent warning inrange'); } - updatePlugins(focusPoint && focusPoint.y, retroTime); + updatePlugins(nowData, retroTime); $('#currentTime') .text(formatTime(retroTime, true)) @@ -587,7 +576,7 @@ function nsArrayDiff(oldArray, newArray) { updateBGDelta(prevSGV, latestSGV); - updatePlugins(latestSGV.y, nowDate); + updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); } From 86c57bcf59dee127f7217f922d59f75abc1f202a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:11:27 -0700 Subject: [PATCH 096/661] fixed some cob tests --- tests/cob.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/cob.test.js b/tests/cob.test.js index d2c2dddb121..db2cc9fbef3 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -5,7 +5,7 @@ var should = require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); - cob.profile = { + var profile = { sens: 95 , carbratio: 18 , carbs_hr: 30 @@ -24,9 +24,9 @@ describe('COB', function ( ) { } ]; - var after100 = cob.cobTotal(treatments, new Date("2015-05-29T02:03:49.827Z")); - var before10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:10.670Z")); - var after10 = cob.cobTotal(treatments, new Date("2015-05-29T03:45:11.670Z")); + var after100 = cob.cobTotal(treatments, profile, new Date("2015-05-29T02:03:49.827Z")); + var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); + var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); console.info('>>>>after100:', after100); console.info('>>>>before10:', before10); @@ -52,11 +52,11 @@ describe('COB', function ( ) { var later3 = new Date("2015-05-29T05:50:00.174Z"); var later4 = new Date("2015-05-29T06:50:00.174Z"); - var result1 = cob.cobTotal(treatments, rightAfterCorrection); - var result2 = cob.cobTotal(treatments, later1); - var result3 = cob.cobTotal(treatments, later2); - var result4 = cob.cobTotal(treatments, later3); - var result5 = cob.cobTotal(treatments, later4); + var result1 = cob.cobTotal(treatments, profile, rightAfterCorrection); + var result2 = cob.cobTotal(treatments, profile, later1); + var result3 = cob.cobTotal(treatments, profile, later2); + var result4 = cob.cobTotal(treatments, profile, later3); + var result5 = cob.cobTotal(treatments, profile, later4); result1.cob.should.equal(8); result2.cob.should.equal(6); From 055e6ad1066d07aac34e37f83dd30fe353f646c4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 02:12:15 -0700 Subject: [PATCH 097/661] almost lost the sandbox --- lib/sandbox.js | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/sandbox.js diff --git a/lib/sandbox.js b/lib/sandbox.js new file mode 100644 index 00000000000..af15c413a42 --- /dev/null +++ b/lib/sandbox.js @@ -0,0 +1,80 @@ +var utils = require('./utils'); + +function init ( ) { + var sbx = {}; + + function init() { + sbx.properties = []; + } + + /** + * Initialize the sandbox using server state + * + * @param env - .js + * @param ctx - created from bootevent + * @returns {{sbx}} + */ + sbx.serverInit = function serverInit(env, ctx) { + init(); + + sbx.time = Date.now(); + sbx.units = env.DISPLAY_UNITS; + sbx.defaults = env.defaults; + sbx.thresholds = env.thresholds; + sbx.alarm_types = env.alarm_types; + sbx.data = ctx.data.clone(); + + //Plugins will expect the right profile based on time + sbx.data.profile = data.profiles.length > 0 ? data.profiles[0] : undefined; + delete sbx.data.profiles; + + sbx.properties = []; + + return sbx; + }; + + /** + * Initialize the sandbox using client state + * + * @param app - app settings + * @param clientSettings - specific settings from the client, starting with the defaults + * @param time - could be a retro time + * @param pluginBase - used by visualization plugins to update the UI + * @param data - svgs, treatments, profile, etc + * @returns {{sbx}} + */ + sbx.clientInit = function clientInit(app, clientSettings, time, pluginBase, data) { + init(); + + sbx.units = clientSettings.units; + sbx.defaults = clientSettings; //TODO: strip out extra stuff + sbx.thresholds = app.thresholds; + sbx.alarm_types = clientSettings.alarm_types; + sbx.showPlugins = clientSettings.showPlugins; + sbx.time = time; + sbx.data = data; + sbx.pluginBase = pluginBase; + + return sbx; + }; + + /** + * Properties are immutable, first plugin to set it wins, plugins should be in the correct order + * + * @param name + * @param setter + */ + sbx.offerProperty = function offerProperty(name, setter) { + if (!sbx.properties.hasOwnProperty(name)) { + var value = setter(); + if (value) { + sbx.properties[name] = value; + } + } + }; + + return sbx; +} + +module.exports = init; + From bcd6b27ac576d0c74964df31d1b53ff4b5f7dd1e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 08:24:34 -0700 Subject: [PATCH 098/661] ctx.bus --- lib/bootevent.js | 10 +++++----- lib/{ticker.js => bus.js} | 13 ++++++++----- lib/notifications.js | 4 ++-- server.js | 4 ++-- 4 files changed, 17 insertions(+), 14 deletions(-) rename lib/{ticker.js => bus.js} (87%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 3005a1f70ec..34b63e81f26 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -30,23 +30,23 @@ function boot (env) { store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - ctx.heartbeat = require('./ticker')(env, ctx); + ctx.bus = require('./bus')(env, ctx); ctx.data = require('./data')(env, ctx); ctx.notifications = require('./notifications')(env, ctx); - ctx.heartbeat.on('tick', function(tick) { + ctx.bus.on('tick', function(tick) { console.info('tick', tick.now); ctx.data.update(function dataUpdated () { - ctx.heartbeat.emit('data-loaded'); + ctx.bus.emit('data-loaded'); }); }); - ctx.heartbeat.on('data-loaded', function() { + ctx.bus.on('data-loaded', function() { ctx.notifications.processData(env, ctx); }); - ctx.heartbeat.uptime( ); + ctx.bus.uptime( ); next( ); }) diff --git a/lib/ticker.js b/lib/bus.js similarity index 87% rename from lib/ticker.js rename to lib/bus.js index 0be1293f297..687f31cb6ee 100644 --- a/lib/ticker.js +++ b/lib/bus.js @@ -1,12 +1,13 @@ - -var es = require('event-stream'); var Stream = require('stream'); -function heartbeat (env, ctx) { +function init (env, ctx) { var beats = 0; var started = new Date( ); var id; var interval = env.HEARTBEAT || 20000; + + var stream = new Stream; + function ictus ( ) { var tick = { now: new Date( ) @@ -18,18 +19,20 @@ function heartbeat (env, ctx) { }; return tick; } + function repeat ( ) { stream.emit('tick', ictus( )); } + function ender ( ) { if (id) cancelInterval(id); stream.emit('end'); } - var stream = new Stream; + stream.readable = true; stream.uptime = repeat; id = setInterval(repeat, interval); return stream; } -module.exports = heartbeat; +module.exports = init; diff --git a/lib/notifications.js b/lib/notifications.js index e4ad3e66645..739e65e9d0e 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -38,7 +38,7 @@ function init (env, ctx) { } } if (sendClear) { - ctx.heartbeat.emit('notification', {clear: true}); + ctx.bus.emit('notification', {clear: true}); console.info('emitted notification clear'); } } @@ -46,7 +46,7 @@ function init (env, ctx) { function emitAlarm (type) { var alarm = alarms[type]; if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - ctx.heartbeat.emit('notification', {type: type}); + ctx.bus.emit('notification', {type: type}); alarm.lastEmitTime = ctx.data.lastUpdated; console.info('emitted notification:' + type); } else { diff --git a/server.js b/server.js index 6a2025b8722..1cf8bc1c8b8 100644 --- a/server.js +++ b/server.js @@ -60,11 +60,11 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(env, ctx, server); - ctx.heartbeat.on('data-loaded', function() { + ctx.bus.on('data-loaded', function() { websocket.processData(); }); - ctx.heartbeat.on('notification', function(info) { + ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); From 6637d3f9d292479f2e44f75ce18b278d1fcc9f48 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 13:07:25 -0700 Subject: [PATCH 099/661] added support for plugins to request and snooze notifications --- env.js | 11 ++- lib/bootevent.js | 6 +- lib/notifications.js | 120 +++++++++++++++--------------- lib/plugins/ar2.js | 50 ++++++++++++- lib/plugins/boluswizardpreview.js | 81 ++++++++++++-------- lib/plugins/index.js | 23 ++++-- lib/plugins/iob.js | 4 +- lib/plugins/pluginbase.js | 42 ----------- lib/plugins/simplealarms.js | 60 +++++++++++++++ lib/sandbox.js | 69 ++++++++++++++++- lib/websocket.js | 22 +++--- 11 files changed, 334 insertions(+), 154 deletions(-) create mode 100644 lib/plugins/simplealarms.js diff --git a/env.js b/env.js index d16ce6ef9c0..38eeda38200 100644 --- a/env.js +++ b/env.js @@ -165,11 +165,20 @@ function config ( ) { var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? "simple" : "predict"); + //TODO: maybe get rid of ALARM_TYPES and only use enable? + if (env.alarm_types.indexOf('simple') > -1) { + env.enable = 'simplealarms ' + env.enable; + } + if (env.alarm_types.indexOf('predict') > -1) { + env.enable = 'ar2 ' + env.enable; + } + // For pushing notifications to Pushover. + //TODO: handle PUSHOVER_ as generic plugin props env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); if (env.pushover_api_token && env.pushover_user_key) { - env.alarm_types.push('pushover'); + env.enable = env.enable + ' pushover'; } // TODO: clean up a bit diff --git a/lib/bootevent.js b/lib/bootevent.js index 34b63e81f26..1bbae95a72f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -43,7 +43,11 @@ function boot (env) { }); ctx.bus.on('data-loaded', function() { - ctx.notifications.processData(env, ctx); + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(env, ctx); }); ctx.bus.uptime( ); diff --git a/lib/notifications.js b/lib/notifications.js index 739e65e9d0e..3d4c1a31703 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,20 +1,19 @@ 'use strict'; -var ar2 = require('./plugins/ar2')(); +var _ = require('lodash'); var THIRTY_MINUTES = 30 * 60 * 1000; -var Alarm = function(_typeName, _threshold) { - this.typeName = _typeName; +var Alarm = function(label) { + this.label = label; this.silenceTime = THIRTY_MINUTES; this.lastAckTime = 0; - this.threshold = _threshold; }; // list of alarms with their thresholds var alarms = { - 'alarm' : new Alarm('Regular', 0.05), - 'urgent_alarm': new Alarm('Urgent', 0.10) + 1 : new Alarm('Warn'), + 2: new Alarm('Urgent') }; function init (env, ctx) { @@ -26,89 +25,88 @@ function init (env, ctx) { //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action function autoAckAlarms() { + var sendClear = false; - for (var type in alarms) { - if (alarms.hasOwnProperty(type)) { - var alarm = alarms[type]; - if (alarm.lastEmitTime) { - console.info('auto acking ' + type); - notifications.ack(type, 1); - sendClear = true; - } + + for (var level = 1; level <=2; level++) { + var alarm = alarms[level]; + if (alarm.lastEmitTime) { + console.info('auto acking ' + alarm.level); + notifications.ack(alarm.level, 1); + sendClear = true; } } + if (sendClear) { ctx.bus.emit('notification', {clear: true}); console.info('emitted notification clear'); } } - function emitAlarm (type) { - var alarm = alarms[type]; + function emitNotification (notify) { + var alarm = alarms[notify.level]; if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { - ctx.bus.emit('notification', {type: type}); + ctx.bus.emit('notification', notify); alarm.lastEmitTime = ctx.data.lastUpdated; - console.info('emitted notification:' + type); + console.info('emitted notification:', notify); } else { - console.log(alarm.typeName + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); + console.log(alarm.label + ' alarm is silenced for ' + Math.floor((alarm.silenceTime - (ctx.data.lastUpdated - alarm.lastAckTime)) / 60000) + ' minutes more'); } } - notifications.processData = function processData ( ) { - var d = ctx.data; + var requests = {}; - console.log('running notifications.processData'); + notifications.initRequests = function initRequests ( ) { + requests = { notifies: [] , snoozes: []}; + }; - var lastSGV = d.sgvs.length > 0 ? d.sgvs[d.sgvs.length - 1].y : null; + notifications.initRequests(); - if (lastSGV) { - var forecast = ar2.forecast(env, ctx); + function findHighestNotify ( ) { + return _.find(requests.notifies, {level: 'urgent'}) || _.find(requests.notifies, {level: 'warn'}) || _.first(requests.notifies); + } - var emitAlarmType = null; + function findLongestSnooze ( ) { + if (_.isEmpty(requests.snoozes)) return null; - if (env.alarm_types.indexOf('simple') > -1) { - if (lastSGV > env.thresholds.bg_high) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_high + ' will emmit ' + emitAlarmType); - } else if (lastSGV > env.thresholds.bg_target_top) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' > ' + env.thresholds.bg_target_top + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_low) { - emitAlarmType = 'urgent_alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_low + ' will emmit ' + emitAlarmType); - } else if (lastSGV < env.thresholds.bg_target_bottom) { - emitAlarmType = 'alarm'; - console.info(lastSGV + ' < ' + env.thresholds.bg_target_bottom + ' will emmit ' + emitAlarmType); - } - } + var groups = _.groupBy(requests.snoozes, 'level'); + var firstKey = _.first(_.keys(groups)); + var longest = firstKey && _.last(groups[firstKey].sort()); - if (!emitAlarmType && env.alarm_types.indexOf('predict') > -1) { - if (forecast.avgLoss > alarms['urgent_alarm'].threshold) { - emitAlarmType = 'urgent_alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['urgent_alarm'].threshold + ' will emmit ' + emitAlarmType); - } else if (forecast.avgLoss > alarms['alarm'].threshold) { - emitAlarmType = 'alarm'; - console.info('Avg Loss:' + forecast.avgLoss + ' > ' + alarms['alarm'].threshold + ' will emmit ' + emitAlarmType); - } - } + return longest; + } - if (d.sgvs.length > 0 && d.sgvs[d.sgvs.length - 1].y < 39) { - emitAlarmType = 'urgent_alarm'; - } + notifications.requestNotify = function requestNotify (notify) { + requests.notifies.push(notify); + }; + + notifications.requestSnooze = function requestSnooze (snooze) { + requests.snoozes.push(snooze); + }; + + notifications.process = function process ( ) { + var highestNotify = findHighestNotify(); + var longestSnooze = findLongestSnooze(); - if (emitAlarmType) { - emitAlarm(emitAlarmType); + if (longestSnooze) { + if (highestNotify && highestNotify.level > longestSnooze.level) { + console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestNotify); } else { - autoAckAlarms(); + console.log('notifications.process, snoozing because: ', longestSnooze); + notifications.ack(longestSnooze.level, longestSnooze.mills) } } - }; - notifications.ack = function ack (type, time) { - var alarm = alarms[type]; - if (alarm) { - console.info('Got an ack for: ', alarm, 'time: ' + time); + if (highestNotify) { + emitNotification(highestNotify); } else { + autoAckAlarms(); + } + }; + + notifications.ack = function ack (level, time) { + var alarm = alarms[level]; + if (!alarm) { console.warn('Got an ack for an unknown alarm time'); return; } diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index f35b0074b2c..6cb62472ce5 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + function init() { function ar2() { @@ -9,13 +11,57 @@ function init() { ar2.label = 'AR2'; ar2.pluginType = 'forecast'; + var WARN_THRESHOLD = 0.05; + var URGENT_THRESHOLD = 0.10; + var ONE_HOUR = 3600000; var ONE_MINUTE = 60000; var FIVE_MINUTES = 300000; - ar2.forecast = function forecast(env, ctx) { + ar2.checkNotifications = function checkNotifications(sbx) { + var forecast = ar2.forecast(sbx.data.sgvs); + + var trigger = false + , level = 0 + , levelLabel = ''; + + if (forecast.avgLoss > URGENT_THRESHOLD) { + trigger = true; + level = 2; + levelLabel = 'Urgent'; + } else if (forecast.avgLoss > WARN_THRESHOLD) { + trigger = true; + level = 1; + levelLabel = 'Warning'; + } + + if (trigger) { + var lastPredicted = _.last(forecast.predicted); + var rangeLabel = ''; + + if (lastPredicted > sbx.thresholds.bg_target_top) { + rangeLabel = 'HIGH'; + } else if (lastPredicted > sbx.thresholds.bg_target_top) { + rangeLabel = 'LOW'; + } + + var display = [levelLabel, rangeLabel, 'predicted - ', forecast.predicted[2], ' in 15mins'].join(' '); + + console.info(display); + + sbx.notifications.requestNotify({ + level: level + , display: display + , debug: { + forecast: forecast + , thresholds: sbx.thresholds + } + }); + } + }; + + ar2.forecast = function forecast(sgvs) { - var sgvs = ctx.data.sgvs; var lastIndex = sgvs.length - 1; var result = { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 14b6392a802..9ca95f7033b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,6 +2,8 @@ var _ = require('lodash'); +var FIFTEEN_MINS = 15 * 60 * 1000; + function init() { function bwp() { @@ -11,50 +13,69 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; + bwp.checkNotifications = function checkNotifications(sbx) { + var results = bwp.calc(sbx); + if (results.bolusEstimate < .25) { + sbx.notifications.requestSnooze({ + level: 2 + , mills: FIFTEEN_MINS + , debug: 'BWP: ' + results.bolusEstimateDisplay + 'U' + }) + } + }; + + bwp.updateVisualisation = function updateVisualisation (sbx) { - var sgv = _.last(sbx.data.sgvs); - //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field - sgv = sgv && (sgv.y || sgv.sgv); + var results = bwp.calc(sbx); - if (sgv == undefined || !sbx.properties.iob) return; - - var profile = sbx.data.profile; + // display text + var info = [ + {label: 'Insulin on Board', value: results.displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} + , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} + ]; + + sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); - var bolusEstimate = 0.0; + }; - // TODO: MMOL -- Jason: if we assume sens is in display units, we don't need to do any conversion - sgv = sbx.pluginBase.scaleBg(sgv, sbx); + bwp.calc = function calc (sbx) { - var iob = sbx.properties.iob.iob; + var results = { + effect: 0 + , outcome: 0 + , bolusEstimate: 0.0 + }; - var effect = iob * profile.sens; - var outcome = sgv - effect; + var sgv = sbx.scaleBg(sbx.data.lastSGV()); + + if (sgv == undefined || !sbx.properties.iob) return; + + var profile = sbx.data.profile; + + var iob = results.iob = sbx.properties.iob.iob; + + results.effect = iob * profile.sens; + results.outcome = sgv - results.effect; var delta = 0; - if (outcome > profile.target_high) { - delta = outcome - profile.target_high; - bolusEstimate = delta / profile.sens; + if (results.outcome > profile.target_high) { + delta = results.outcome - profile.target_high; + results.bolusEstimate = delta / profile.sens; } - if (outcome < profile.target_low) { - delta = Math.abs(outcome - profile.target_low); - bolusEstimate = delta / profile.sens * -1; + if (results.outcome < profile.target_low) { + delta = Math.abs(results.outcome - profile.target_low); + results.bolusEstimate = delta / profile.sens * -1; } - bolusEstimate = sbx.pluginBase.roundInsulinForDisplayFormat(bolusEstimate, sbx); - outcome = sbx.pluginBase.roundBGToDisplayFormat(outcome, sbx); - var displayIOB = sbx.pluginBase.roundInsulinForDisplayFormat(iob, sbx); - - // display text - var info = [ - {label: 'Insulin on Board', value: displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + sbx.pluginBase.roundBGToDisplayFormat(effect, sbx) + ' ' + sbx.pluginBase.getBGUnits(sbx)} - , {label: 'Expected outcome', value: outcome + ' ' + sbx.pluginBase.getBGUnits(sbx)} - ]; - - sbx.pluginBase.updatePillText(bwp, bolusEstimate + 'U', 'BWP', info); + results.bolusEstimateDisplay = sbx.roundInsulinForDisplayFormat(results.bolusEstimate); + results.outcomeDisplay = sbx.roundBGToDisplayFormat(results.outcome); + results.displayIOB = sbx.roundInsulinForDisplayFormat(results.iob); + results.effectDisplay = sbx.roundBGToDisplayFormat(results.effect); + return results; }; return bwp(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 62905cc1a71..d4d61b81869 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -14,16 +14,23 @@ function init() { plugins.base = require('./pluginbase'); plugins.registerServerDefaults = function registerServerDefaults() { - plugins.register([ require('./pushnotify')() ]); + plugins.register([ + require('./ar2')() + , require('./simplealarms')() + , require('./pushnotify')() + , require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + ]); return plugins; }; plugins.registerClientDefaults = function registerClientDefaults() { plugins.register([ - require('./iob')(), - require('./cob')(), - require('./boluswizardpreview')(), - require('./cannulaage')() + require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./cannulaage')() ]); return plugins; }; @@ -81,6 +88,12 @@ function init() { }); }; + plugins.checkNotifications = function checkNotifications(sbx) { + plugins.eachEnabledPlugin( function eachPlugin (plugin) { + plugin.checkNotifications && plugin.checkNotifications(sbx); + }); + }; + plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { plugin.updateVisualisation && plugin.updateVisualisation(sbx); diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 30748e51cfd..806107f61c2 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -95,11 +95,11 @@ function init() { if (prop && prop.lastBolus) { var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); - var amount = sbx.pluginBase.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin), sbx) + 'U'; + var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U'; info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(iob, sbx.pluginBase.roundInsulinForDisplayFormat(prop.display, sbx) + 'U', 'IOB', info); + sbx.pluginBase.updatePillText(iob, sbx.roundInsulinForDisplayFormat(prop.display) + 'U', 'IOB', info); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 09c342399c7..9ef44913970 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -44,48 +44,6 @@ function init (majorPills, minorPills, tooltip) { } }; - pluginBase.roundInsulinForDisplayFormat = function roundInsulinForDisplayFormat (iob, sbx) { - - if (iob == 0) return 0; - - if (sbx.properties.roundingStyle == 'medtronic') { - var denominator = 0.1; - var digits = 1; - if (iob > 0.5 && iob < 1) { - denominator = 0.05; - digits = 2; - } - if (iob <= 0.5) { - denominator = 0.025; - digits = 3; - } - return (Math.floor(iob / denominator) * denominator).toFixed(digits); - } - - return (Math.floor(iob / 0.01) * 0.01).toFixed(2); - - }; - - pluginBase.getBGUnits = function getBGUnits (sbx) { - if (sbx.units == 'mmol') return 'mmol/L'; - return "mg/dl"; - }; - - pluginBase.roundBGToDisplayFormat = function roundBGToDisplayFormat (bg, sbx) { - if (sbx.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); - }; - - pluginBase.scaleBg = function scaleBg (bg, sbx) { - if (sbx.units == 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - }; - return pluginBase(); } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js new file mode 100644 index 00000000000..78b42f6b398 --- /dev/null +++ b/lib/plugins/simplealarms.js @@ -0,0 +1,60 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function simplealarms() { + return simplealarms; + } + + simplealarms.label = 'Simple Alarms'; + simplealarms.pluginType = 'notification'; + + simplealarms.checkNotifications = function checkNotifications(sbx) { + var lastSGV = sbx.data.lastSGV() + , trigger = false + , level = 0 + , label = '' + ; + + if (lastSGV) { + if (lastSGV > sbx.thresholds.bg_high) { + trigger = true; + level = 2; + label = 'Urgent HIGH:'; + console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + } else if (lastSGV > sbx.thresholds.bg_target_top) { + trigger = true; + level = 1; + label = 'High warning:'; + console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + } else if (lastSGV < sbx.thresholds.bg_low) { + trigger = true; + level = 2; + label = 'Urgent LOW:'; + console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + } else if (lastSGV < sbx.thresholds.bg_target_bottom) { + trigger = true; + level = 1; + label = 'Low warning:'; + console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + } + + if (trigger) { + sbx.notifications.requestNotify({ + level: level + , display: [label, lastSGV].join(' ') + , debug: { + lastSGV: lastSGV, thresholds: sbx.thresholds + } + }); + } + } + }; + + return simplealarms(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index af15c413a42..a15031f43b9 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -1,3 +1,7 @@ +'use strict'; + +var _ = require('lodash'); +var units = require('./units'); var utils = require('./utils'); function init ( ) { @@ -5,6 +9,19 @@ function init ( ) { function init() { sbx.properties = []; + sbx.scaleBg = scaleBg; + sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; + sbx.roundBGToDisplayFormat = roundBGToDisplayFormat; + } + + function extend () { + sbx.unitsLabel = unitsLabel(); + sbx.data = sbx.data || {}; + sbx.data.lastSGV = function lastSGV () { + var last = _.last(sbx.data.sgvs); + //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field + return last && (last.y || last.sgv); + }; } /** @@ -24,12 +41,17 @@ function init ( ) { sbx.alarm_types = env.alarm_types; sbx.data = ctx.data.clone(); + //don't expose all of notifications, ctx.notifications will decide what to do after all plugins chime in + sbx.notifications = _.pick(ctx.notifications, ['requestNotify', 'requestSnooze', 'requestClear']); + //Plugins will expect the right profile based on time - sbx.data.profile = data.profiles.length > 0 ? data.profiles[0] : undefined; + sbx.data.profile = _.first(ctx.data.profiles); delete sbx.data.profiles; sbx.properties = []; + extend(); + return sbx; }; @@ -55,6 +77,8 @@ function init ( ) { sbx.data = data; sbx.pluginBase = pluginBase; + extend(); + return sbx; }; @@ -73,6 +97,49 @@ function init ( ) { } }; + function scaleBg (bg) { + if (sbx.units == 'mmol' && bg) { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function roundInsulinForDisplayFormat (insulin) { + + if (insulin == 0) return '0'; + + if (sbx.properties.roundingStyle == 'medtronic') { + var denominator = 0.1; + var digits = 1; + if (insulin > 0.5 && iob < 1) { + denominator = 0.05; + digits = 2; + } + if (insulin <= 0.5) { + denominator = 0.025; + digits = 3; + } + return (Math.floor(insulin / denominator) * denominator).toFixed(digits); + } + + return (Math.floor(insulin / 0.01) * 0.01).toFixed(2); + + } + + function unitsLabel ( ) { + if (sbx.units == 'mmol') return 'mmol/L'; + return "mg/dl"; + } + + function roundBGToDisplayFormat (bg) { + if (sbx.units == 'mmol') { + return Math.round(bg * 10) / 10; + } + return Math.round(bg); + } + + return sbx; } diff --git a/lib/websocket.js b/lib/websocket.js index fe09ac8dbbb..0d6dbababc7 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -37,10 +37,11 @@ function init (env, ctx, server) { socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - ctx.notifications.ack(alarmType, silenceTime); - if (alarmType == 'urgent_alarm') { + var level = alarmType == 'urgent_alarm' ? 2 : 1; + ctx.notifications.ack(level, silenceTime); + if (level == 2) { //also clean normal alarm so we don't get a double alarm as BG comes back into range - ctx.notifications.ack('alarm', silenceTime); + ctx.notifications.ack(1, silenceTime); } }); socket.on('disconnect', function () { @@ -65,13 +66,16 @@ function init (env, ctx, server) { } }; - websocket.emitNotification = function emitNotification (info) { - if (info.clear) { + websocket.emitNotification = function emitNotification (notify) { + if (notify.clear) { io.emit('clear_alarm', true); - console.info('emitted clear_alarm to all clients'); - } else if (info.type) { - io.emit(info.type); - console.info('emitted ' + info.type + ' to all clients'); + console.notify('emitted clear_alarm to all clients'); + } else if (notify.level == 1) { + io.emit('alarm', notify); + console.info('emitted alarm to all clients'); + } else if (notify.level == 2) { + io.emit('urgent_alarm', notify); + console.info('emitted urgent_alarm to all clients'); } }; From 36bbf8d7da9a6c5d1b39ce6322b36f23026069d4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:13:54 -0700 Subject: [PATCH 100/661] cleanup/fix --- lib/plugins/ar2.js | 2 +- lib/sandbox.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 6cb62472ce5..35e633516e7 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -56,7 +56,7 @@ function init() { forecast: forecast , thresholds: sbx.thresholds } - }); + }); } }; diff --git a/lib/sandbox.js b/lib/sandbox.js index a15031f43b9..b5cb8acf067 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); -var units = require('./units'); +var units = require('./units')(); var utils = require('./utils'); function init ( ) { From cb1ced609f27cee34b288ffacb18182e509fa83d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:16:30 -0700 Subject: [PATCH 101/661] also trigger alarms from BWP --- lib/plugins/boluswizardpreview.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9ca95f7033b..13c114ea32d 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -15,12 +15,27 @@ function init() { bwp.checkNotifications = function checkNotifications(sbx) { var results = bwp.calc(sbx); - if (results.bolusEstimate < .25) { + + //TODO: not sure where these will come from yet + var snoozeBWP = sbx.properties.snoozeBWP || 0.10; + var warnBWP = sbx.properties.warnBWP || 0.35; + var urgentBWP = sbx.properties.urgentBWP || 0.75; + + if (results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: 2 , mills: FIFTEEN_MINS - , debug: 'BWP: ' + results.bolusEstimateDisplay + 'U' + , debug: results }) + } else if (results.bolusEstimate > warnBWP) { + var level = results.bolusEstimate > urgentBWP ? 2 : 1; + var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent': 'Warning'; + var display = [levelLabel, 'Check BG, time to bolus?'].join(' '); + sbx.notifications.requestNotify({ + level: level + , display: display + , debug: results + }); } }; From e1a05c26eb4f41805831992de8d88522d2ade847 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:19:19 -0700 Subject: [PATCH 102/661] if an urgent alarm is acked, also ack warns --- lib/notifications.js | 4 ++++ lib/websocket.js | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 3d4c1a31703..ecd5c003cb0 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -114,6 +114,10 @@ function init (env, ctx) { alarm.silenceTime = time ? time : THIRTY_MINUTES; delete alarm.lastEmitTime; + if (level == 2) { + notifications.ack(1, time); + } + }; return notifications(); diff --git a/lib/websocket.js b/lib/websocket.js index 0d6dbababc7..d06964a3ef2 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -39,10 +39,6 @@ function init (env, ctx, server) { socket.on('ack', function(alarmType, silenceTime) { var level = alarmType == 'urgent_alarm' ? 2 : 1; ctx.notifications.ack(level, silenceTime); - if (level == 2) { - //also clean normal alarm so we don't get a double alarm as BG comes back into range - ctx.notifications.ack(1, silenceTime); - } }); socket.on('disconnect', function () { io.emit('clients', --watchers); From 65d24a86068e33d32c59d6bc510a7da9f99b5153 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 14:38:49 -0700 Subject: [PATCH 103/661] add lastSVG to results/debug --- lib/plugins/boluswizardpreview.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 13c114ea32d..541390cf0be 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -65,6 +65,8 @@ function init() { var sgv = sbx.scaleBg(sbx.data.lastSGV()); + results.lastSGV = sgv; + if (sgv == undefined || !sbx.properties.iob) return; var profile = sbx.data.profile; From 48fad6870d93de0b1e4fa5570cc9ab8a0341f2c2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 16:23:20 -0700 Subject: [PATCH 104/661] a few more steps to get pushover hooked in the new way --- lib/bootevent.js | 23 ++++- lib/entries.js | 6 +- lib/plugins/index.js | 1 - lib/plugins/simplealarms.js | 16 +++- lib/{plugins => }/pushnotify.js | 145 ++++++++++++++++++++------------ lib/treatments.js | 7 +- lib/websocket.js | 4 +- server.js | 4 +- 8 files changed, 132 insertions(+), 74 deletions(-) rename lib/{plugins => }/pushnotify.js (60%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 1bbae95a72f..c8e2947a1b5 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -29,25 +29,39 @@ function boot (env) { store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - + ctx.bus = require('./bus')(env, ctx); ctx.data = require('./data')(env, ctx); ctx.notifications = require('./notifications')(env, ctx); - ctx.bus.on('tick', function(tick) { - console.info('tick', tick.now); + function updateData ( ) { ctx.data.update(function dataUpdated () { ctx.bus.emit('data-loaded'); }); + } + + ctx.bus.on('tick', function timedReloadData (tick) { + console.info('tick', tick.now); + updateData(); }); - ctx.bus.on('data-loaded', function() { + ctx.bus.on('data-received', function forceReloadData ( ) { + console.info('got data-received event, reloading now'); + updateData(); + }); + + ctx.bus.on('data-loaded', function updatePlugins ( ) { var sbx = require('./sandbox')().serverInit(env, ctx); ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); ctx.notifications.process(env, ctx); + ctx.bus.emit('data-processed'); + }); + + ctx.bus.on('notification', function(info) { + websocket.emitNotification(info); }); ctx.bus.uptime( ); @@ -58,4 +72,5 @@ function boot (env) { return proc; } + module.exports = boot; diff --git a/lib/entries.js b/lib/entries.js index fe57606e6e9..e271272ff3c 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -116,13 +116,11 @@ function storage(env, ctx) { collection.update(query, doc, {upsert: true}, function (err, created) { firstErr = firstErr || err; if (++totalCreated === numDocs) { + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); fn(firstErr, docs); } }); - - ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processEntry) plugin.processEntry(doc, ctx, env); - }); }); }); } diff --git a/lib/plugins/index.js b/lib/plugins/index.js index d4d61b81869..5242f27f7a2 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -17,7 +17,6 @@ function init() { plugins.register([ require('./ar2')() , require('./simplealarms')() - , require('./pushnotify')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 78b42f6b398..860df1d583d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -12,10 +12,12 @@ function init() { simplealarms.pluginType = 'notification'; simplealarms.checkNotifications = function checkNotifications(sbx) { - var lastSGV = sbx.data.lastSGV() + var lastSGV = sbx.scaleBg(sbx.data.lastSGV()) + , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 , label = '' + , pushoverSound = null ; if (lastSGV) { @@ -23,28 +25,40 @@ function init() { trigger = true; level = 2; label = 'Urgent HIGH:'; + pushoverSound = 'persistent'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; label = 'High warning:'; + pushoverSound = 'climb'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; label = 'Urgent LOW:'; + pushoverSound = 'persistent'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; label = 'Low warning:'; + pushoverSound = 'falling'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { + trigger = true; + level = o; + label = 'Perfect:'; + pushoverSound = 'magic'; } + + if (trigger) { sbx.notifications.requestNotify({ level: level , display: [label, lastSGV].join(' ') + , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds } diff --git a/lib/plugins/pushnotify.js b/lib/pushnotify.js similarity index 60% rename from lib/plugins/pushnotify.js rename to lib/pushnotify.js index c938af66130..9b6f8a95431 100644 --- a/lib/plugins/pushnotify.js +++ b/lib/pushnotify.js @@ -1,16 +1,17 @@ 'use strict'; -var units = require('../units')(); +var _ = require('lodash'); +var units = require('units')(); -function init() { +function init(env, ctx) { // declare local constants for time differences var TIME_10_MINS = 10 * 60 * 1000, TIME_15_MINS = 15 * 60 * 1000, TIME_30_MINS = TIME_15_MINS * 2; - //the uploader may the last MBG multiple times, make sure we get a single notification - var lastMBGDate = 0; + var lastSentMBG = null; + var lastSentTreatment = null; //simple SGV Alert throttling //TODO: single snooze for websockets and push (when we add push callbacks) @@ -22,42 +23,69 @@ function init() { return pushnotify; } - pushnotify.label = 'Push Notify'; - pushnotify.pluginType = 'server-process'; + pushnotify.emitNotification = function emitNotification (notify) { + + if (!ctx.pushover) return; + + var msg = { + expire: TIME_15_MINS, + message: notify.message, + title: notify.display, + sound: notify.pushoverSound || 'gamelan', + timestamp: new Date( ), + priority: notify.level, + retry: 30 + }; + + ctx.pushover.send( msg, function( err, result ) { + console.info('pushnotify.emitNotification', err, result); + }); + - pushnotify.processEntry = function processEntry(entry, ctx, env) { - if (entry.type && entry.date && ctx.pushover) { - if (entry.type == 'mbg' || entry.type == 'meter') { - sendMBGPushover(entry, ctx); - } else if (entry.type == 'sgv') { - sendSGVPushover(entry, ctx); - } - } }; - pushnotify.processTreatment = function processTreatment(treatment, eventTime, preBolusCarbs, ctx, env) { + pushnotify.update = function update( ) { + sendMBG(); + sendTreatment() + }; - if (!ctx.pushover) return; + function sendTreatment ( ) { + + var lastTreatment = _.last(ctx.data.treatments); + if (!lastTreatment) return; + + var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); + + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + return; + } //since we don't know the time zone on the device viewing the push message //we can only show the amount of adjustment - var timeAdjustment = calcTimeAdjustment(eventTime); + //TODO: need to store time extra info to figure out if treatment was added in past/future + //var timeAdjustment = calcTimeAdjustment(eventTime); + + var text = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + + (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs : '') + - var text = (treatment.glucose ? 'BG: ' + treatment.glucose + ' (' + treatment.glucoseType + ')' : '') + - (treatment.carbs ? '\nCarbs: ' + treatment.carbs : '') + - (preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ - (treatment.insulin ? '\nInsulin: ' + treatment.insulin : '')+ - (treatment.enteredBy ? '\nEntered By: ' + treatment.enteredBy : '') + - (timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - (treatment.notes ? '\nNotes: ' + treatment.notes : ''); + //TODO: find a better way to connect split treatments + //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + + (lastTreatment.insulin ? '\nInsulin: ' + lastTreatment.insulin : '')+ + (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + + + //TODO: find a better way to store timeAdjustment + //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + + (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); var msg = { - expire: 14400, // 4 hours + expire: TIME_10_MINS, message: text, - title: treatment.eventType, + title: lastTreatment.eventType, sound: 'gamelan', timestamp: new Date( ), - priority: (treatment.eventType == 'Note' ? -1 : 0), + priority: (lastTreatment.eventType == 'Note' ? -1 : 0), retry: 30 }; @@ -65,39 +93,46 @@ function init() { console.log(result); }); - }; + lastSentTreatment = lastTreatment; + } - function sendMBGPushover(entry, ctx) { - - if (entry.mbg && entry.type == 'mbg' && entry.date != lastMBGDate) { - var offset = new Date().getTime() - entry.date; - if (offset > TIME_10_MINS) { - console.info('No MBG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime()); - } else { - var mbg = entry.mbg; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: 14400, // 4 hours - message: '\nMeter BG: ' + mbg, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(entry.date), - priority: 0, - retry: 30 - }; - - ctx.pushover.send(msg, function (err, result) { - console.log(result); - }); - } - lastMBGDate = entry.date; + + function sendMBG( ) { + + var lastMBG = _.last(ctx.data.mbgs); + if (!lastMBG) return; + + var ago = new Date().getTime() - lastMBG.date; + + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + return; } + + var mbg = lastMBG.mbg; + if (env.DISPLAY_UNITS == 'mmol') { + mbg = units.mgdlToMMOL(mbg); + } + var msg = { + expire: TIME_30_MINS, + message: '\nMeter BG: ' + mbg, + title: 'Calibration', + sound: 'magic', + timestamp: new Date(entry.date), + priority: 0, + retry: 30 + }; + + ctx.pushover.send(msg, function (err, result) { + console.log(result); + }); + + lastSentMBG = lastMBG; + } - function sendSGVPushover(entry, ctx) { + //TODO: move some of this to simplealarms + function old(entry, ctx) { if (!entry.sgv || entry.type != 'sgv') { return; diff --git a/lib/treatments.js b/lib/treatments.js index c0859a07e5c..8e92128dcc8 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -51,11 +51,8 @@ function storage (env, ctx) { }); } - //TODO: call plugins with processTreatments - ctx.plugins.eachEnabledPlugin(function eachEnabled(plugin) { - if (plugin.processTreatment) plugin.processTreatment(obj, eventTime, preBolusCarbs, ctx, env) - }); - + //TODO: this is triggering a read from Mongo, we can do better + ctx.bus.emit('data-received'); }); } diff --git a/lib/websocket.js b/lib/websocket.js index d06964a3ef2..d6e6880861b 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -46,8 +46,8 @@ function init (env, ctx, server) { }); } - websocket.processData = function processData ( ) { - console.log('running websocket.processData'); + websocket.update = function update ( ) { + console.log('running websocket.update'); var lastSGV = ctx.data.sgvs.length > 0 ? ctx.data.sgvs[ctx.data.sgvs.length - 1].y : null; if (lastSGV) { if (lastData.sgvs) { diff --git a/server.js b/server.js index 1cf8bc1c8b8..8dcecfce491 100644 --- a/server.js +++ b/server.js @@ -60,8 +60,8 @@ bootevent(env).boot(function booted (ctx) { /////////////////////////////////////////////////// var websocket = require('./lib/websocket')(env, ctx, server); - ctx.bus.on('data-loaded', function() { - websocket.processData(); + ctx.bus.on('data-processed', function() { + websocket.update(); }); ctx.bus.on('notification', function(info) { From eaa7faa3e1a6bfda66fd09e447d766fb15eded72 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 17:38:22 -0700 Subject: [PATCH 105/661] pushnotify, not websockets --- lib/bootevent.js | 5 ++--- lib/pushnotify.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index c8e2947a1b5..17aa02d5af1 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,6 +18,7 @@ function boot (env) { /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); @@ -60,9 +61,7 @@ function boot (env) { ctx.bus.emit('data-processed'); }); - ctx.bus.on('notification', function(info) { - websocket.emitNotification(info); - }); + ctx.bus.on('notification', ctx.pushnotify.emitNotification); ctx.bus.uptime( ); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 9b6f8a95431..779b9e5fb7a 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -1,7 +1,7 @@ 'use strict'; var _ = require('lodash'); -var units = require('units')(); +var units = require('./units')(); function init(env, ctx) { From b6cf0b3a616138223f3d6ce290a70b2bccb08f8a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:08:02 -0700 Subject: [PATCH 106/661] some more little bugs --- lib/plugins/ar2.js | 6 +++--- lib/plugins/boluswizardpreview.js | 4 +++- lib/websocket.js | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 35e633516e7..64a9cf8c076 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -39,13 +39,13 @@ function init() { var lastPredicted = _.last(forecast.predicted); var rangeLabel = ''; - if (lastPredicted > sbx.thresholds.bg_target_top) { + if (lastPredicted > sbx.thresholds.bg_target_bottom) { rangeLabel = 'HIGH'; - } else if (lastPredicted > sbx.thresholds.bg_target_top) { + } else if (lastPredicted < sbx.thresholds.bg_target_top) { rangeLabel = 'LOW'; } - var display = [levelLabel, rangeLabel, 'predicted - ', forecast.predicted[2], ' in 15mins'].join(' '); + var display = [levelLabel, rangeLabel, 'predicted', sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); console.info(display); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 541390cf0be..428e576ecd2 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -16,12 +16,14 @@ function init() { bwp.checkNotifications = function checkNotifications(sbx) { var results = bwp.calc(sbx); + if (results.lastSGV < sbx.data.profile.target_high) return; + //TODO: not sure where these will come from yet var snoozeBWP = sbx.properties.snoozeBWP || 0.10; var warnBWP = sbx.properties.warnBWP || 0.35; var urgentBWP = sbx.properties.urgentBWP || 0.75; - if (results.bolusEstimate < snoozeBWP) { + if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: 2 , mills: FIFTEEN_MINS diff --git a/lib/websocket.js b/lib/websocket.js index d6e6880861b..a2028656755 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -65,7 +65,7 @@ function init (env, ctx, server) { websocket.emitNotification = function emitNotification (notify) { if (notify.clear) { io.emit('clear_alarm', true); - console.notify('emitted clear_alarm to all clients'); + console.info('emitted clear_alarm to all clients'); } else if (notify.level == 1) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); From 2316c4ee1c1fa98c1d415ed04a8b2f946a11d89b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:19:39 -0700 Subject: [PATCH 107/661] add title/message for all notifications --- lib/plugins/ar2.js | 6 ++++-- lib/plugins/boluswizardpreview.js | 7 ++++--- lib/plugins/simplealarms.js | 16 +++++++++------- lib/pushnotify.js | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 64a9cf8c076..c1d0305a574 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -45,13 +45,15 @@ function init() { rangeLabel = 'LOW'; } - var display = [levelLabel, rangeLabel, 'predicted', sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + var title = [levelLabel, rangeLabel, 'predicted'].join(' '); + var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); console.info(display); sbx.notifications.requestNotify({ level: level - , display: display + , title: title + , message: message , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 428e576ecd2..77d7042cbf4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -31,11 +31,12 @@ function init() { }) } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? 2 : 1; - var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent': 'Warning'; - var display = [levelLabel, 'Check BG, time to bolus?'].join(' '); + var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent' : 'Warning'; + var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); sbx.notifications.requestNotify({ level: level - , display: display + , title: 'Check BG, time to bolus?' + , message: message , debug: results }); } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 860df1d583d..5c39cdcbf4c 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -16,7 +16,8 @@ function init() { , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 - , label = '' + , title = '' + , message = '' , pushoverSound = null ; @@ -24,31 +25,31 @@ function init() { if (lastSGV > sbx.thresholds.bg_high) { trigger = true; level = 2; - label = 'Urgent HIGH:'; + title = 'Urgent HIGH'; pushoverSound = 'persistent'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; - label = 'High warning:'; + title = 'High warning'; pushoverSound = 'climb'; console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; - label = 'Urgent LOW:'; + title = 'Urgent LOW'; pushoverSound = 'persistent'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; - label = 'Low warning:'; + title = 'Low warning'; pushoverSound = 'falling'; console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; - label = 'Perfect:'; + title = 'Perfect'; pushoverSound = 'magic'; } @@ -57,7 +58,8 @@ function init() { if (trigger) { sbx.notifications.requestNotify({ level: level - , display: [label, lastSGV].join(' ') + , title: title + , message: [lastSGV, sbx.unitsLabel].join(' ') , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 779b9e5fb7a..2857326759e 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -29,8 +29,8 @@ function init(env, ctx) { var msg = { expire: TIME_15_MINS, + title: notify.title, message: notify.message, - title: notify.display, sound: notify.pushoverSound || 'gamelan', timestamp: new Date( ), priority: notify.level, From 184ba224c67b567e177142a928392a47e32b208c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 18:28:12 -0700 Subject: [PATCH 108/661] starting to work, simple bg pushover messages getting through --- lib/plugins/simplealarms.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 5c39cdcbf4c..870e7e8c3cb 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -17,7 +17,6 @@ function init() { , trigger = false , level = 0 , title = '' - , message = '' , pushoverSound = null ; @@ -27,25 +26,25 @@ function init() { level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_high)); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - console.info(label + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_low)); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - console.info(label + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; From 6db5844ac31d6f651bfad38bf726ce2de153b5e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 22:13:29 -0700 Subject: [PATCH 109/661] fix pushover expire time; always return a result when calcing bwp --- lib/plugins/boluswizardpreview.js | 2 +- lib/pushnotify.js | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 77d7042cbf4..3074bcebf57 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -70,7 +70,7 @@ function init() { results.lastSGV = sgv; - if (sgv == undefined || !sbx.properties.iob) return; + if (sgv == undefined || !sbx.properties.iob) return results; var profile = sbx.data.profile; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 2857326759e..5e0a17c653b 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -6,9 +6,10 @@ var units = require('./units')(); function init(env, ctx) { // declare local constants for time differences - var TIME_10_MINS = 10 * 60 * 1000, - TIME_15_MINS = 15 * 60 * 1000, - TIME_30_MINS = TIME_15_MINS * 2; + var TIME_10_MINS_MS = 10 * 60 * 1000, + TIME_15_MINS_S = 15 * 60, + TIME_15_MINS_MS = TIME_15_MINS_S * 1000, + TIME_30_MINS_MS = TIME_15_MINS_MS * 2; var lastSentMBG = null; var lastSentTreatment = null; @@ -28,7 +29,7 @@ function init(env, ctx) { if (!ctx.pushover) return; var msg = { - expire: TIME_15_MINS, + expire: TIME_15_MINS_S, title: notify.title, message: notify.message, sound: notify.pushoverSound || 'gamelan', @@ -56,7 +57,7 @@ function init(env, ctx) { var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + if (JSON.stringify(lastTreatment) == JSON.stringify(lastSentTreatment) || ago > TIME_10_MINS_MS) { return; } @@ -80,7 +81,7 @@ function init(env, ctx) { (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); var msg = { - expire: TIME_10_MINS, + expire: TIME_15_MINS_S, message: text, title: lastTreatment.eventType, sound: 'gamelan', @@ -105,7 +106,7 @@ function init(env, ctx) { var ago = new Date().getTime() - lastMBG.date; - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS) { + if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { return; } @@ -114,7 +115,7 @@ function init(env, ctx) { mbg = units.mgdlToMMOL(mbg); } var msg = { - expire: TIME_30_MINS, + expire: TIME_15_MINS_S, message: '\nMeter BG: ' + mbg, title: 'Calibration', sound: 'magic', @@ -141,7 +142,7 @@ function init(env, ctx) { var now = new Date().getTime(), offset = new Date().getTime() - entry.date; - if (offset > TIME_10_MINS || entry.date == lastSGVDate) { + if (offset > TIME_10_MINS_MS || entry.date == lastSGVDate) { console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); return; } @@ -163,7 +164,7 @@ function init(env, ctx) { // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high if (entry.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS) { + if (sinceLastAlert > TIME_30_MINS_MS) { title = 'CGM Error'; priority = 1; sound = 'persistent'; @@ -188,11 +189,11 @@ function init(env, ctx) { title = 'Double Up'; priority = 1; sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS) { + } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS_MS) { title = 'High'; priority = 1; sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS) { + } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS_MS) { title = 'Urgent High'; priority = 1; sound = 'persistent'; From a7a65469bd7d8530a40cc1f38157e60ec62a8a44 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 22:41:34 -0700 Subject: [PATCH 110/661] ug --- lib/plugins/ar2.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index c1d0305a574..391f128518c 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -48,8 +48,6 @@ function init() { var title = [levelLabel, rangeLabel, 'predicted'].join(' '); var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); - console.info(display); - sbx.notifications.requestNotify({ level: level , title: title From da3b56425a3d2b6411a11fe7d693c818fcd1f465 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 23:34:52 -0700 Subject: [PATCH 111/661] less pushing, better messages --- lib/bootevent.js | 1 - lib/plugins/ar2.js | 29 ++++++++++++++++++++++++----- lib/pushnotify.js | 17 +++++++++++------ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 17aa02d5af1..9755fd5a5e6 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,7 +17,6 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushover = require('./pushover')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 391f128518c..6539b195280 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -23,12 +23,15 @@ function init() { var trigger = false , level = 0 - , levelLabel = ''; + , levelLabel = '' + , pushoverSound = null + ; if (forecast.avgLoss > URGENT_THRESHOLD) { trigger = true; level = 2; levelLabel = 'Urgent'; + pushoverSound = 'persistent'; } else if (forecast.avgLoss > WARN_THRESHOLD) { trigger = true; level = 1; @@ -36,22 +39,38 @@ function init() { } if (trigger) { - var lastPredicted = _.last(forecast.predicted); + + var predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); + + var first = _.first(predicted); + var last = _.last(predicted); + var avg = _.sum(predicted) / predicted.length; + + var max = _.max([first, last, avg]); + var min = _.max([first, last, avg]); + var rangeLabel = ''; - if (lastPredicted > sbx.thresholds.bg_target_bottom) { + if (max > sbx.thresholds.bg_target_top) { rangeLabel = 'HIGH'; - } else if (lastPredicted < sbx.thresholds.bg_target_top) { + if (!pushoverSound) pushoverSound = 'climb' + } else if (min < sbx.thresholds.bg_target_bottom) { rangeLabel = 'LOW'; + if (!pushoverSound) pushoverSound = 'falling' + } else { + rangeLabel = ''; } - var title = [levelLabel, rangeLabel, 'predicted'].join(' '); + var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); + sbx.notifications.requestNotify({ level: level , title: title , message: message + , pushoverSound: pushoverSound , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 5e0a17c653b..a743777dba4 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -5,6 +5,8 @@ var units = require('./units')(); function init(env, ctx) { + var pushover = require('./pushover')(env); + // declare local constants for time differences var TIME_10_MINS_MS = 10 * 60 * 1000, TIME_15_MINS_S = 15 * 60, @@ -24,9 +26,12 @@ function init(env, ctx) { return pushnotify; } + var lastEmit = {}; + pushnotify.emitNotification = function emitNotification (notify) { - if (!ctx.pushover) return; + //make this smarter, for now send alerts every 15mins till cleared + if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; var msg = { expire: TIME_15_MINS_S, @@ -38,11 +43,11 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send( msg, function( err, result ) { + pushover.send( msg, function( err, result ) { console.info('pushnotify.emitNotification', err, result); }); - + lastEmit[notify.level] = Date.now(); }; pushnotify.update = function update( ) { @@ -90,7 +95,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send( msg, function( err, result ) { + pushover.send( msg, function( err, result ) { console.log(result); }); @@ -124,7 +129,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send(msg, function (err, result) { + pushover.send(msg, function (err, result) { console.log(result); }); @@ -212,7 +217,7 @@ function init(env, ctx) { retry: 30 }; - ctx.pushover.send(msg, function (err, result) { + pushover.send(msg, function (err, result) { console.log(result); }); } From 46935cf20b4d97f954436929047c43bd02dba17a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 9 Jun 2015 23:42:12 -0700 Subject: [PATCH 112/661] include units in message --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 6539b195280..9588868f0a4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -62,7 +62,7 @@ function init() { } var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var message = [sbx.scaleBg(forecast.predicted[2].y), 'in 15mins'].join(' '); + var message = [predicted[2], sbx.unitsLabel, 'in 15mins'].join(' '); forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); From ca3b20b26cb6d59012d9dfc1d16ab7d9a938bcb5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 10 Jun 2015 00:52:59 -0700 Subject: [PATCH 113/661] got mbg calibrations and treatment notifications working --- lib/bootevent.js | 3 ++- lib/pushnotify.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 9755fd5a5e6..e0ecfc2ee34 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -56,7 +56,8 @@ function boot (env) { ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(env, ctx); + ctx.pushnotify.process(sbx); + ctx.notifications.process(sbx); ctx.bus.emit('data-processed'); }); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index a743777dba4..205df593ada 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -50,12 +50,12 @@ function init(env, ctx) { lastEmit[notify.level] = Date.now(); }; - pushnotify.update = function update( ) { - sendMBG(); - sendTreatment() + pushnotify.process = function process(sbx) { + sendMBG(sbx); + sendTreatment(sbx) }; - function sendTreatment ( ) { + function sendTreatment (sbx) { var lastTreatment = _.last(ctx.data.treatments); if (!lastTreatment) return; @@ -104,7 +104,7 @@ function init(env, ctx) { } - function sendMBG( ) { + function sendMBG(sbx) { var lastMBG = _.last(ctx.data.mbgs); if (!lastMBG) return; @@ -115,16 +115,16 @@ function init(env, ctx) { return; } - var mbg = lastMBG.mbg; + var mbg = lastMBG.y; if (env.DISPLAY_UNITS == 'mmol') { mbg = units.mgdlToMMOL(mbg); } var msg = { expire: TIME_15_MINS_S, - message: '\nMeter BG: ' + mbg, + message: '\nMeter BG: ' + mbg + ' ' + sbx.unitsLabel, title: 'Calibration', sound: 'magic', - timestamp: new Date(entry.date), + timestamp: new Date(lastMBG.date), priority: 0, retry: 30 }; From 7446ef6c3a94d63435e32521fe9fe90757785e99 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Wed, 10 Jun 2015 22:52:20 +0300 Subject: [PATCH 114/661] removed code duplication from delta calculation --- lib/data.js | 127 ++++++++++++++++++++++++---------------------------- 1 file changed, 59 insertions(+), 68 deletions(-) diff --git a/lib/data.js b/lib/data.js index ddecf300c0b..a130c25991c 100644 --- a/lib/data.js +++ b/lib/data.js @@ -7,12 +7,12 @@ var ObjectID = require('mongodb').ObjectID; function uniq(a) { var seen = {}; - return a.filter(function(item) { + return a.filter(function (item) { return seen.hasOwnProperty(item.x) ? false : (seen[item.x] = true); }); } -function init (env, ctx) { +function init(env, ctx) { var data = { sgvs: [] @@ -45,7 +45,7 @@ function init (env, ctx) { return dir2Char[direction] || '-'; } - data.clone = function clone ( ) { + data.clone = function clone() { return _.cloneDeep(data, function (value) { //special handling of mongo ObjectID's //see https://github.com/lodash/lodash/issues/602#issuecomment-47414964 @@ -55,7 +55,7 @@ function init (env, ctx) { }); }; - data.update = function update (done) { + data.update = function update(done) { console.log('running data.update'); data.lastUpdated = Date.now(); @@ -63,15 +63,15 @@ function init (env, ctx) { var earliest_data = data.lastUpdated - TWO_DAYS; var treatment_earliest_data = data.lastUpdated - (ONE_DAY * 8); - function sort (values) { - values.sort(function sorter (a, b) { + function sort(values) { + values.sort(function sorter(a, b) { return a.x - b.x; }); } async.parallel({ entries: function (callback) { - var q = { find: {"date": {"$gte": earliest_data}} }; + var q = {find: {"date": {"$gte": earliest_data}}}; ctx.entries.list(q, function (err, results) { if (!err && results) { var mbgs = []; @@ -99,7 +99,7 @@ function init (env, ctx) { }) }, cal: function (callback) { //FIXME: date $gte????? - var cq = { count: 1, find: {"type": "cal"} }; + var cq = {count: 1, find: {"type": "cal"}}; ctx.entries.list(cq, function (err, results) { if (!err && results) { var cals = []; @@ -115,7 +115,7 @@ function init (env, ctx) { callback(); }); }, treatments: function (callback) { - var tq = { find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}} }; + var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { var treatments = []; @@ -126,7 +126,7 @@ function init (env, ctx) { }); //FIXME: sort in mongo - treatments.sort(function(a, b) { + treatments.sort(function (a, b) { return a.x - b.x; }); @@ -163,7 +163,7 @@ function init (env, ctx) { }; - data.calculateDelta = function calculateDelta (lastData) { + data.calculateDelta = function calculateDelta(lastData) { var delta = {'delta': true}; var changesFound = false; @@ -171,80 +171,71 @@ function init (env, ctx) { // if there's no updates done so far, just return the full set if (!lastData.sgvs) return data; - console.log('lastData.sgvs last record time', lastData.sgvs[lastData.sgvs.length-1].x); - console.log('d.sgvslast record time', data.sgvs[data.sgvs.length-1].x); - function nsArrayDiff(oldArray, newArray) { var seen = {}; var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + for (var i = 0; i < l; i++) { + seen[oldArray[i].x] = true + } var result = []; l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + for (var j = 0; j < l; j++) { + if (!seen.hasOwnProperty(newArray[j].x)) { + result.push(newArray[j]); + } + } return result; } - function sort (values) { - values.sort(function sorter (a, b) { + function sort(values) { + values.sort(function sorter(a, b) { return a.x - b.x; }); } - var sgvDelta = nsArrayDiff(lastData.sgvs, data.sgvs); - - if (sgvDelta.length > 0) { - console.log('sgv changes found'); - changesFound = true; - sort(sgvDelta); - delta.sgvs = sgvDelta; - } - - var treatmentDelta = nsArrayDiff(lastData.treatments, data.treatments); - - if (treatmentDelta.length > 0) { - console.log('treatment changes found'); - changesFound = true; - sort(treatmentDelta); - delta.treatments = treatmentDelta; - } - - var mbgsDelta = nsArrayDiff(lastData.mbgs, data.mbgs); - - if (mbgsDelta.length > 0) { - console.log('mbgs changes found'); - changesFound = true; - sort(mbgsDelta); - delta.mbgs = mbgsDelta; - } - - var calsDelta = nsArrayDiff(lastData.cals, data.cals); - - if (calsDelta.length > 0) { - console.log('cals changes found'); - changesFound = true; - sort(calsDelta); - delta.cals = calsDelta; - } - - if (JSON.stringify(lastData.devicestatus) != JSON.stringify(data.devicestatus)) { - console.log('devicestatus changes found'); - changesFound = true; - delta.devicestatus = data.devicestatus; - } - - if (JSON.stringify(lastData.profiles) != JSON.stringify(data.profiles)) { - console.log('profile changes found'); - changesFound = true; - delta.profiles = data.profiles; + delta.lastUpdated = data.lastUpdated; + + // array compression + var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; + + for (var array in compressibleArrays) { + var a = compressibleArrays[array]; + if (data.hasOwnProperty(a)) { + + // if previous data doesn't have the property (first time delta?), just assign data over + if (!lastData.hasOwnProperty(a)) { + delta[a] = data[a]; + changesFound = true; + continue; + } + + // Calculate delta and assign delta over if changes were found + var deltaData = nsArrayDiff(lastData[a], data[a]); + if (deltaData.length > 0) { + console.log('delta changes found on', a); + changesFound = true; + sort(deltaData); + delta[a] = deltaData; + } + } } - if (changesFound) { - console.log('changes found'); - return delta; + // objects + var skippableObjects = ['profiles', 'devicestatus']; + + for (var object in skippableObjects) { + var o = skippableObjects[object]; + if (data.hasOwnProperty(o)) { + if (JSON.stringify(data[o]) != JSON.stringify(lastData[o])) { + console.log('delta changes found on', o); + changesFound = true; + delta[o] = data[o]; + } + } } + if (changesFound) return delta; return data; - }; From f6b33e86da2df3c81bd081c2ef5d75401525148e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 08:28:11 -0700 Subject: [PATCH 115/661] don't blow up if pushover isn't configured --- lib/pushnotify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 205df593ada..33abf10b91d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -29,6 +29,7 @@ function init(env, ctx) { var lastEmit = {}; pushnotify.emitNotification = function emitNotification (notify) { + if (!pushover) return; //make this smarter, for now send alerts every 15mins till cleared if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; @@ -51,6 +52,7 @@ function init(env, ctx) { }; pushnotify.process = function process(sbx) { + if (!pushover) return; sendMBG(sbx); sendTreatment(sbx) }; From 144ed5ad0acf1b4040d38123a8580f7442376b91 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 17:42:20 -0700 Subject: [PATCH 116/661] date on mbg at time of pushnotify processing is in the x field, need to clean that up --- lib/pushnotify.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 33abf10b91d..ac850b6072d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -111,7 +111,8 @@ function init(env, ctx) { var lastMBG = _.last(ctx.data.mbgs); if (!lastMBG) return; - var ago = new Date().getTime() - lastMBG.date; + //TODO: figure out why date is x here, clean up data model + var ago = new Date().getTime() - lastMBG.x; if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { return; From c9711b86040c6410c6915732af175968f9346dc0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 18:26:19 -0700 Subject: [PATCH 117/661] don't blow up if the target_high or target_low fields aren't set --- lib/plugins/boluswizardpreview.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 3074bcebf57..ac9dd496ee4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -74,6 +74,10 @@ function init() { var profile = sbx.data.profile; + if (!profile.target_high || !profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + } + var iob = results.iob = sbx.properties.iob.iob; results.effect = iob * profile.sens; From fe3c3d2ed3ca32a8dccfa0162f0127fcd8c02468 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 18:33:24 -0700 Subject: [PATCH 118/661] also make sure there is a treatment profile --- lib/plugins/boluswizardpreview.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index ac9dd496ee4..2e58dccfdc4 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -74,8 +74,14 @@ function init() { var profile = sbx.data.profile; + if (!profile) { + console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); + return results; + } + if (!profile.target_high || !profile.target_low) { console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + return results; } var iob = results.iob = sbx.properties.iob.iob; From 6f14bc2ecc6bb683ad76fcd54d66870d2cc0b81a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 11 Jun 2015 21:39:32 -0700 Subject: [PATCH 119/661] include IOB in prediction message if available --- lib/plugins/ar2.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 9588868f0a4..17cfeeafc85 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -50,7 +50,6 @@ function init() { var min = _.max([first, last, avg]); var rangeLabel = ''; - if (max > sbx.thresholds.bg_target_top) { rangeLabel = 'HIGH'; if (!pushoverSound) pushoverSound = 'climb' @@ -62,7 +61,17 @@ function init() { } var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var message = [predicted[2], sbx.unitsLabel, 'in 15mins'].join(' '); + var lines = [ + ['Now', sbx.data.lastSGV(), sbx.unitsLabel].join(' ') + , ['15m', predicted[2], sbx.unitsLabel].join(' ') + ]; + + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.unshift(['\nIOB:', iob, 'U'].join(' ')); + } + + var message = lines.join('\n'); forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); From 8a16d147092651177ddfa822811d31a222ad6833 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 14:32:28 -0700 Subject: [PATCH 120/661] factored MBG/Calibratoin and Treatment notificaions into a new plugin; auto snooze for 10m after any treatment --- env.js | 5 +- lib/bootevent.js | 1 - lib/notifications.js | 22 ++- lib/plugins/boluswizardpreview.js | 10 +- lib/plugins/cob.js | 11 ++ lib/plugins/index.js | 1 + lib/plugins/treatmentnotify.js | 86 ++++++++++ lib/pushnotify.js | 254 ++++++------------------------ lib/sandbox.js | 14 +- 9 files changed, 182 insertions(+), 222 deletions(-) create mode 100644 lib/plugins/treatmentnotify.js diff --git a/env.js b/env.js index 38eeda38200..3be410ef33c 100644 --- a/env.js +++ b/env.js @@ -178,9 +178,12 @@ function config ( ) { env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); if (env.pushover_api_token && env.pushover_user_key) { - env.enable = env.enable + ' pushover'; + env.enable += ' pushover'; + //TODO: after config changes are documented this shouldn't be auto enabled + env.enable += ' treatmentnotify'; } + // TODO: clean up a bit // Some people prefer to use a json configuration file instead. // This allows a provided json config to override environment variables diff --git a/lib/bootevent.js b/lib/bootevent.js index e0ecfc2ee34..e4af413f07c 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -56,7 +56,6 @@ function boot (env) { ctx.plugins.setProperties(sbx); ctx.notifications.initRequests(); ctx.plugins.checkNotifications(sbx); - ctx.pushnotify.process(sbx); ctx.notifications.process(sbx); ctx.bus.emit('data-processed'); }); diff --git a/lib/notifications.js b/lib/notifications.js index ecd5c003cb0..6680b690e29 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -12,8 +12,9 @@ var Alarm = function(label) { // list of alarms with their thresholds var alarms = { - 1 : new Alarm('Warn'), - 2: new Alarm('Urgent') + 0: new Alarm('Info') + , 1: new Alarm('Warn') + , 2: new Alarm('Urgent') }; function init (env, ctx) { @@ -21,6 +22,23 @@ function init (env, ctx) { return notifications; } + notifications.levels = { + URGENT: 2 + , WARN: 1 + , INFO: 0 + }; + + notifications.levels.toString = function levelToString(level) { + switch (level) { + case 2: + return 'Urgent'; + case 1: + return 'Warn'; + case 0: + return 'Info'; + } + }; + //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 2e58dccfdc4..f60c7f0480e 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -13,7 +13,7 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; - bwp.checkNotifications = function checkNotifications(sbx) { + bwp.checkNotifications = function checkNotifications (sbx) { var results = bwp.calc(sbx); if (results.lastSGV < sbx.data.profile.target_high) return; @@ -25,13 +25,13 @@ function init() { if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ - level: 2 + level: sbx.notifications.levels.URGENT , mills: FIFTEEN_MINS , debug: results }) } else if (results.bolusEstimate > warnBWP) { - var level = results.bolusEstimate > urgentBWP ? 2 : 1; - var levelLabel = results.bolusEstimate > urgentBWP ? 'Urgent' : 'Warning'; + var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; + var levelLabel = sbx.notifications.levels.toString(level); var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); sbx.notifications.requestNotify({ level: level @@ -80,7 +80,7 @@ function init() { } if (!profile.target_high || !profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must hava a both target_high and target_low fields'); + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); return results; } diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index eab827c483c..7ce5e71619e 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -21,6 +21,17 @@ function init() { }; cob.cobTotal = function cobTotal(treatments, profile, time) { + + if (!profile) { + console.warn('For the COB plugin to function you need a treatment profile'); + return {}; + } + + if (!profile.sens || !profile.carbratio) { + console.warn('For the CPB plugin to function your treatment profile must have both sens and carbratio fields'); + return {}; + } + var liverSensRatio = 1; var sens = profile.sens; var carbratio = profile.carbratio; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5242f27f7a2..72953881e72 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -20,6 +20,7 @@ function init() { , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() + , require('./treatmentnotify')() ]); return plugins; }; diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js new file mode 100644 index 00000000000..ad5829cb041 --- /dev/null +++ b/lib/plugins/treatmentnotify.js @@ -0,0 +1,86 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + var TIME_10_MINS_MS = 10 * 60 * 1000; + + function treatmentnotify() { + return treatmentnotify; + } + + treatmentnotify.label = 'Treatment Notifications'; + treatmentnotify.pluginType = 'notification'; + + treatmentnotify.checkNotifications = function checkNotifications (sbx) { + + var now = Date.now(); + + var lastMBG = _.last(sbx.data.mbgs); + var lastTreatment = _.last(sbx.data.treatments); + + //TODO: figure out why date is x here #CleanUpDataModel + var mbgAgo = (lastMBG && lastMBG.x < now) ? now - lastMBG.x : 0; + var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; + + var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; + var treatmentAgo = (lastTreatmentTime && lastTreatmentTime < now) ? now - lastTreatmentTime : 0; + var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; + + if (mbgCurrent || treatmentCurrent) { + //first auto snooze any alarms + sbx.notifications.requestSnooze({ + level: sbx.notifications.levels.URGENT + , mills: TIME_10_MINS_MS + //, debug: results + }); + + //and add some info notifications + //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly + if (mbgCurrent) requestMBGNotify(lastMBG, sbx); + if (treatmentCurrent) requestTreatmentNotify(lastTreatment, sbx); + } + }; + + function requestMBGNotify (lastMBG, sbx) { + sbx.notifications.requestNotify({ + level: sbx.notifications.levels.INFO + , title: 'Calibration' + //TODO: figure out why mbg is y here #CleanUpDataModel + , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , pushoverSound: 'magic' + //, debug: results + }); + } + + function requestTreatmentNotify (lastTreatment, sbx) { + var message = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + + (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs + 'g' : '') + + + //TODO: find a better way to connect split treatments + //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + + (lastTreatment.insulin ? '\nInsulin: ' + sbx.roundInsulinForDisplayFormat(lastTreatment.insulin) + 'U' : '')+ + (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + + + //TODO: find a better way to store timeAdjustment + //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + + + (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); + + + sbx.notifications.requestNotify({ + level: sbx.notifications.levels.INFO + , title: lastTreatment.eventType + , message: message +// , debug: results + }); + + } + + return treatmentnotify(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/pushnotify.js b/lib/pushnotify.js index ac850b6072d..c63cc9d1b48 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var crypto = require('crypto'); var units = require('./units')(); function init(env, ctx) { @@ -8,31 +9,21 @@ function init(env, ctx) { var pushover = require('./pushover')(env); // declare local constants for time differences - var TIME_10_MINS_MS = 10 * 60 * 1000, - TIME_15_MINS_S = 15 * 60, - TIME_15_MINS_MS = TIME_15_MINS_S * 1000, - TIME_30_MINS_MS = TIME_15_MINS_MS * 2; - - var lastSentMBG = null; - var lastSentTreatment = null; - - //simple SGV Alert throttling - //TODO: single snooze for websockets and push (when we add push callbacks) - var lastAlert = 0; - var lastSGVDate = 0; - + var TIME_5_MINS_MS = 5 * 60 * 1000 + ,TIME_15_MINS_S = 15 * 60 + , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 + ; function pushnotify() { return pushnotify; } - var lastEmit = {}; + var recentlySent = {}; pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; - //make this smarter, for now send alerts every 15mins till cleared - if (lastEmit[notify.level] && lastEmit[notify.level] > Date.now() - TIME_15_MINS_MS) return; + if (isDuplicate(notify)) return; var msg = { expire: TIME_15_MINS_S, @@ -44,216 +35,67 @@ function init(env, ctx) { retry: 30 }; - pushover.send( msg, function( err, result ) { - console.info('pushnotify.emitNotification', err, result); + pushover.send(msg, function(err, result) { + updateRecentlySent(err, notify); + if (err) { + console.error('unable to send pushover notification', err); + } else { + console.info('sent pushover notification: ', msg, 'result: ', result); + } }); - lastEmit[notify.level] = Date.now(); - }; - - pushnotify.process = function process(sbx) { - if (!pushover) return; - sendMBG(sbx); - sendTreatment(sbx) }; - function sendTreatment (sbx) { - - var lastTreatment = _.last(ctx.data.treatments); - if (!lastTreatment) return; - - var ago = new Date().getTime() - new Date(lastTreatment.created_at).getTime(); - - if (JSON.stringify(lastTreatment) == JSON.stringify(lastSentTreatment) || ago > TIME_10_MINS_MS) { - return; - } - - //since we don't know the time zone on the device viewing the push message - //we can only show the amount of adjustment - //TODO: need to store time extra info to figure out if treatment was added in past/future - //var timeAdjustment = calcTimeAdjustment(eventTime); - - var text = (lastTreatment.glucose ? 'BG: ' + lastTreatment.glucose + ' (' + lastTreatment.glucoseType + ')' : '') + - (lastTreatment.carbs ? '\nCarbs: ' + lastTreatment.carbs : '') + - - //TODO: find a better way to connect split treatments - //(preBolusCarbs ? '\nCarbs: ' + preBolusCarbs + ' (in ' + treatment.preBolus + ' minutes)' : '')+ + function isDuplicate(notify) { + var byLevel = sentByLevel(notify); + var hash = notifyToHash(notify); - (lastTreatment.insulin ? '\nInsulin: ' + lastTreatment.insulin : '')+ - (lastTreatment.enteredBy ? '\nEntered By: ' + lastTreatment.enteredBy : '') + - - //TODO: find a better way to store timeAdjustment - //(timeAdjustment ? '\nEvent Time: ' + timeAdjustment : '') + - - (lastTreatment.notes ? '\nNotes: ' + lastTreatment.notes : ''); - - var msg = { - expire: TIME_15_MINS_S, - message: text, - title: lastTreatment.eventType, - sound: 'gamelan', - timestamp: new Date( ), - priority: (lastTreatment.eventType == 'Note' ? -1 : 0), - retry: 30 - }; - - pushover.send( msg, function( err, result ) { - console.log(result); + var found = _.find(byLevel, function findByHash(sent) { + return sent.hash = hash; }); - lastSentTreatment = lastTreatment; - - } - - - function sendMBG(sbx) { - - var lastMBG = _.last(ctx.data.mbgs); - if (!lastMBG) return; - - //TODO: figure out why date is x here, clean up data model - var ago = new Date().getTime() - lastMBG.x; - - if (JSON.stringify(lastMBG) == JSON.stringify(lastSentMBG) || ago > TIME_10_MINS_MS) { - return; + if (found) { + console.info('Found duplicate notification that was sent recently using hash: ', hash, 'of notify: ', notify); + return true; + } else { + console.info('No duplicate notification found, using hash: ', hash, 'of notify: ', notify); + return false; } - var mbg = lastMBG.y; - if (env.DISPLAY_UNITS == 'mmol') { - mbg = units.mgdlToMMOL(mbg); - } - var msg = { - expire: TIME_15_MINS_S, - message: '\nMeter BG: ' + mbg + ' ' + sbx.unitsLabel, - title: 'Calibration', - sound: 'magic', - timestamp: new Date(lastMBG.date), - priority: 0, - retry: 30 - }; + } - pushover.send(msg, function (err, result) { - console.log(result); + function updateRecentlySent(err, notify) { + sentByLevel(notify).push({ + time: Date.now() + , err: err + , hash: notifyToHash(notify) }); - - lastSentMBG = lastMBG; - } - //TODO: move some of this to simplealarms - function old(entry, ctx) { - - if (!entry.sgv || entry.type != 'sgv') { - return; - } - - var now = new Date().getTime(), - offset = new Date().getTime() - entry.date; - - if (offset > TIME_10_MINS_MS || entry.date == lastSGVDate) { - console.info('No SVG Pushover, offset: ' + offset + ' too big, doc.date: ' + entry.date + ', now: ' + new Date().getTime() + ', lastSGVDate: ' + lastSGVDate); - return; + function sentByLevel(notify) { + var byLevel = recentlySent[notify.level]; + if (!byLevel) { + byLevel = []; } - // initialize message data - var sinceLastAlert = now - lastAlert, - title = 'CGM Alert', - priority = 0, - sound = null, - readingtime = entry.date, - readago = now - readingtime; + var now = Date.now(); - console.info('now: ' + now); - console.info('doc.sgv: ' + entry.sgv); - console.info('doc.direction: ' + entry.direction); - console.info('doc.date: ' + entry.date); - console.info('readingtime: ' + readingtime); - console.info('readago: ' + readago); - - // set vibration pattern; alert value; 0 nothing, 1 normal, 2 low, 3 high - if (entry.sgv < 39) { - if (sinceLastAlert > TIME_30_MINS_MS) { - title = 'CGM Error'; - priority = 1; - sound = 'persistent'; - } - } else if (entry.sgv < env.thresholds.bg_low && sinceLastAlert > TIME_15_MINS) { - title = 'Urgent Low'; - priority = 2; - sound = 'persistent'; - } else if (entry.sgv < env.thresholds.bg_target_bottom && sinceLastAlert > TIME_15_MINS) { - title = 'Low'; - priority = 1; - sound = 'falling'; - } else if (entry.sgv < 120 && entry.direction == 'DoubleDown') { - title = 'Double Down'; - priority = 1; - sound = 'falling'; - } else if (entry.sgv == 100 && entry.direction == 'Flat' && sinceLastAlert > TIME_15_MINS) { //Perfect Score - a good time to take a picture :) - title = 'Perfect'; - priority = 0; - sound = 'cashregister'; - } else if (entry.sgv > 120 && entry.direction == 'DoubleUp' && sinceLastAlert > TIME_15_MINS) { - title = 'Double Up'; - priority = 1; - sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_target_top && sinceLastAlert > TIME_30_MINS_MS) { - title = 'High'; - priority = 1; - sound = 'climb'; - } else if (entry.sgv > env.thresholds.bg_high && sinceLastAlert > TIME_30_MINS_MS) { - title = 'Urgent High'; - priority = 1; - sound = 'persistent'; - } - - if (sound != null) { - lastAlert = now; - - var msg = { - expire: 14400, // 4 hours - message: 'BG NOW: ' + entry.sgv, - title: title, - sound: sound, - timestamp: new Date(entry.date), - priority: priority, - retry: 30 - }; - - pushover.send(msg, function (err, result) { - console.log(result); - }); - } + byLevel = _.filter(byLevel, function isRecent(sent) { + //consider errors stale sooner than successful sends + var staleAfter = sent.err ? TIME_5_MINS_MS : TIME_15_MINS_MS; + return now - sent.time < staleAfter; + }); + recentlySent[notify.level] = byLevel; - lastSGVDate = entry.date; + return byLevel; } - function calcTimeAdjustment(eventTime) { - - if (!eventTime) return null; - - var now = (new Date()).getTime(), - other = eventTime.getTime(), - past = other < now, - offset = Math.abs(now - other); - - var MINUTE = 60 * 1000, - HOUR = 3600 * 1000; - - var parts = {}; - - if (offset <= MINUTE) - return 'now'; - else if (offset < (HOUR * 2)) - parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins' }; - else - parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs' }; - - if (past) - return parts.value + ' ' + parts.label + ' ago'; - else - return 'in ' + parts.value + ' ' + parts.label; + function notifyToHash(notify) { + var hash = crypto.createHash('sha1'); + var info = JSON.stringify(_.pick(notify, ['title', 'message'])); + hash.update(info); + return hash.digest('hex'); } return pushnotify(); diff --git a/lib/sandbox.js b/lib/sandbox.js index b5cb8acf067..2c4c6bb9127 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -7,7 +7,7 @@ var utils = require('./utils'); function init ( ) { var sbx = {}; - function init() { + function reset () { sbx.properties = []; sbx.scaleBg = scaleBg; sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; @@ -31,8 +31,8 @@ function init ( ) { * @param ctx - created from bootevent * @returns {{sbx}} */ - sbx.serverInit = function serverInit(env, ctx) { - init(); + sbx.serverInit = function serverInit (env, ctx) { + reset(); sbx.time = Date.now(); sbx.units = env.DISPLAY_UNITS; @@ -42,7 +42,7 @@ function init ( ) { sbx.data = ctx.data.clone(); //don't expose all of notifications, ctx.notifications will decide what to do after all plugins chime in - sbx.notifications = _.pick(ctx.notifications, ['requestNotify', 'requestSnooze', 'requestClear']); + sbx.notifications = _.pick(ctx.notifications, ['levels', 'requestNotify', 'requestSnooze', 'requestClear']); //Plugins will expect the right profile based on time sbx.data.profile = _.first(ctx.data.profiles); @@ -65,8 +65,8 @@ function init ( ) { * @param data - svgs, treatments, profile, etc * @returns {{sbx}} */ - sbx.clientInit = function clientInit(app, clientSettings, time, pluginBase, data) { - init(); + sbx.clientInit = function clientInit (app, clientSettings, time, pluginBase, data) { + reset(); sbx.units = clientSettings.units; sbx.defaults = clientSettings; //TODO: strip out extra stuff @@ -88,7 +88,7 @@ function init ( ) { * @param name * @param setter */ - sbx.offerProperty = function offerProperty(name, setter) { + sbx.offerProperty = function offerProperty (name, setter) { if (!sbx.properties.hasOwnProperty(name)) { var value = setter(); if (value) { From a9e5e10341d4859444ad28137956db46bbd324c6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 16:16:35 -0700 Subject: [PATCH 121/661] make sure infos are always sent, only alarms (warn/urgent) get snoozed --- lib/notifications.js | 30 ++++++++++++++++++++++-------- lib/plugins/treatmentnotify.js | 24 +++++++++++++++--------- lib/websocket.js | 8 ++++++-- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 6680b690e29..0dce5a503fb 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -22,12 +22,14 @@ function init (env, ctx) { return notifications; } - notifications.levels = { + var levels = { URGENT: 2 , WARN: 1 , INFO: 0 }; + notifications.levels = levels; + notifications.levels.toString = function levelToString(level) { switch (level) { case 2: @@ -80,8 +82,16 @@ function init (env, ctx) { notifications.initRequests(); - function findHighestNotify ( ) { - return _.find(requests.notifies, {level: 'urgent'}) || _.find(requests.notifies, {level: 'warn'}) || _.first(requests.notifies); + /** + * Find the first URGENT or first WARN + * @returns a notification or undefined + */ + function findHighestAlarm ( ) { + return _.find(requests.notifies, {level: levels.URGENT}) || _.find(requests.notifies, {level: levels.WARN}); + } + + function findInfos ( ) { + return _.filter(requests.notifies, {level: levels.INFO}); } function findLongestSnooze ( ) { @@ -103,23 +113,27 @@ function init (env, ctx) { }; notifications.process = function process ( ) { - var highestNotify = findHighestNotify(); + var highestAlarm = findHighestAlarm(); var longestSnooze = findLongestSnooze(); if (longestSnooze) { - if (highestNotify && highestNotify.level > longestSnooze.level) { - console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestNotify); + if (highestAlarm && highestAlarm.level > longestSnooze.level) { + console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestAlarm); } else { console.log('notifications.process, snoozing because: ', longestSnooze); notifications.ack(longestSnooze.level, longestSnooze.mills) } } - if (highestNotify) { - emitNotification(highestNotify); + if (highestAlarm) { + emitNotification(highestAlarm); } else { autoAckAlarms(); } + + findInfos().forEach(function eachInfo (info) { + emitNotification(info); + }) }; notifications.ack = function ack (level, time) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index ad5829cb041..0cfd79fd86b 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -21,7 +21,8 @@ function init() { var lastTreatment = _.last(sbx.data.treatments); //TODO: figure out why date is x here #CleanUpDataModel - var mbgAgo = (lastMBG && lastMBG.x < now) ? now - lastMBG.x : 0; + var lastMBGTime = lastMBG ? lastMBG.x : 0; + var mbgAgo = (lastMBGTime && lastMBGTime < now) ? now - lastMBGTime : 0; var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; @@ -29,13 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - //first auto snooze any alarms - sbx.notifications.requestSnooze({ - level: sbx.notifications.levels.URGENT - , mills: TIME_10_MINS_MS - //, debug: results - }); - + autoSnoozeAlarms(lastMBGTime, lastTreatmentTime); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -43,10 +38,21 @@ function init() { } }; + /** + * auto snooze any alarms by max(treatment, mbg) time + 10m + */ + function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime) { + sbx.notifications.requestSnooze({ + level: sbx.notifications.levels.URGENT + , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS + //, debug: results + }); + } + function requestMBGNotify (lastMBG, sbx) { sbx.notifications.requestNotify({ level: sbx.notifications.levels.INFO - , title: 'Calibration' + , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel , pushoverSound: 'magic' diff --git a/lib/websocket.js b/lib/websocket.js index a2028656755..ef6b0c83896 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -13,6 +13,8 @@ function init (env, ctx, server) { var watchers = 0; var lastData = {}; + var levels = ctx.notifications.levels; + function start ( ) { io = require('socket.io')({ 'transports': ['xhr-polling'], 'log level': 0 @@ -66,10 +68,12 @@ function init (env, ctx, server) { if (notify.clear) { io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); - } else if (notify.level == 1) { + } else if (notify.level == levels.INFO) { + //client doesn't know how to display info notifications yet, ignoring + } else if (notify.level == levels.WARN) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); - } else if (notify.level == 2) { + } else if (notify.level == levels.URGENT) { io.emit('urgent_alarm', notify); console.info('emitted urgent_alarm to all clients'); } From 1731fdc18316b087323f4f9690b94e8345995d70 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 17:38:14 -0700 Subject: [PATCH 122/661] init tests for notifications --- lib/notifications.js | 18 ++++++------- tests/notifications.test.js | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 tests/notifications.test.js diff --git a/lib/notifications.js b/lib/notifications.js index 0dce5a503fb..f4bfce3b55f 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -86,15 +86,15 @@ function init (env, ctx) { * Find the first URGENT or first WARN * @returns a notification or undefined */ - function findHighestAlarm ( ) { + notifications.findHighestAlarm = function findHighestAlarm ( ) { return _.find(requests.notifies, {level: levels.URGENT}) || _.find(requests.notifies, {level: levels.WARN}); - } + }; - function findInfos ( ) { + notifications.findInfos = function findInfos ( ) { return _.filter(requests.notifies, {level: levels.INFO}); - } + }; - function findLongestSnooze ( ) { + notifications.findLongestSnooze = function findLongestSnooze ( ) { if (_.isEmpty(requests.snoozes)) return null; var groups = _.groupBy(requests.snoozes, 'level'); @@ -102,7 +102,7 @@ function init (env, ctx) { var longest = firstKey && _.last(groups[firstKey].sort()); return longest; - } + }; notifications.requestNotify = function requestNotify (notify) { requests.notifies.push(notify); @@ -113,8 +113,8 @@ function init (env, ctx) { }; notifications.process = function process ( ) { - var highestAlarm = findHighestAlarm(); - var longestSnooze = findLongestSnooze(); + var highestAlarm = notifications.findHighestAlarm(); + var longestSnooze = notifications.findLongestSnooze(); if (longestSnooze) { if (highestAlarm && highestAlarm.level > longestSnooze.level) { @@ -131,7 +131,7 @@ function init (env, ctx) { autoAckAlarms(); } - findInfos().forEach(function eachInfo (info) { + notifications.findInfos().forEach(function eachInfo (info) { emitNotification(info); }) }; diff --git a/tests/notifications.test.js b/tests/notifications.test.js new file mode 100644 index 00000000000..1ed633f968c --- /dev/null +++ b/tests/notifications.test.js @@ -0,0 +1,51 @@ +var should = require('should'); +var Stream = require('stream'); + +describe('units', function ( ) { + + var env = {}; + var ctx = { + bus: new Stream + , data: { + lastUpdated: Date.now() + } + }; + + var notifications = require('../lib/notifications')(env, ctx); + + it('initAndReInit', function (done) { + notifications.initRequests(); + var notify = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + notifications.requestNotify(notify); + notifications.findHighestAlarm().should.equal(notify); + notifications.initRequests(); + should.not.exist(notifications.findHighestAlarm()); + done(); + }); + + + it('emitANotification', function (done) { + + //if notification doesn't get called test will time out + ctx.bus.on('notification', function callback ( ) { + done(); + }); + + notifications.initRequests(); + var notify = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + notifications.requestNotify(notify); + notifications.findHighestAlarm().should.equal(notify); + notifications.process(); + + }); + + +}); From 0097176ccb1bf1b799e67789232555296e20d4e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 18:05:02 -0700 Subject: [PATCH 123/661] forgot to pass the sbx --- lib/plugins/treatmentnotify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 0cfd79fd86b..56cd819d4c3 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -30,7 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - autoSnoozeAlarms(lastMBGTime, lastTreatmentTime); + autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -41,7 +41,7 @@ function init() { /** * auto snooze any alarms by max(treatment, mbg) time + 10m */ - function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime) { + function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS From 3fe4a4bf2c953a2cfd4e719f7a86d6df4ada30eb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 12 Jun 2015 18:27:39 -0700 Subject: [PATCH 124/661] more notification tests (and fixes for the bugs they found) --- lib/notifications.js | 34 ++++++++------- tests/notifications.test.js | 83 ++++++++++++++++++++++++++++++------- 2 files changed, 86 insertions(+), 31 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index f4bfce3b55f..1de7c5c9a40 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -94,14 +94,22 @@ function init (env, ctx) { return _.filter(requests.notifies, {level: levels.INFO}); }; - notifications.findLongestSnooze = function findLongestSnooze ( ) { - if (_.isEmpty(requests.snoozes)) return null; + notifications.snoozedBy = function snoozedBy (notify) { + if (_.isEmpty(requests.snoozes)) return false; - var groups = _.groupBy(requests.snoozes, 'level'); - var firstKey = _.first(_.keys(groups)); - var longest = firstKey && _.last(groups[firstKey].sort()); + var byLevel = _.filter(requests.snoozes, function checkSnooze (snooze) { + return snooze.level >= notify.level; + }); + var sorted = _.sortBy(byLevel, 'mills'); + var longest = _.last(sorted); - return longest; + var alarm = alarms[notify.level]; + + if (longest && Date.now() + longest.mills > alarm.lastAckTime + alarm.silenceTime) { + return longest; + } else { + return null; + } }; notifications.requestNotify = function requestNotify (notify) { @@ -114,15 +122,11 @@ function init (env, ctx) { notifications.process = function process ( ) { var highestAlarm = notifications.findHighestAlarm(); - var longestSnooze = notifications.findLongestSnooze(); - - if (longestSnooze) { - if (highestAlarm && highestAlarm.level > longestSnooze.level) { - console.log('notifications.process, ignoring snooze: ', longestSnooze, ' because notify: ', highestAlarm); - } else { - console.log('notifications.process, snoozing because: ', longestSnooze); - notifications.ack(longestSnooze.level, longestSnooze.mills) - } + + var snoozedBy = notifications.snoozedBy(highestAlarm); + if (snoozedBy) { + console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); + notifications.ack(snoozedBy.level, snoozedBy.mills) } if (highestAlarm) { diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 1ed633f968c..ba65ed8ef51 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -1,7 +1,7 @@ var should = require('should'); var Stream = require('stream'); -describe('units', function ( ) { +describe('notifications', function ( ) { var env = {}; var ctx = { @@ -13,15 +13,37 @@ describe('units', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var exampleWarn = { + title: 'test' + , message: 'testing' + , level: notifications.levels.WARN + }; + + var exampleUrgent = { + title: 'test' + , message: 'testing' + , level: notifications.levels.URGENT + }; + + var exampleSnooze = { + level: notifications.levels.WARN + , mills: 1000 + }; + + var exampleSnoozeNone = { + level: notifications.levels.WARN + , mills: -1000 + }; + + var exampleSnoozeUrgent = { + level: notifications.levels.URGENT + , mills: 1000 + }; + it('initAndReInit', function (done) { notifications.initRequests(); - var notify = { - title: 'test' - , message: 'testing' - , level: notifications.levels.WARN - }; - notifications.requestNotify(notify); - notifications.findHighestAlarm().should.equal(notify); + notifications.requestNotify(exampleWarn); + notifications.findHighestAlarm().should.equal(exampleWarn); notifications.initRequests(); should.not.exist(notifications.findHighestAlarm()); done(); @@ -29,23 +51,52 @@ describe('units', function ( ) { it('emitANotification', function (done) { - //if notification doesn't get called test will time out ctx.bus.on('notification', function callback ( ) { done(); }); notifications.initRequests(); - var notify = { - title: 'test' - , message: 'testing' - , level: notifications.levels.WARN - }; - notifications.requestNotify(notify); - notifications.findHighestAlarm().should.equal(notify); + notifications.requestNotify(exampleWarn); + notifications.findHighestAlarm().should.equal(exampleWarn); notifications.process(); + }); + it('Can be snoozed', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnooze); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnooze); + + done(); }); + it('Can be snoozed by last snooze', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnoozeNone); + notifications.requestSnooze(exampleSnooze); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnooze); + + done(); + }); + + it('Urgent alarms can\'t be snoozed by warn', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleUrgent); + notifications.requestSnooze(exampleSnooze); + should.not.exist(notifications.snoozedBy(exampleUrgent)); + + done(); + }); + + it('Warnings can be snoozed by urgent', function (done) { + notifications.initRequests(); + notifications.requestNotify(exampleWarn); + notifications.requestSnooze(exampleSnoozeUrgent); + notifications.snoozedBy(exampleWarn).should.equal(exampleSnoozeUrgent); + + done(); + }); }); From 10cbae52ff6b1e1eaea4b6743839a7937659144a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:10:26 -0700 Subject: [PATCH 125/661] don't allow requests for incomplete notifies or snoozes --- lib/notifications.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/notifications.js b/lib/notifications.js index 1de7c5c9a40..5a37fe8fb74 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -113,10 +113,18 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { + if (!notify.level || !notify.title || !notify.message) { + console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); + return; + } requests.notifies.push(notify); }; notifications.requestSnooze = function requestSnooze (snooze) { + if (!snooze.level || !snooze.mills) { + console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(notify))); + return; + } requests.snoozes.push(snooze); }; From f90cf78cae4c68c9a014e3d60b64f8c5ce2d9bd9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:13:31 -0700 Subject: [PATCH 126/661] when trying to find the position on the timeline for a treatment, don't consider sgvs < 40 --- static/js/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 97b950ed57b..4f7e7fdcf5d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1210,7 +1210,7 @@ function nsArrayDiff(oldArray, newArray) { function calcBGByTime(time) { var withBGs = _.filter(data, function(d) { - return d.y && d.type == 'sgv'; + return d.y > 39 && d.type == 'sgv'; }); var beforeTreatment = _.findLast(withBGs, function (d) { From 7d71341c8caaadb9d53bac15117dc0f491e8c0ac Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 09:26:51 -0700 Subject: [PATCH 127/661] need to make sure there is an alarm, before checking if it is snoozed --- lib/notifications.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 5a37fe8fb74..48b0f5fd159 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -131,13 +131,13 @@ function init (env, ctx) { notifications.process = function process ( ) { var highestAlarm = notifications.findHighestAlarm(); - var snoozedBy = notifications.snoozedBy(highestAlarm); - if (snoozedBy) { - console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.mills) - } - if (highestAlarm) { + var snoozedBy = notifications.snoozedBy(highestAlarm); + if (snoozedBy) { + console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); + notifications.ack(snoozedBy.level, snoozedBy.mills) + } + emitNotification(highestAlarm); } else { autoAckAlarms(); From 5f611f2ccdf647aa8dc31c61d46c0fc2517f260c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 10:33:45 -0700 Subject: [PATCH 128/661] tests for simplealarms plugin --- tests/simplealarms.test.js | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/simplealarms.test.js diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js new file mode 100644 index 00000000000..c4f47384181 --- /dev/null +++ b/tests/simplealarms.test.js @@ -0,0 +1,69 @@ +var should = require('should'); + +describe('simplealarms', function ( ) { + + var simplealarms = require('../lib/plugins/simplealarms')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning when above target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 181}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when really high', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 400}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a warning when below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 70}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when really low', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 40}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + simplealarms.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + +}); \ No newline at end of file From a2ac5fa61744853db2b5bd9039c1981a06efc06b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 11:07:32 -0700 Subject: [PATCH 129/661] just use the snooze time, don't add it to the last treatment time --- lib/plugins/treatmentnotify.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 56cd819d4c3..5e54e79a564 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -30,7 +30,7 @@ function init() { var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { - autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx); + autoSnoozeAlarms(sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly if (mbgCurrent) requestMBGNotify(lastMBG, sbx); @@ -38,13 +38,10 @@ function init() { } }; - /** - * auto snooze any alarms by max(treatment, mbg) time + 10m - */ - function autoSnoozeAlarms(lastMBGTime, lastTreatmentTime, sbx) { + function autoSnoozeAlarms(sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: Math.max(lastMBGTime, lastTreatmentTime) + TIME_10_MINS_MS + , mills: TIME_10_MINS_MS //, debug: results }); } From 826254052bf2df75d7dd0220ad6b2a32f420d37d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 12:09:31 -0700 Subject: [PATCH 130/661] better checking for required info in the BWP plugin --- lib/plugins/boluswizardpreview.js | 47 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index f60c7f0480e..fcc4eb34d32 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -13,7 +13,37 @@ function init() { bwp.label = 'Bolus Wizard Preview'; bwp.pluginType = 'pill-minor'; + function hasRequiredInfo (sbx) { + if (!sbx.data.profile) { + console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); + return false; + } + + if (!sbx.data.profile.target_high || !sbx.data.profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + return false; + } + + if (!sbx.properties.iob) { + console.warn('For the BolusWizardPreview plugin to function the IOB plugin must also be enabled'); + return false; + } + + if (!sbx.data.lastSGV()) { + console.warn('For the BolusWizardPreview plugin to function there needs to be a current SGV'); + return false; + } + + return true; + + } + bwp.checkNotifications = function checkNotifications (sbx) { + + if (!hasRequiredInfo(sbx)) { + return; + } + var results = bwp.calc(sbx); if (results.lastSGV < sbx.data.profile.target_high) return; @@ -45,6 +75,10 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { + if (!hasRequiredInfo(sbx)) { + return; + } + var results = bwp.calc(sbx); // display text @@ -70,20 +104,11 @@ function init() { results.lastSGV = sgv; - if (sgv == undefined || !sbx.properties.iob) return results; - - var profile = sbx.data.profile; - - if (!profile) { - console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); - return results; - } - - if (!profile.target_high || !profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + if (!hasRequiredInfo(sbx)) { return results; } + var profile = sbx.data.profile; var iob = results.iob = sbx.properties.iob.iob; results.effect = iob * profile.sens; From b25485cf4f2ed472676dd2fa3387d71ad15086f2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 12:38:02 -0700 Subject: [PATCH 131/661] added basic tests for AR2, and fixed old off-by-1 but that was requiring 3 points when only 2 are needed --- lib/plugins/ar2.js | 2 +- tests/ar2.test.js | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/ar2.test.js diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 17cfeeafc85..690ade39323 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -97,7 +97,7 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 1) { + if (lastIndex > 0) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/tests/ar2.test.js b/tests/ar2.test.js new file mode 100644 index 00000000000..a84f8febf66 --- /dev/null +++ b/tests/ar2.test.js @@ -0,0 +1,72 @@ +var should = require('should'); + +describe('ar2', function ( ) { + + var ar2 = require('../lib/plugins/ar2')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var now = Date.now(); + var before = now - (5 * 60 * 1000); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 100, x: before}, {y: 105, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning when going above target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 150, x: before}, {y: 170, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when going high fast', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 140, x: before}, {y: 200, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a warning when below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 90, x: before}, {y: 80, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('should trigger a urgent alarm when low fast', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 120, x: before}, {y: 80, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + +}); \ No newline at end of file From e19f2e2496d5c14229f63471cd04b664d7a00a2f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 14:42:06 -0700 Subject: [PATCH 132/661] rename snooze.mills to snooze.lengthMills --- lib/notifications.js | 8 ++++---- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/treatmentnotify.js | 2 +- tests/notifications.test.js | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 48b0f5fd159..d95607543a8 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -105,7 +105,7 @@ function init (env, ctx) { var alarm = alarms[notify.level]; - if (longest && Date.now() + longest.mills > alarm.lastAckTime + alarm.silenceTime) { + if (longest && Date.now() + longest.lengthMills > alarm.lastAckTime + alarm.silenceTime) { return longest; } else { return null; @@ -121,8 +121,8 @@ function init (env, ctx) { }; notifications.requestSnooze = function requestSnooze (snooze) { - if (!snooze.level || !snooze.mills) { - console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(notify))); + if (!snooze.level || !snooze.lengthMills) { + console.error(new Error('Unable to request snooze, since the snooze isn\'t complete: ' + JSON.stringify(snooze))); return; } requests.snoozes.push(snooze); @@ -135,7 +135,7 @@ function init (env, ctx) { var snoozedBy = notifications.snoozedBy(highestAlarm); if (snoozedBy) { console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.mills) + notifications.ack(snoozedBy.level, snoozedBy.lengthMills) } emitNotification(highestAlarm); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index fcc4eb34d32..20161fa9a06 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -56,7 +56,7 @@ function init() { if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: FIFTEEN_MINS + , lengthMills: FIFTEEN_MINS , debug: results }) } else if (results.bolusEstimate > warnBWP) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 5e54e79a564..cb8f7d81420 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -41,7 +41,7 @@ function init() { function autoSnoozeAlarms(sbx) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , mills: TIME_10_MINS_MS + , lengthMills: TIME_10_MINS_MS //, debug: results }); } diff --git a/tests/notifications.test.js b/tests/notifications.test.js index ba65ed8ef51..3e5e05a9261 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -27,17 +27,17 @@ describe('notifications', function ( ) { var exampleSnooze = { level: notifications.levels.WARN - , mills: 1000 + , lengthMills: 1000 }; var exampleSnoozeNone = { level: notifications.levels.WARN - , mills: -1000 + , lengthMills: 1 }; var exampleSnoozeUrgent = { level: notifications.levels.URGENT - , mills: 1000 + , lengthMills: 1000 }; it('initAndReInit', function (done) { From 41b14503ae8bfa0ae9323d4501e09fa807367d21 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 19:14:01 -0700 Subject: [PATCH 133/661] try adding codecov.io --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index ec7e4f21041..c7fa740dcbb 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ report: test -f ${ANALYZED} && \ (npm install coveralls && cat ${ANALYZED} | \ ./node_modules/.bin/coveralls) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codecov.io && cat ${ANALYZED} | \ + ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 4aeee25b92143761c4e9dc9091d03da7b4bb41c3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 19:14:01 -0700 Subject: [PATCH 134/661] try adding codecov.io --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index ec7e4f21041..c7fa740dcbb 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ report: test -f ${ANALYZED} && \ (npm install coveralls && cat ${ANALYZED} | \ ./node_modules/.bin/coveralls) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codecov.io && cat ${ANALYZED} | \ + ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 169a46129d6e71972046df4df416b401d6dec907 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 20:17:35 -0700 Subject: [PATCH 135/661] some BWP plugin tests --- lib/plugins/boluswizardpreview.js | 4 +- tests/boluswizardpreview.test.js | 66 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tests/boluswizardpreview.test.js diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 20161fa9a06..26089ded5f7 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -19,8 +19,8 @@ function init() { return false; } - if (!sbx.data.profile.target_high || !sbx.data.profile.target_low) { - console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both target_high and target_low fields'); + if (!sbx.data.profile.sens || !sbx.data.profile.target_high || !sbx.data.profile.target_low) { + console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both sens, target_high, and target_low fields'); return false; } diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js new file mode 100644 index 00000000000..5280d19e3ea --- /dev/null +++ b/tests/boluswizardpreview.test.js @@ -0,0 +1,66 @@ +var should = require('should'); + +describe('boluswizardpreview', function ( ) { + + var boluswizardpreview = require('../lib/plugins/boluswizardpreview')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var profile = { + sens: 90 + , target_high: 120 + , target_low: 100 + }; + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('trigger a warning when going out of range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 180}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + + done(); + }); + + it('trigger an urgent alarms when going too high', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 300}]; + ctx.data.profiles = [profile]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + sbx.offerProperty('iob', function () { + return {iob: 0} + }); + + boluswizardpreview.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + +}); \ No newline at end of file From 05e567b76404e1439219d8071b142483555f34dc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 20:37:33 -0700 Subject: [PATCH 136/661] added some tests for utils; made timeAgo take clientSettings as a param instead of getting lucky that browserSetting is global on the client --- lib/utils.js | 6 +++--- static/js/client.js | 6 +++--- tests/utils.test.js | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/utils.test.js diff --git a/lib/utils.js b/lib/utils.js index 6bc24037801..35761d80042 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -19,7 +19,7 @@ function init() { } }; - utils.timeAgo = function timeAgo(time) { + utils.timeAgo = function timeAgo(time, clientSettings) { var now = Date.now() , offset = time == -1 ? -1 : (now - time) / 1000 @@ -37,9 +37,9 @@ function init() { if (offset > DAY_IN_SECS * 7) { parts.status = 'warn'; - } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) { + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * clientSettings.alarmTimeAgoUrgentMins)) { parts.status = 'urgent'; - } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) { + } else if (offset > (MINUTE_IN_SECS * clientSettings.alarmTimeAgoWarnMins)) { parts.status = 'warn'; } else { parts.status = 'current'; diff --git a/static/js/client.js b/static/js/client.js index 4f7e7fdcf5d..1006e6f8acf 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -134,7 +134,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTitle() { var time = latestSGV ? new Date(latestSGV.x).getTime() : (prevSGV ? new Date(prevSGV.x).getTime() : -1) - , ago = timeAgo(time); + , ago = timeAgo(time, browserSettings); var bg_title = browserSettings.customTitle || ''; @@ -412,7 +412,7 @@ function nsArrayDiff(oldArray, newArray) { var value, time, ago, isCurrent; value = entry.y; time = new Date(entry.x).getTime(); - ago = timeAgo(time); + ago = timeAgo(time, browserSettings); isCurrent = ago.status === 'current'; if (value == 9) { @@ -1481,7 +1481,7 @@ function nsArrayDiff(oldArray, newArray) { function updateTimeAgo() { var lastEntry = $('#lastEntry') , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 - , ago = timeAgo(time) + , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); lastEntry.removeClass('current warn urgent'); diff --git a/tests/utils.test.js b/tests/utils.test.js new file mode 100644 index 00000000000..3d6873d7ff5 --- /dev/null +++ b/tests/utils.test.js @@ -0,0 +1,22 @@ +var should = require('should'); + +describe('utils', function ( ) { + var utils = require('../lib/utils')(); + + var clientSettings = { + alarmTimeAgoUrgentMins: 30 + , alarmTimeAgoWarnMins: 15 + }; + + it('shod format numbers', function () { + utils.toFixed(5.499999999).should.equal('5.50') + }); + + it('show format recent times to 1 minute', function () { + var result = utils.timeAgo(Date.now() - 30000, clientSettings); + result.value.should.equal(1); + result.label.should.equal('min ago'); + result.status.should.equal('current'); + }); + +}); From 1ded4d4b36a974c2ce1771ae611b002d6c709c28 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:12:04 -0700 Subject: [PATCH 137/661] another notification test; and bug fix for infos --- lib/notifications.js | 2 +- tests/notifications.test.js | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index d95607543a8..a5992b8d084 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -113,7 +113,7 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { - if (!notify.level || !notify.title || !notify.message) { + if (!notify.hasOwnProperty('level') || !notify.title || !notify.message) { console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); return; } diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 3e5e05a9261..215b4a0b709 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,6 +13,12 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var exampleInfo = { + title: 'test' + , message: 'testing' + , level: notifications.levels.INFO + }; + var exampleWarn = { title: 'test' , message: 'testing' @@ -50,7 +56,9 @@ describe('notifications', function ( ) { }); - it('emitANotification', function (done) { + it('emitAWarning', function (done) { + //start fresh to we don't pick up other notifications + ctx.bus = new Stream; //if notification doesn't get called test will time out ctx.bus.on('notification', function callback ( ) { done(); @@ -62,6 +70,23 @@ describe('notifications', function ( ) { notifications.process(); }); + it('emitAnInfo', function (done) { + //start fresh to we don't pick up other notifications + ctx.bus = new Stream; + //if notification doesn't get called test will time out + ctx.bus.on('notification', function callback (notify) { + if (!notify.clear) { + done(); + } + }); + + notifications.initRequests(); + notifications.requestNotify(exampleInfo); + should.not.exist(notifications.findHighestAlarm()); + + notifications.process(); + }); + it('Can be snoozed', function (done) { notifications.initRequests(); notifications.requestNotify(exampleWarn); From afa31a6a69ac547cee93654155e9a6518fac4cf4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:33:04 -0700 Subject: [PATCH 138/661] sandbox init tests --- tests/sandbox.test.js | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/sandbox.test.js diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js new file mode 100644 index 00000000000..60ee9b91e93 --- /dev/null +++ b/tests/sandbox.test.js @@ -0,0 +1,49 @@ +var should = require('should'); + +describe('sandbox', function ( ) { + var sandbox = require('../lib/sandbox')(); + + it('init on client', function (done) { + var app = { + thresholds:{ + bg_high: 260 + , bg_target_top: 180 + , bg_target_bottom: 80 + , bg_low: 55 + } + }; + + var clientSettings = { + units: 'mg/dl' + }; + + var pluginBase = {}; + var data = {sgvs: [{sgv: 100}]}; + + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.pluginBase.should.equal(pluginBase); + sbx.data.should.equal(data); + sbx.data.lastSGV().should.equal(100); + + done(); + }); + + it('init on server', function (done) { + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.data.sgvs = [{sgv: 100}]; + ctx.notifications = require('../lib/notifications')(env, ctx); + + var sbx = sandbox.serverInit(env, ctx); + + should.exist(sbx.notifications.requestNotify); + should.not.exist(sbx.notifications.process); + should.not.exist(sbx.notifications.ack); + sbx.data.lastSGV().should.equal(100); + + done(); + }); + +}); From 2696d8d984d581a0202578365ccb4dadbcfe04a5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 22:53:48 -0700 Subject: [PATCH 139/661] treatmentnotify tests --- tests/treatmentnotify.test.js | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 tests/treatmentnotify.test.js diff --git a/tests/treatmentnotify.test.js b/tests/treatmentnotify.test.js new file mode 100644 index 00000000000..0f6a41fe696 --- /dev/null +++ b/tests/treatmentnotify.test.js @@ -0,0 +1,74 @@ +var _ = require('lodash'); +var should = require('should'); + +describe('treatmentnotify', function ( ) { + + var treatmentnotify = require('../lib/plugins/treatmentnotify')(); + + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + it('Request a snooze for a recent treatment and request an info notify', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.treatments = [{eventType: 'BG Check', glucose: '100', created_at: (new Date()).toISOString()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.INFO); + + done(); + }); + + it('Not Request a snooze for an older treatment and not request an info notification', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.treatments = [{created_at: (new Date(Date.now() - (15 * 60 * 1000))).toISOString()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + should.not.exist(_.first(ctx.notifications.findInfos())); + + done(); + }); + + it('Request a snooze for a recent calibration and request an info notify', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.mbgs = [{y: '100', x: Date.now()}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.INFO); + + done(); + }); + + it('Not Request a snooze for an older calibration treatment and not request an info notification', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{sgv: 100}]; + ctx.data.mbgs = [{y: '100', x: Date.now() - (15 * 60 * 1000)}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + treatmentnotify.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + should.exist(ctx.notifications.snoozedBy({level: ctx.notifications.levels.URGENT})); + + should.not.exist(_.first(ctx.notifications.findInfos())); + + done(); + }); + + +}); \ No newline at end of file From 2a575d232606930738dcf5bbab7d85cfb00728fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:09:10 -0700 Subject: [PATCH 140/661] = now is ok --- lib/plugins/treatmentnotify.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index cb8f7d81420..2984401ad3f 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -22,12 +22,12 @@ function init() { //TODO: figure out why date is x here #CleanUpDataModel var lastMBGTime = lastMBG ? lastMBG.x : 0; - var mbgAgo = (lastMBGTime && lastMBGTime < now) ? now - lastMBGTime : 0; - var mbgCurrent = mbgAgo != 0 && mbgAgo < TIME_10_MINS_MS; + var mbgAgo = (lastMBGTime && lastMBGTime <= now) ? now - lastMBGTime : -1; + var mbgCurrent = mbgAgo != -1 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; - var treatmentAgo = (lastTreatmentTime && lastTreatmentTime < now) ? now - lastTreatmentTime : 0; - var treatmentCurrent = treatmentAgo != 0 && treatmentAgo < TIME_10_MINS_MS; + var treatmentAgo = (lastTreatmentTime && lastTreatmentTime <= now) ? now - lastTreatmentTime : -1; + var treatmentCurrent = treatmentAgo != -1 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { autoSnoozeAlarms(sbx); @@ -47,6 +47,7 @@ function init() { } function requestMBGNotify (lastMBG, sbx) { + console.info('requestMBGNotify for', lastMBG); sbx.notifications.requestNotify({ level: sbx.notifications.levels.INFO , title: 'Calibration' //assume all MGBs are calibrations for now From 890cb714774482ace09fd6ddff4b45df04ed8e04 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:27:34 -0700 Subject: [PATCH 141/661] added cannula age test --- lib/plugins/cannulaage.js | 2 +- tests/cannulaage.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/cannulaage.test.js diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 1d6e0c2400a..41fd6c75344 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -19,7 +19,7 @@ function init() { var message = ''; _.forEach(sbx.data.treatments, function eachTreatment (treatment) { - if (treatment.eventType == "Site Change") { + if (treatment.eventType == 'Site Change') { treatmentDate = new Date(treatment.created_at); var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js new file mode 100644 index 00000000000..2db06434073 --- /dev/null +++ b/tests/cannulaage.test.js @@ -0,0 +1,29 @@ +var should = require('should'); + +describe('cage', function ( ) { + var cage = require('../lib/plugins/cannulaage')(); + var sandbox = require('../lib/sandbox')(); + + it('set a pill to the current cannula age', function (done) { + + var app = {}; + var clientSettings = {}; + + var data = { + treatments: [{eventType: 'Site Change', created_at: (new Date(Date.now() - 24 * 60 * 60000)).toISOString()}] + }; + + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, updatedText, label, info) { + updatedText.should.equal('24h'); + done(); + } + }; + + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + cage.updateVisualisation(sbx); + + }); + + +}); From 5d287443c279ed239bd86e0c00bb7280247166db Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 13 Jun 2015 23:27:47 -0700 Subject: [PATCH 142/661] clean up --- tests/utils.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.test.js b/tests/utils.test.js index 3d6873d7ff5..0b79d107ade 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -8,7 +8,7 @@ describe('utils', function ( ) { , alarmTimeAgoWarnMins: 15 }; - it('shod format numbers', function () { + it('format numbers', function () { utils.toFixed(5.499999999).should.equal('5.50') }); From 36a9675dadab70fb75fecb2a635f6710d5743bd0 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 12:00:40 +0300 Subject: [PATCH 143/661] Small structure change to data to make testing deltas easier. Test for delta calculation. --- lib/data.js | 24 +++++++++++--------- tests/data.test.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 tests/data.test.js diff --git a/lib/data.js b/lib/data.js index a130c25991c..8a557ff8029 100644 --- a/lib/data.js +++ b/lib/data.js @@ -164,12 +164,16 @@ function init(env, ctx) { }; data.calculateDelta = function calculateDelta(lastData) { + return data.calculateDeltaBetweenDatasets(lastData,data); + } + + data.calculateDeltaBetweenDatasets = function calculateDeltaBetweenDatasets(oldData,newData) { var delta = {'delta': true}; var changesFound = false; // if there's no updates done so far, just return the full set - if (!lastData.sgvs) return data; + if (!oldData.sgvs) return newData; function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -193,24 +197,24 @@ function init(env, ctx) { }); } - delta.lastUpdated = data.lastUpdated; + delta.lastUpdated = newData.lastUpdated; // array compression var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; for (var array in compressibleArrays) { var a = compressibleArrays[array]; - if (data.hasOwnProperty(a)) { + if (newData.hasOwnProperty(a)) { // if previous data doesn't have the property (first time delta?), just assign data over - if (!lastData.hasOwnProperty(a)) { - delta[a] = data[a]; + if (!oldData.hasOwnProperty(a)) { + delta[a] = newData[a]; changesFound = true; continue; } // Calculate delta and assign delta over if changes were found - var deltaData = nsArrayDiff(lastData[a], data[a]); + var deltaData = nsArrayDiff(oldData[a], newData[a]); if (deltaData.length > 0) { console.log('delta changes found on', a); changesFound = true; @@ -225,17 +229,17 @@ function init(env, ctx) { for (var object in skippableObjects) { var o = skippableObjects[object]; - if (data.hasOwnProperty(o)) { - if (JSON.stringify(data[o]) != JSON.stringify(lastData[o])) { + if (newData.hasOwnProperty(o)) { + if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { console.log('delta changes found on', o); changesFound = true; - delta[o] = data[o]; + delta[o] = newData[o]; } } } if (changesFound) return delta; - return data; + return newData; }; diff --git a/tests/data.test.js b/tests/data.test.js new file mode 100644 index 00000000000..574db0f8d8e --- /dev/null +++ b/tests/data.test.js @@ -0,0 +1,55 @@ +var should = require('should'); + +describe('Data', function ( ) { + + var env = require('../env')(); + var ctx = {}; + data = require('../lib/data')(env, ctx); +// console.log(data); + + it('should return original data if there are no changes', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,data); + delta.should.equal(data); + }); + + it('adding one sgv record should return delta with one sgv', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.sgvs.length.should.equal(1); + }); + + it('changes to treatments, mbgs and cals should be calculated even if sgvs is not changed', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.mbgs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.cals = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.treatments = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.mbgs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.cals = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.treatments.length.should.equal(1); + delta.mbgs.length.should.equal(1); + delta.cals.length.should.equal(1); + }); + + it('delta should include profile and devicestatus object if changed', function() { + data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; + data.profiles = {foo:true}; + data.devicestatus = {foo:true}; + var newData = data.clone(); + newData.sgvs = [{sgv: 100, x:101},{sgv: 100, x:100},{sgv: 100, x:99}]; + newData.profiles = {bar:true}; + newData.devicestatus = {bar:true}; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.profiles.bar.should.equal(true); + delta.devicestatus.bar.should.equal(true); + }); + +}); \ No newline at end of file From 8c029922a3687f6a89127913617c59e45c02d5f3 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 16:10:07 +0300 Subject: [PATCH 144/661] Fix threshold calculation with ar2 prediction, when using mmol/L --- lib/plugins/ar2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 690ade39323..dd05c878c07 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -50,10 +50,10 @@ function init() { var min = _.max([first, last, avg]); var rangeLabel = ''; - if (max > sbx.thresholds.bg_target_top) { + if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; if (!pushoverSound) pushoverSound = 'climb' - } else if (min < sbx.thresholds.bg_target_bottom) { + } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; if (!pushoverSound) pushoverSound = 'falling' } else { @@ -62,7 +62,7 @@ function init() { var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); var lines = [ - ['Now', sbx.data.lastSGV(), sbx.unitsLabel].join(' ') + ['Now', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ') , ['15m', predicted[2], sbx.unitsLabel].join(' ') ]; From 6461de61976b919916bed960eeb3c1e4c44e634b Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 16:23:28 +0300 Subject: [PATCH 145/661] Fix Simple Alarms threshold calculation when on mmol --- lib/plugins/simplealarms.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 870e7e8c3cb..d6634f69824 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -21,30 +21,30 @@ function init() { ; if (lastSGV) { - if (lastSGV > sbx.thresholds.bg_high) { + if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_high)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.thresholds.bg_target_top) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - console.info(title + ': ' + (lastSGV + ' > ' + sbx.thresholds.bg_target_top)); + console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.thresholds.bg_low) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_low)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.thresholds.bg_target_bottom) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - console.info(title + ': ' + (lastSGV + ' < ' + sbx.thresholds.bg_target_bottom)); + console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { trigger = true; level = o; From 4eaaeffb27360cb4aab2147fca5e02ab10d5378a Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 17:13:32 +0300 Subject: [PATCH 146/661] Ensure sbx returns numbers when scaling BGs as this function is used before comparing BGs, which subsequently fails if the unit conversion returns a value converted with toFixed() --- lib/sandbox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sandbox.js b/lib/sandbox.js index 2c4c6bb9127..a724bb6de72 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -99,9 +99,9 @@ function init ( ) { function scaleBg (bg) { if (sbx.units == 'mmol' && bg) { - return units.mgdlToMMOL(bg); + return Number(units.mgdlToMMOL(bg)); } else { - return bg; + return Number(bg); } } From 0ddcc4bf828cbdc3eca8c01497419967d4e8039e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 14 Jun 2015 17:19:12 +0300 Subject: [PATCH 147/661] Had missed a couple thresholds in Simple Alarms. (Need to configure test env to use the same profile as production) --- lib/plugins/simplealarms.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index d6634f69824..30b4e34563d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -27,19 +27,19 @@ function init() { title = 'Urgent HIGH'; pushoverSound = 'persistent'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); - } else if (lastSGV > sbx.thresholds.bg_target_top) { + } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); - } else if (lastSGV < sbx.thresholds.bg_low) { + } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); - } else if (lastSGV < sbx.thresholds.bg_target_bottom) { + } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; From c329a3019912cd1634a14f4eeb9e58734fa122d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 09:55:31 -0700 Subject: [PATCH 148/661] add placeholder point the same way as the old websocket.js did --- static/js/client.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 1006e6f8acf..5a18f2dbb42 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1698,10 +1698,11 @@ function nsArrayDiff(oldArray, newArray) { // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove all 'color != 'none'' code - var lastdata = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - for (var i = 1; i <= 7; i++) { + var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { data.push({ - date: new Date(lastdata + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' }); } From 45d182b7d8c6cdf1679911316f451f7f04676c6d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 11:18:20 -0700 Subject: [PATCH 149/661] mqtt tests and fixes for the sgv/sensor merge --- lib/mqtt.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index a89d5663386..daa4a86be6b 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -112,24 +112,27 @@ function sgvSensorMerge(packet) { for (var i = 1; i <= smallerLength; i++) { var sgv = sgvs[sgvsLength - i]; var sensor = sensors[sensorsLength - i]; - if (Math.abs(sgv.date - sensor.date) < 10000) { + if (sgv && sensor && Math.abs(sgv.date - sensor.date) < 10000) { //timestamps are close so merge sgv.filtered = sensor.filtered; sgv.unfiltered = sensor.unfiltered; sgv.rssi = sensor.rssi; merged.push(sgv); } else { + console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor); //timestamps aren't close enough so add both - merged.push(sgv); + if (sgv) merged.push(sgv); //but the sensor will become and sgv now - sensor.type = 'sgv'; - merged.push(sensor); + if (sensor) { + sensor.type = 'sgv'; + merged.push(sensor); + } } } //any extra sgvs? if (sgvsLength > smallerLength) { - for (var j = sgvsLength - smallerLength; j < sgvsLength; j++) { + for (var j = 0; j < sgvsLength - smallerLength; j++) { var extraSGV = sgvs[j]; merged.push(extraSGV); } @@ -137,7 +140,7 @@ function sgvSensorMerge(packet) { //any extra sensors? if (sensorsLength > smallerLength) { - for (var k = sensorsLength - smallerLength; k < sensorsLength; k++) { + for (var k = 0; k < sensorsLength - smallerLength; k++) { var extraSensor = sensors[k]; //from now on we consider it a sgv extraSensor.type = 'sgv'; @@ -250,4 +253,8 @@ function configure(env, ctx) { client.every = every; return client; } + +//expose for tests that don't need to connect +configure.sgvSensorMerge = sgvSensorMerge; + module.exports = configure; From b87f2f41df960d01ccf68314c83a4b4a2cb73d70 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 11:24:22 -0700 Subject: [PATCH 150/661] mqtt tests that didn't get added before --- tests/mqtt.test.js | 102 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tests/mqtt.test.js diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js new file mode 100644 index 00000000000..546978221a7 --- /dev/null +++ b/tests/mqtt.test.js @@ -0,0 +1,102 @@ +var should = require('should'); + +var FIVE_MINS = 5 * 60 * 1000; + +describe('mqtt', function ( ) { + + var mqtt = require('../lib/mqtt'); + + var now = Date.now() + , prev1 = now - FIVE_MINS + , prev2 = prev1 - FIVE_MINS + ; + + it('setup env correctly', function (done) { + process.env.MONGO="mongodb://localhost/test_db"; + process.env.MONGO_COLLECTION="test_sgvs"; + process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; + var env = require('../env')(); + env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); + done(); + }); + + it('merge sgvs and sensor records that match up', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + merged.filter(function (sgv) { + return sgv.filtered && sgv.unfiltered && sgv.rssi; + }).length.should.equal(packet.sgv.length); + + done(); + + }); + + it('merge sgvs and sensor records that match up, and get the sgvs that don\'t match', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + var withBoth = merged.filter(function (sgv) { + return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; + }); + + withBoth.length.should.equal(1); + + done(); + + }); + + it('merge sgvs and sensor records that match up, and get the sensors that don\'t match', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 100, trend: 4, date: now} + ] + , sensor: [ + {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev2} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: prev1} + , {filtered: 99999, unfiltered: 99999, rssi: 200, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sensor.length); + + var withBoth = merged.filter(function (sgv) { + return sgv.sgv && sgv.filtered && sgv.unfiltered && sgv.rssi; + }); + + withBoth.length.should.equal(1); + + done(); + + }); + + +}); From aa5576904fd3617cfe7a83158133f2f643de2446 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 13:35:01 -0700 Subject: [PATCH 151/661] 1 more mqtt test --- tests/mqtt.test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 546978221a7..76d4c3c6911 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -20,6 +20,23 @@ describe('mqtt', function ( ) { done(); }); + it('handle a download with only sgvs', function (done) { + var packet = { + sgv: [ + {sgv_mgdl: 110, trend: 4, date: prev2} + , {sgv_mgdl: 105, trend: 4, date: prev1} + , {sgv_mgdl: 100, trend: 4, date: now} + ] + }; + + var merged = mqtt.sgvSensorMerge(packet); + + merged.length.should.equal(packet.sgv.length); + + done(); + + }); + it('merge sgvs and sensor records that match up', function (done) { var packet = { sgv: [ From 49b280e43b07f26cae903e7f5390c6ce887bf36c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:15:52 -0700 Subject: [PATCH 152/661] make sure ar2 and simple alarms don't trigger on error codes --- lib/plugins/ar2.js | 2 +- lib/plugins/simplealarms.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dd05c878c07..591db518720 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -97,7 +97,7 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 0) { + if (lastIndex > 0 && sgvs[lastIndex].y > 39 && sgvs[lastIndex - 1].y > 39) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 30b4e34563d..2966f80be1a 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -20,7 +20,7 @@ function init() { , pushoverSound = null ; - if (lastSGV) { + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; From d361b1b999fc5fe7250452500589b16d3af08ca4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:16:30 -0700 Subject: [PATCH 153/661] plugin to trigger notifications based on cgm error codes --- env.js | 1 + lib/notifications.js | 34 ++++++++++----- lib/plugins/errorcodes.js | 90 +++++++++++++++++++++++++++++++++++++++ lib/plugins/index.js | 1 + tests/errorcodes.test.js | 60 ++++++++++++++++++++++++++ 5 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 lib/plugins/errorcodes.js create mode 100644 tests/errorcodes.test.js diff --git a/env.js b/env.js index 3be410ef33c..f730f14a7f4 100644 --- a/env.js +++ b/env.js @@ -182,6 +182,7 @@ function config ( ) { //TODO: after config changes are documented this shouldn't be auto enabled env.enable += ' treatmentnotify'; } + env.enable += ' errorcodes'; // TODO: clean up a bit diff --git a/lib/notifications.js b/lib/notifications.js index a5992b8d084..6cf58d11a99 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -11,21 +11,21 @@ var Alarm = function(label) { }; // list of alarms with their thresholds -var alarms = { - 0: new Alarm('Info') - , 1: new Alarm('Warn') - , 2: new Alarm('Urgent') -}; +var alarms = {}; function init (env, ctx) { function notifications () { return notifications; } + //aligned with https://pushover.net/api#priority var levels = { URGENT: 2 , WARN: 1 , INFO: 0 + , LOW: -1 + , LOWEST: -2 + , NONE: -3 }; notifications.levels = levels; @@ -38,9 +38,21 @@ function init (env, ctx) { return 'Warn'; case 0: return 'Info'; + case -1: + return 'Low'; + case -2: + return 'Lowest'; } }; + function getAlarm (level) { + var alarm = alarms[level]; + if (!alarm) { + alarm = new Alarm(levels.toString(level)); + alarms[level] = alarm; + } + return alarm; + } //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action @@ -49,7 +61,7 @@ function init (env, ctx) { var sendClear = false; for (var level = 1; level <=2; level++) { - var alarm = alarms[level]; + var alarm = getAlarm(level); if (alarm.lastEmitTime) { console.info('auto acking ' + alarm.level); notifications.ack(alarm.level, 1); @@ -64,7 +76,7 @@ function init (env, ctx) { } function emitNotification (notify) { - var alarm = alarms[notify.level]; + var alarm = getAlarm(notify.level); if (ctx.data.lastUpdated > alarm.lastAckTime + alarm.silenceTime) { ctx.bus.emit('notification', notify); alarm.lastEmitTime = ctx.data.lastUpdated; @@ -91,7 +103,9 @@ function init (env, ctx) { }; notifications.findInfos = function findInfos ( ) { - return _.filter(requests.notifies, {level: levels.INFO}); + return _.filter(requests.notifies, function (notify) { + return notify.level <= levels.INFO; + }); }; notifications.snoozedBy = function snoozedBy (notify) { @@ -103,7 +117,7 @@ function init (env, ctx) { var sorted = _.sortBy(byLevel, 'mills'); var longest = _.last(sorted); - var alarm = alarms[notify.level]; + var alarm = getAlarm(notify.level); if (longest && Date.now() + longest.lengthMills > alarm.lastAckTime + alarm.silenceTime) { return longest; @@ -149,7 +163,7 @@ function init (env, ctx) { }; notifications.ack = function ack (level, time) { - var alarm = alarms[level]; + var alarm = getAlarm(level); if (!alarm) { console.warn('Got an ack for an unknown alarm time'); return; diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js new file mode 100644 index 00000000000..5f27bd486a4 --- /dev/null +++ b/lib/plugins/errorcodes.js @@ -0,0 +1,90 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + var TIME_10_MINS_MS = 10 * 60 * 1000; + + function errorcodes() { + return errorcodes; + } + + errorcodes.label = 'Dexcom Error Codes'; + errorcodes.pluginType = 'notification'; + + errorcodes.checkNotifications = function checkNotifications (sbx) { + var now = Date.now(); + var lastSGV = _.last(sbx.data.sgvs); + + if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 40) { + + var errorDisplay; + var pushoverSound = null; + var notifyLevel = sbx.notifications.levels.LOW; + + switch (parseInt(lastSGV.y)) { + case 0: //None + errorDisplay = '??0'; + break; + case 1: //SENSOR_NOT_ACTIVE + errorDisplay = '?SN'; + break; + case 2: //MINIMAL_DEVIATION + errorDisplay = '??2'; + break; + case 3: //NO_ANTENNA + errorDisplay = '?NA'; + break; + case 5: //SENSOR_NOT_CALIBRATED + errorDisplay = '?NC'; + break; + case 6: //COUNTS_DEVIATION + errorDisplay = '?CD'; + break; + case 7: //? + errorDisplay = '??7'; + break; + case 8: //? + errorDisplay = '??8'; + break; + case 9: //ABSOLUTE_DEVIATION + errorDisplay = '?HG'; + pushoverSound = 'persistent'; + notifyLevel = sbx.notifications.levels.URGENT; + break; + case 10: //POWER_DEVIATION + errorDisplay = '???'; + pushoverSound = 'persistent'; + notifyLevel = sbx.notifications.levels.URGENT; + break; + case 12: //BAD_RF + errorDisplay = '?RF'; + break; + default: + notifyLevel = sbx.notifications.levels.LOWEST; + errorDisplay = '?' + parseInt(lastSVG.y) + '?'; + break; + } + + if (notifyLevel > sbx.notifications.levels.NONE) { + sbx.notifications.requestNotify({ + level: notifyLevel + , title: 'CGM Error Code' + , message: errorDisplay + , pushoverSound: pushoverSound + , debug: { + lastSGV: lastSGV + } + }); + } + + } + }; + + + return errorcodes(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 72953881e72..171ae16446e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -17,6 +17,7 @@ function init() { plugins.register([ require('./ar2')() , require('./simplealarms')() + , require('./errorcodes')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js new file mode 100644 index 00000000000..75c2f880aff --- /dev/null +++ b/tests/errorcodes.test.js @@ -0,0 +1,60 @@ +var _ = require('lodash'); +var should = require('should'); + +describe('errorcodes', function ( ) { + + var errorcodes = require('../lib/plugins/errorcodes')(); + + var now = Date.now(); + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + + it('Not trigger an alarm when in range', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 100, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a urgent alarm when ???', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 10, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a urgent alarm when hourglass', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 9, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + + done(); + }); + + it('should trigger a low notification when needing calibration', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 5, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + _.first(ctx.notifications.findInfos()).level.should.equal(ctx.notifications.levels.LOW); + + done(); + }); + +}); \ No newline at end of file From 719dfdfc1ff25a69da543424432616551c1c3900 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:21:05 -0700 Subject: [PATCH 154/661] use the ugly y field instead of sgv for now --- tests/simplealarms.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index c4f47384181..ab7a3f941a6 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -12,7 +12,7 @@ describe('simplealarms', function ( ) { it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 100}]; + ctx.data.sgvs = [{y: 100}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -23,7 +23,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 181}]; + ctx.data.sgvs = [{y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -34,7 +34,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 400}]; + ctx.data.sgvs = [{y: 400}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -45,7 +45,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when below target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 70}]; + ctx.data.sgvs = [{y: 70}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -56,7 +56,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really low', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 40}]; + ctx.data.sgvs = [{y: 40}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); From 2eef14648ba5f290bbedbcb7f20de7c0606d9870 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 14 Jun 2015 19:38:05 -0700 Subject: [PATCH 155/661] more errorcode tests --- lib/plugins/errorcodes.js | 4 ++-- tests/errorcodes.test.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 5f27bd486a4..ddba4d56aad 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -49,7 +49,7 @@ function init() { errorDisplay = '??8'; break; case 9: //ABSOLUTE_DEVIATION - errorDisplay = '?HG'; + errorDisplay = '?AD'; pushoverSound = 'persistent'; notifyLevel = sbx.notifications.levels.URGENT; break; @@ -63,7 +63,7 @@ function init() { break; default: notifyLevel = sbx.notifications.levels.LOWEST; - errorDisplay = '?' + parseInt(lastSVG.y) + '?'; + errorDisplay = '?' + parseInt(lastSGV.y) + '?'; break; } diff --git a/tests/errorcodes.test.js b/tests/errorcodes.test.js index 75c2f880aff..2624d4344e1 100644 --- a/tests/errorcodes.test.js +++ b/tests/errorcodes.test.js @@ -57,4 +57,18 @@ describe('errorcodes', function ( ) { done(); }); + it('should trigger a low notification when code < 9', function (done) { + + for (var i = 0; i < 9; i++) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: i, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + errorcodes.checkNotifications(sbx); + should.not.exist(ctx.notifications.findHighestAlarm()); + _.first(ctx.notifications.findInfos()).level.should.be.lessThan(ctx.notifications.levels.WARN); + } + done(); + }); + }); \ No newline at end of file From e495bdc94d3ea94000f5ff1bea09476b3c710656 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 16 Jun 2015 22:32:43 -0700 Subject: [PATCH 156/661] use node-cache to keep track of recently sent pushovers; prevent some more dupe and false alarms --- lib/notifications.js | 2 +- lib/plugins/ar2.js | 27 ++++++++----- lib/plugins/boluswizardpreview.js | 8 ++++ lib/plugins/errorcodes.js | 5 ++- lib/plugins/simplealarms.js | 5 ++- lib/plugins/treatmentnotify.js | 2 + lib/pushnotify.js | 67 ++++++++----------------------- package.json | 1 + tests/notifications.test.js | 5 +++ tests/simplealarms.test.js | 12 +++--- 10 files changed, 65 insertions(+), 69 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 6cf58d11a99..4d078242786 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -127,7 +127,7 @@ function init (env, ctx) { }; notifications.requestNotify = function requestNotify (notify) { - if (!notify.hasOwnProperty('level') || !notify.title || !notify.message) { + if (!notify.hasOwnProperty('level') || !notify.title || !notify.message || !notify.plugin) { console.error(new Error('Unable to request notification, since the notify isn\'t complete: ' + JSON.stringify(notify))); return; } diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 591db518720..15ad1a20c3b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -17,25 +17,31 @@ function init() { var ONE_HOUR = 3600000; var ONE_MINUTE = 60000; var FIVE_MINUTES = 300000; + var TEN_MINUTES = 600000; + ar2.checkNotifications = function checkNotifications(sbx) { - var forecast = ar2.forecast(sbx.data.sgvs); var trigger = false + , lastSGVEntry = _.last(sbx.data.sgvs) + , forecast = null , level = 0 , levelLabel = '' , pushoverSound = null ; - if (forecast.avgLoss > URGENT_THRESHOLD) { - trigger = true; - level = 2; - levelLabel = 'Urgent'; - pushoverSound = 'persistent'; - } else if (forecast.avgLoss > WARN_THRESHOLD) { - trigger = true; - level = 1; - levelLabel = 'Warning'; + if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { + forecast = ar2.forecast(sbx.data.sgvs); + if (forecast.avgLoss > URGENT_THRESHOLD) { + trigger = true; + level = 2; + levelLabel = 'Urgent'; + pushoverSound = 'persistent'; + } else if (forecast.avgLoss > WARN_THRESHOLD) { + trigger = true; + level = 1; + levelLabel = 'Warning'; + } } if (trigger) { @@ -80,6 +86,7 @@ function init() { , title: title , message: message , pushoverSound: pushoverSound + , plugin: ar2 , debug: { forecast: forecast , thresholds: sbx.thresholds diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 26089ded5f7..97d9a4cd3b8 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -62,11 +62,19 @@ function init() { } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); + var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + message += ['\nIOB:', iob, 'U'].join(' '); + } + sbx.notifications.requestNotify({ level: level , title: 'Check BG, time to bolus?' , message: message + , pushoverSound: sound + , plugin: bwp , debug: results }); } diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index ddba4d56aad..2462dd2a5de 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -50,12 +50,12 @@ function init() { break; case 9: //ABSOLUTE_DEVIATION errorDisplay = '?AD'; - pushoverSound = 'persistent'; + pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; break; case 10: //POWER_DEVIATION errorDisplay = '???'; - pushoverSound = 'persistent'; + pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; break; case 12: //BAD_RF @@ -72,6 +72,7 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay + , plugin: errorcodes , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 2966f80be1a..8db1aea089d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -8,6 +8,8 @@ function init() { return simplealarms; } + var TIME_10_MINS_MS = 10 * 60 * 1000; + simplealarms.label = 'Simple Alarms'; simplealarms.pluginType = 'notification'; @@ -20,7 +22,7 @@ function init() { , pushoverSound = null ; - if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39) { + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39 && Date.now() - lastSGVEntry.x < TIME_10_MINS_MS) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; @@ -59,6 +61,7 @@ function init() { level: level , title: title , message: [lastSGV, sbx.unitsLabel].join(' ') + , plugin: simplealarms , pushoverSound: pushoverSound , debug: { lastSGV: lastSGV, thresholds: sbx.thresholds diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 2984401ad3f..72b57fdb3e6 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -53,6 +53,7 @@ function init() { , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , plugin: treatmentnotify , pushoverSound: 'magic' //, debug: results }); @@ -78,6 +79,7 @@ function init() { level: sbx.notifications.levels.INFO , title: lastTreatment.eventType , message: message + , plugin: treatmentnotify // , debug: results }); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index c63cc9d1b48..462c31b81ea 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -3,14 +3,14 @@ var _ = require('lodash'); var crypto = require('crypto'); var units = require('./units')(); +var NodeCache = require( "node-cache" ); function init(env, ctx) { var pushover = require('./pushover')(env); // declare local constants for time differences - var TIME_5_MINS_MS = 5 * 60 * 1000 - ,TIME_15_MINS_S = 15 * 60 + var TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 ; @@ -18,12 +18,24 @@ function init(env, ctx) { return pushnotify; } - var recentlySent = {}; + var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; - if (isDuplicate(notify)) return; + var key = null; + if (notify.level >= ctx.notifications.levels.WARN) { + //for WARN and higher use the plugin name and notification level so that louder alarms aren't triggered too often + key = notify.plugin.name + '_' + notify.level; + } else { + //INFO and lower notifications should be sent as long as they are different + key = notifyToHash(notify); + } + + if (recentlySent.get(key)) { + console.info('notify: ' + key + ' has ALREADY been sent'); + return; + } var msg = { expire: TIME_15_MINS_S, @@ -36,61 +48,16 @@ function init(env, ctx) { }; pushover.send(msg, function(err, result) { - updateRecentlySent(err, notify); if (err) { console.error('unable to send pushover notification', err); } else { + recentlySent.set(key, notify); console.info('sent pushover notification: ', msg, 'result: ', result); } }); }; - function isDuplicate(notify) { - var byLevel = sentByLevel(notify); - var hash = notifyToHash(notify); - - var found = _.find(byLevel, function findByHash(sent) { - return sent.hash = hash; - }); - - if (found) { - console.info('Found duplicate notification that was sent recently using hash: ', hash, 'of notify: ', notify); - return true; - } else { - console.info('No duplicate notification found, using hash: ', hash, 'of notify: ', notify); - return false; - } - - } - - function updateRecentlySent(err, notify) { - sentByLevel(notify).push({ - time: Date.now() - , err: err - , hash: notifyToHash(notify) - }); - } - - function sentByLevel(notify) { - var byLevel = recentlySent[notify.level]; - if (!byLevel) { - byLevel = []; - } - - var now = Date.now(); - - byLevel = _.filter(byLevel, function isRecent(sent) { - //consider errors stale sooner than successful sends - var staleAfter = sent.err ? TIME_5_MINS_MS : TIME_15_MINS_MS; - return now - sent.time < staleAfter; - }); - - recentlySent[notify.level] = byLevel; - - return byLevel; - } - function notifyToHash(notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); diff --git a/package.json b/package.json index f3714e54091..649081ba62c 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "mongodb": "^1.4.7", "moment": "2.8.1", "mqtt": "~0.3.11", + "node-cache": "^3.0.0", "pushover-notifications": "0.2.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "socket.io": "^1.3.5" diff --git a/tests/notifications.test.js b/tests/notifications.test.js index 215b4a0b709..df386cbf098 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,22 +13,27 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); + var examplePlugin = function examplePlugin () {}; + var exampleInfo = { title: 'test' , message: 'testing' , level: notifications.levels.INFO + , plugin: examplePlugin }; var exampleWarn = { title: 'test' , message: 'testing' , level: notifications.levels.WARN + , plugin: examplePlugin }; var exampleUrgent = { title: 'test' , message: 'testing' , level: notifications.levels.URGENT + , plugin: examplePlugin }; var exampleSnooze = { diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index ab7a3f941a6..1aac1ea2043 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -9,10 +9,12 @@ describe('simplealarms', function ( ) { ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); + var now = Date.now(); + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 100}]; + ctx.data.sgvs = [{x: now, y: 100}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -23,7 +25,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 181}]; + ctx.data.sgvs = [{x: now, y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -34,7 +36,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 400}]; + ctx.data.sgvs = [{x: now, y: 400}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -45,7 +47,7 @@ describe('simplealarms', function ( ) { it('should trigger a warning when below target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 70}]; + ctx.data.sgvs = [{x: now, y: 70}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); @@ -56,7 +58,7 @@ describe('simplealarms', function ( ) { it('should trigger a urgent alarm when really low', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 40}]; + ctx.data.sgvs = [{x: now, y: 40}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); simplealarms.checkNotifications(sbx); From a04a436a0192ee02e4d0d9e540d25d3d1270659b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 16 Jun 2015 23:22:47 -0700 Subject: [PATCH 157/661] don't store date strings in memory --- lib/data.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/data.js b/lib/data.js index 8a557ff8029..166d8ce7785 100644 --- a/lib/data.js +++ b/lib/data.js @@ -80,11 +80,11 @@ function init(env, ctx) { if (element) { if (element.mbg) { mbgs.push({ - y: element.mbg, x: element.date, d: element.dateString, device: element.device + y: element.mbg, x: element.date, device: element.device }); } else if (element.sgv) { sgvs.push({ - y: element.sgv, x: element.date, d: element.dateString, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + y: element.sgv, x: element.date, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } @@ -106,7 +106,7 @@ function init(env, ctx) { results.forEach(function (element) { if (element) { cals.push({ - x: element.date, d: element.dateString, scale: element.scale, intercept: element.intercept, slope: element.slope + x: element.date, scale: element.scale, intercept: element.intercept, slope: element.slope }); } }); @@ -120,14 +120,13 @@ function init(env, ctx) { if (!err && results) { var treatments = []; treatments = results.map(function (treatment) { - var timestamp = new Date(treatment.timestamp || treatment.created_at); - treatment.x = timestamp.getTime(); + treatment.created_at = new Date(treatment.created_at).getTime(); return treatment; }); //FIXME: sort in mongo treatments.sort(function (a, b) { - return a.x - b.x; + return a.created_at - b.created_at; }); data.treatments = treatments; From 2a9cb9559f68a1dd1862104288e80ca1a936964c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 01:02:35 -0700 Subject: [PATCH 158/661] also pull extended setting from the env based on what plugin have been enabled any env var that is prefixed with _ will be added to a new env.enableExt field, and will be made available to the individual plugin Example ENABLE=bwp BWP_WARN=.45 BWP_URGENT=.75 ==> {warn: .45, urgent: .75} will be available to BWP plugin, but not others --- env.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/env.js b/env.js index f730f14a7f4..dcaec2fa5fb 100644 --- a/env.js +++ b/env.js @@ -1,6 +1,7 @@ 'use strict'; var env = { }; +var _ = require('lodash'); var crypto = require('crypto'); var consts = require('./lib/constants'); var fs = require('fs'); @@ -184,6 +185,8 @@ function config ( ) { } env.enable += ' errorcodes'; + env.enableExt = findExtendedSettings(env.enable, process.env); + // TODO: clean up a bit // Some people prefer to use a json configuration file instead. @@ -215,4 +218,26 @@ function readENV(varName, defaultValue) { return value != null ? value : defaultValue; } +function findExtendedSettings (enables, envs) { + var extended = {}; + enables.split(' ').forEach(function eachEnable(enable) { + if (_.trim(enable)) { + _.forIn(envs, function (value, key) { + if (_.startsWith(key, enable.toUpperCase() + '_') || _.startsWith(key, enable.toLowerCase() + '_')) { + var split = key.indexOf('_'); + if (split > -1 && split <= key.length) { + var exts = extended[enable] || {}; + extended[enable] = exts; + var ext = _.kebabCase(key.substring(split + 1).toLowerCase()); + exts[ext] = value; + } + } + }); + } + }); + + console.info('Extended Settings: ', extended); + return extended; +} + module.exports = config; From 4b8a6de683405b09808ef92c47124b26bea30910 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:15:55 -0700 Subject: [PATCH 159/661] clean up --- env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.js b/env.js index dcaec2fa5fb..9b77a67878e 100644 --- a/env.js +++ b/env.js @@ -222,7 +222,7 @@ function findExtendedSettings (enables, envs) { var extended = {}; enables.split(' ').forEach(function eachEnable(enable) { if (_.trim(enable)) { - _.forIn(envs, function (value, key) { + _.forIn(envs, function eachEnvPair (value, key) { if (_.startsWith(key, enable.toUpperCase() + '_') || _.startsWith(key, enable.toLowerCase() + '_')) { var split = key.indexOf('_'); if (split > -1 && split <= key.length) { From 5c07f165e604dc43392294daa0c28734d19edc14 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:16:13 -0700 Subject: [PATCH 160/661] try harder to prevent dupes --- lib/pushnotify.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 462c31b81ea..d1adf75164c 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -47,11 +47,14 @@ function init(env, ctx) { retry: 30 }; + //add the key to the cache before sending, but with a short TTL + recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { if (err) { console.error('unable to send pushover notification', err); } else { - recentlySent.set(key, notify); + //after successfully sent, increase the TTL + recentlySent.ttl(key, TIME_15_MINS_S); console.info('sent pushover notification: ', msg, 'result: ', result); } }); From b6c2959bb201cc5f6d36ae4f0dbabf9d8bfc61f4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 15:41:46 -0700 Subject: [PATCH 161/661] BWP should only send notifications when there's a current sgv --- lib/plugins/boluswizardpreview.js | 6 +++++- tests/boluswizardpreview.test.js | 8 +++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 97d9a4cd3b8..0e8aad21e93 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,6 +2,8 @@ var _ = require('lodash'); + +var TEN_MINS = 10 * 60 * 1000; var FIFTEEN_MINS = 15 * 60 * 1000; function init() { @@ -29,7 +31,9 @@ function init() { return false; } - if (!sbx.data.lastSGV()) { + var lastSGVEntry = _.last(sbx.data.sgvs); + + if (!lastSGVEntry || lastSGVEntry.y < 40 || Date.now() - lastSGVEntry.x > TEN_MINS) { console.warn('For the BolusWizardPreview plugin to function there needs to be a current SGV'); return false; } diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 5280d19e3ea..7338e15c98c 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -9,6 +9,8 @@ describe('boluswizardpreview', function ( ) { ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); + var now = Date.now(); + var profile = { sens: 90 , target_high: 120 @@ -17,7 +19,7 @@ describe('boluswizardpreview', function ( ) { it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 100}]; + ctx.data.sgvs = [{x: now, y: 100}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); @@ -33,7 +35,7 @@ describe('boluswizardpreview', function ( ) { it('trigger a warning when going out of range', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 180}]; + ctx.data.sgvs = [{x: now, y: 180}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); @@ -49,7 +51,7 @@ describe('boluswizardpreview', function ( ) { it('trigger an urgent alarms when going too high', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{sgv: 300}]; + ctx.data.sgvs = [{x: now, y: 300}]; ctx.data.profiles = [profile]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); From 3936ecaed3b4b24f5370fb8df45d294f0311646a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 18:10:26 -0700 Subject: [PATCH 162/661] expose extended plugin settings via the sandox --- env.js | 2 +- lib/api/index.js | 1 + lib/api/status.js | 1 + lib/plugins/boluswizardpreview.js | 7 ++--- lib/plugins/index.js | 48 +++++++++++++++++++------------ lib/sandbox.js | 18 ++++++++++++ static/js/client.js | 1 + 7 files changed, 55 insertions(+), 23 deletions(-) diff --git a/env.js b/env.js index 9b77a67878e..daa9e41a638 100644 --- a/env.js +++ b/env.js @@ -185,7 +185,7 @@ function config ( ) { } env.enable += ' errorcodes'; - env.enableExt = findExtendedSettings(env.enable, process.env); + env.extendedSettings = findExtendedSettings(env.enable, process.env); // TODO: clean up a bit diff --git a/lib/api/index.js b/lib/api/index.js index a53840fd5e7..0732e797847 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -25,6 +25,7 @@ function create (env, ctx) { if (env.enable) { app.enabledOptions = env.enable || ''; + app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; env.enable.toLowerCase().split(' ').forEach(function (value) { var enable = value.trim(); console.info("enabling feature:", enable); diff --git a/lib/api/status.js b/lib/api/status.js index ec0b4452a3a..eca642ce0ab 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -14,6 +14,7 @@ function configure (app, wares) { , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && app.enabled('careportal') , enabledOptions: app.enabledOptions + , extendedSettings: app.extendedClientSettings , defaults: app.defaults , units: app.get('units') , head: wares.get_head( ) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 0e8aad21e93..78d08fc558b 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -52,10 +52,9 @@ function init() { if (results.lastSGV < sbx.data.profile.target_high) return; - //TODO: not sure where these will come from yet - var snoozeBWP = sbx.properties.snoozeBWP || 0.10; - var warnBWP = sbx.properties.warnBWP || 0.35; - var urgentBWP = sbx.properties.urgentBWP || 0.75; + var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; + var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; + var urgentBWP = Number(sbx.extendedSettings.urgent) || 1.00; if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 171ae16446e..5a7c3ce27c9 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -13,26 +13,30 @@ function init() { plugins.base = require('./pluginbase'); + var clientDefaultPlugins = [ + require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./cannulaage')() + ]; + + var serverDefaultPlugins = [ + require('./ar2')() + , require('./simplealarms')() + , require('./errorcodes')() + , require('./iob')() + , require('./cob')() + , require('./boluswizardpreview')() + , require('./treatmentnotify')() + ]; + plugins.registerServerDefaults = function registerServerDefaults() { - plugins.register([ - require('./ar2')() - , require('./simplealarms')() - , require('./errorcodes')() - , require('./iob')() - , require('./cob')() - , require('./boluswizardpreview')() - , require('./treatmentnotify')() - ]); + plugins.register(serverDefaultPlugins); return plugins; }; plugins.registerClientDefaults = function registerClientDefaults() { - plugins.register([ - require('./iob')() - , require('./cob')() - , require('./boluswizardpreview')() - , require('./cannulaage')() - ]); + plugins.register(clientDefaultPlugins); return plugins; }; @@ -85,19 +89,19 @@ function init() { plugins.setProperties = function setProperties(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.setProperties && plugin.setProperties(sbx); + plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin)); }); }; plugins.checkNotifications = function checkNotifications(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.checkNotifications && plugin.checkNotifications(sbx); + plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin)); }); }; plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(sbx); + plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); }); }; @@ -107,6 +111,14 @@ function init() { }).join(' '); }; + plugins.extendedClientSettings = function extendedClientSettings (allExtendedSettings) { + var clientSettings = {}; + _.forEach(clientDefaultPlugins, function eachClientPlugin (plugin) { + clientSettings[plugin.name] = allExtendedSettings[plugin.name]; + }); + return clientSettings; + }; + return plugins(); } diff --git a/lib/sandbox.js b/lib/sandbox.js index a724bb6de72..74315083cf8 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -22,6 +22,15 @@ function init ( ) { //ug, on the client y, is unscaled, on the server we only have the unscaled sgv field return last && (last.y || last.sgv); }; + + //default to prevent adding checks everywhere + sbx.extendedSettings = {empty: true}; + } + + function withExtendedSettings(plugin, allExtendedSettings, sbx) { + var sbx2 = _.extend({}, sbx); + sbx2.extendedSettings = allExtendedSettings[plugin.name] || {}; + return sbx2; } /** @@ -50,6 +59,10 @@ function init ( ) { sbx.properties = []; + sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { + return withExtendedSettings(plugin, env.extendedSettings, sbx); + }; + extend(); return sbx; @@ -77,6 +90,11 @@ function init ( ) { sbx.data = data; sbx.pluginBase = pluginBase; + sbx.extendedSettings = {empty: true}; + sbx.withExtendedSettings = function getPluginExtendedSettingsOnly (plugin) { + return withExtendedSettings(plugin, app.extendedSettings, sbx); + }; + extend(); return sbx; diff --git a/static/js/client.js b/static/js/client.js index 5a18f2dbb42..8249f75251d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1804,6 +1804,7 @@ function nsArrayDiff(oldArray, newArray) { , head: xhr.head , apiEnabled: xhr.apiEnabled , enabledOptions: xhr.enabledOptions || '' + , extendedSettings: xhr.extendedSettings , thresholds: xhr.thresholds , alarm_types: xhr.alarm_types , units: xhr.units From cebd9a8a3793f649813880136d2d8fe8a9a66e9b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 17 Jun 2015 23:55:10 -0700 Subject: [PATCH 163/661] added support for pushover callbacks to snooze system wide alarms --- README.md | 1 + env.js | 3 +- lib/api/index.js | 1 + lib/api/notifications-api.js | 27 +++++++++++++++ lib/notifications.js | 34 ++++++++++++++++++- lib/pushnotify.js | 45 ++++++++++++++++++++----- static/result/index.html | 64 ++++++++++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 lib/api/notifications-api.js create mode 100644 static/result/index.html diff --git a/README.md b/README.md index 136fd3cede1..2bf40e19279 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's + * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site diff --git a/env.js b/env.js index daa9e41a638..a458a908c03 100644 --- a/env.js +++ b/env.js @@ -187,13 +187,14 @@ function config ( ) { env.extendedSettings = findExtendedSettings(env.enable, process.env); + env.baseUrl = readENV('BASE_URL'); // TODO: clean up a bit // Some people prefer to use a json configuration file instead. // This allows a provided json config to override environment variables var DB = require('./database_configuration.json'), DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; env.mongo = DB_URL; env.mongo_collection = DB_COLLECTION; env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); diff --git a/lib/api/index.js b/lib/api/index.js index 0732e797847..6e28946db46 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -51,6 +51,7 @@ function create (env, ctx) { app.use('/', require('./treatments/')(app, wares, ctx)); app.use('/', require('./profile/')(app, wares, ctx)); app.use('/', require('./devicestatus/')(app, wares, ctx)); + app.use('/', require('./notifications-api')(app, wares, ctx)); // Status app.use('/', require('./status')(app, wares)); diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js new file mode 100644 index 00000000000..5e502bc24c4 --- /dev/null +++ b/lib/api/notifications-api.js @@ -0,0 +1,27 @@ +'use strict'; + +function configure (app, wares, ctx) { + var express = require('express'), + notifications = express.Router( ) + ; + + notifications.get('/notifications/snooze', function (req, res) { + console.info('GOT web notification snooze', req.query); + var result = ctx.notifications.secureAck( + req.query.level + , req.query.lengthMills + , req.query.t + , req.query.sig + ); + res.redirect(302, '/result/?ok=' + result); + }); + + notifications.post('/notifications/pushovercallback', function (req, res) { + console.info('GOT Pushover callback', req.body); + var result = ctx.pushnotify.ack(req.body); + res.redirect(302, '/result/?ok=' + result); + }); + + return notifications; +} +module.exports = configure; diff --git a/lib/notifications.js b/lib/notifications.js index 4d078242786..0e08165b1cd 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var crypto = require('crypto'); var THIRTY_MINUTES = 30 * 60 * 1000; @@ -162,7 +163,7 @@ function init (env, ctx) { }) }; - notifications.ack = function ack (level, time) { + notifications.ack = function ack (level, time, sendClear) { var alarm = getAlarm(level); if (!alarm) { console.warn('Got an ack for an unknown alarm time'); @@ -176,6 +177,37 @@ function init (env, ctx) { notifications.ack(1, time); } + if (sendClear) { + ctx.bus.emit('notification', {clear: true}); + } + + }; + + notifications.secureAck = function secureAck (level, lenghtMills, t, sig) { + var expected = notifications.sign(level, lenghtMills, t); + + if (expected && expected == sig) { + notifications.ack(level, lenghtMills, true); + return true + } else { + console.info(expected, ' != ', sig); + return false; + } + }; + + notifications.sign = function sign (level, lenghtMills, t) { + + console.info('trying to sign with', level, lenghtMills, t); + if (env.api_secret) { + var shasum = crypto.createHash('sha1'); + shasum.update(level.toString()); + shasum.update(lenghtMills.toString()); + shasum.update(t.toString()); + return shasum.digest('base64'); + } else { + return false; + } + }; return notifications(); diff --git a/lib/pushnotify.js b/lib/pushnotify.js index d1adf75164c..48784c18779 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -12,16 +12,19 @@ function init(env, ctx) { // declare local constants for time differences var TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 + , TIME_30_MINS_MS = 30 * 60 * 1000 ; function pushnotify() { return pushnotify; } + var receipts = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 120 }); var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { if (!pushover) return; + if (notify.clear) return; var key = null; if (notify.level >= ctx.notifications.levels.WARN) { @@ -38,29 +41,55 @@ function init(env, ctx) { } var msg = { - expire: TIME_15_MINS_S, - title: notify.title, - message: notify.message, - sound: notify.pushoverSound || 'gamelan', - timestamp: new Date( ), - priority: notify.level, - retry: 30 + expire: TIME_15_MINS_S + , title: notify.title + , message: notify.message + , sound: notify.pushoverSound || 'gamelan' + , timestamp: new Date() + , priority: notify.level }; + if (notify.level == ctx.notifications.levels.URGENT) { + msg.retry = 120; + if (env.baseUrl) { + msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; + } + } else if (notify.level == ctx.notifications.levels.WARN && env.baseUrl) { + var now = Date.now(); + var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + if (sig) { + msg.url_title = 'Snooze for 30 minutes'; + msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + } + } + //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { if (err) { console.error('unable to send pushover notification', err); } else { + //result comes back as a string here, so fix it + result = JSON.parse(result); + console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL recentlySent.ttl(key, TIME_15_MINS_S); - console.info('sent pushover notification: ', msg, 'result: ', result); + //also hold on to the receipt/notify mapping + receipts.set(result.receipt, notify); } }); }; + pushnotify.ack = function ack (response) { + var notify = receipts.get(response.receipt); + console.info('push ack, response: ', response, ', notify: ', notify); + if (notify) { + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + } + return !!notify; + }; + function notifyToHash(notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); diff --git a/static/result/index.html b/static/result/index.html new file mode 100644 index 00000000000..b0f5969aca0 --- /dev/null +++ b/static/result/index.html @@ -0,0 +1,64 @@ + + + + + Notifications + + + + + + + + + + +

    Success

    + +

    + You can close this page this page now. +

    + + + + + + + + + + From c95503c6518a4f77b6133db67c6f06d752dad187 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 00:27:18 -0700 Subject: [PATCH 164/661] only emergency alarms get a receipt, use hex digest so we don't have to wory about url encoding --- lib/notifications.js | 4 ++-- lib/pushnotify.js | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 0e08165b1cd..1b219a0dc52 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -197,13 +197,13 @@ function init (env, ctx) { notifications.sign = function sign (level, lenghtMills, t) { - console.info('trying to sign with', level, lenghtMills, t); if (env.api_secret) { var shasum = crypto.createHash('sha1'); + shasum.update(env.api_secret); shasum.update(level.toString()); shasum.update(lenghtMills.toString()); shasum.update(t.toString()); - return shasum.digest('base64'); + return shasum.digest('hex'); } else { return false; } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 48784c18779..2de184fac8d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -74,14 +74,19 @@ function init(env, ctx) { console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL recentlySent.ttl(key, TIME_15_MINS_S); - //also hold on to the receipt/notify mapping - receipts.set(result.receipt, notify); + + if (result.receipt) { + //if this was an emergency alarm, also hold on to the receipt/notify mapping, for later acking + receipts.set(result.receipt, notify); + } } }); }; pushnotify.ack = function ack (response) { + if (!response.receipt) return false; + var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { From 980c45ea29a39d09509091969b40f43ea672f5d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 00:38:11 -0700 Subject: [PATCH 165/661] don't log the extended settings, they may include keys like pushover --- env.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/env.js b/env.js index a458a908c03..9575d754818 100644 --- a/env.js +++ b/env.js @@ -236,8 +236,6 @@ function findExtendedSettings (enables, envs) { }); } }); - - console.info('Extended Settings: ', extended); return extended; } From c0588fa11d1ee92ac9fd79f9ee5d72cb942c333b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 18 Jun 2015 18:37:06 -0700 Subject: [PATCH 166/661] add back the x field to treatments so they can be used like normal entries --- lib/data.js | 5 +++-- tests/data.test.js | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/data.js b/lib/data.js index 166d8ce7785..cc7025a4d25 100644 --- a/lib/data.js +++ b/lib/data.js @@ -118,9 +118,10 @@ function init(env, ctx) { var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { - var treatments = []; - treatments = results.map(function (treatment) { + var treatments = results.map(function (treatment) { treatment.created_at = new Date(treatment.created_at).getTime(); + //TODO: #CleanUpDataModel, some code expects x everywhere + treatment.x = treatment.created_at; return treatment; }); diff --git a/tests/data.test.js b/tests/data.test.js index 574db0f8d8e..27370f1fc93 100644 --- a/tests/data.test.js +++ b/tests/data.test.js @@ -22,6 +22,15 @@ describe('Data', function ( ) { delta.sgvs.length.should.equal(1); }); + it('adding one treatment record should return delta with one treatment', function() { + data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; + var newData = data.clone(); + newData.treatments = [{sgv: 100, x:100},{sgv: 100, x:99},{sgv: 100, x:98}]; + var delta = data.calculateDeltaBetweenDatasets(data,newData); + delta.delta.should.equal(true); + delta.treatments.length.should.equal(1); + }); + it('changes to treatments, mbgs and cals should be calculated even if sgvs is not changed', function() { data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; data.treatments = [{sgv: 100, x:100},{sgv: 100, x:99}]; From 5f22b97477ba0209b9fde28aa920ca1932f8d7d7 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:33:22 -0400 Subject: [PATCH 167/661] Some notes on configuration changes and variables needed with wip/push-notify --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2bf40e19279..7f2faa52a78 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_TARGET_BOTTOM` (`80`) - must be set using mg/dl units; the bottom of the target range, also used to draw the line on the chart * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's - * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks + * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site - + * `PUSHOVER_GROUP_KEY` - If you wish to send to a Pushover delivery group instead of just to a user, Specify a group key in this variable, See the [Pushover](https://pushover.net) site to set up a delivery group and get a group key. + #### Core @@ -132,7 +133,21 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled - +#### A note on BWP/Bolus wizard preview + * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment + likely won't run cause it couldn't find some profile values that bwp is looking for + * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information + ```json +{ + "carbratio": 7.5, // Insulin to carb ratio + "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + "dia": 4, // Duration of insulin action used for calculating IOB remaining in iob/bwp + "sens": 35, // Insulin Sensitivity Factor how much one unit lowers your blood glucose + "target_low": 95, // Bottom number for your target range for BWP + "target_high": 120 // Top number for your target range for BWP +} + ``` + ## Setting environment variables Easy to emulate on the commandline: From db71b6e3bddd8e34b5db2bf394609a170962d3dc Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:37:51 -0400 Subject: [PATCH 168/661] Additional cleanup and stuff. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7f2faa52a78..57f96b77b72 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information - ```json + ```bash { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) From 17e8689d1a173574f0bf67af5071db97ad4eee3f Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:39:42 -0400 Subject: [PATCH 169/661] Fix a typo... --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57f96b77b72..03c794d5217 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information - ```bash +```bash { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) @@ -146,7 +146,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. "target_low": 95, // Bottom number for your target range for BWP "target_high": 120 // Top number for your target range for BWP } - ``` +``` ## Setting environment variables Easy to emulate on the commandline: From 9f2008d6c0daf6abe730555ee37c2885ca974abb Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:41:00 -0400 Subject: [PATCH 170/661] Cleanup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03c794d5217..1b3684bb7cc 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information -```bash +```json { "carbratio": 7.5, // Insulin to carb ratio "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) From aac13639ec5dd9c33aaefabe8e534a8c4d9b2c61 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 12:58:49 -0400 Subject: [PATCH 171/661] A bit more mentioned on BWP/IOB profile info --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1b3684bb7cc..55835dcaea0 100644 --- a/README.md +++ b/README.md @@ -136,17 +136,24 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. #### A note on BWP/Bolus wizard preview * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment likely won't run cause it couldn't find some profile values that bwp is looking for - * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information + * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information, + Data below is provided as an example please ensure you change it to fit. ```json { - "carbratio": 7.5, // Insulin to carb ratio - "carbs_hr": 30, // see here [IOB-COB site](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) - "dia": 4, // Duration of insulin action used for calculating IOB remaining in iob/bwp - "sens": 35, // Insulin Sensitivity Factor how much one unit lowers your blood glucose - "target_low": 95, // Bottom number for your target range for BWP - "target_high": 120 // Top number for your target range for BWP + "carbratio": 7.5, + "carbs_hr": 30, + "dia": 4, + "sens": 35, + "target_low": 95, + "target_high": 120 } ``` + * The ```carbratio``` value should be the insulin to carb ratio used for BWP. + The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. + The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. + The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. + The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. + Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) ## Setting environment variables Easy to emulate on the commandline: From e31e539639fecccd683c45f807daef5c1c767761 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 13:00:36 -0400 Subject: [PATCH 172/661] Finishing touches --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 55835dcaea0..d990735d1e6 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. } ``` * The ```carbratio``` value should be the insulin to carb ratio used for BWP. - The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. - The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. - The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. - The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + * The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. + * The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. + * The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. + * The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. + * Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) ## Setting environment variables Easy to emulate on the commandline: From 3f9dc24b9d35b4de5801f8725d6855a942e28c00 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Fri, 19 Jun 2015 15:03:08 -0400 Subject: [PATCH 173/661] Instead of noting the PUSHOVER_GROUP_KEY added a note that a group key could be used instead of a user key --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d990735d1e6..35e456103d7 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site - * `PUSHOVER_GROUP_KEY` - If you wish to send to a Pushover delivery group instead of just to a user, Specify a group key in this variable, See the [Pushover](https://pushover.net) site to set up a delivery group and get a group key. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. #### Core From 387e4e33e9698b677931b823337b74ebb7ec8ee2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 16:50:19 -0700 Subject: [PATCH 174/661] make sure the placeholder points are added when the now line moves --- static/js/client.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 8249f75251d..4c0f0bc1c38 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -301,6 +301,21 @@ function nsArrayDiff(oldArray, newArray) { } } + function addPlaceholderPoints () { + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove all 'color != 'none'' code + var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { + data.push({ + date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' + }); + } + } + // clears the current user brush and resets to the current real time data function updateBrushToNow(skipBrushing) { @@ -313,6 +328,8 @@ function nsArrayDiff(oldArray, newArray) { .duration(UPDATE_TRANS_MS) .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + addPlaceholderPoints(); + if (!skipBrushing) { brushed(true); @@ -1693,18 +1710,7 @@ function nsArrayDiff(oldArray, newArray) { data = []; data = data.concat(temp1, temp2); - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code - var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); - var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; - for (var i = 1; i <= n; i++) { - data.push({ - date: new Date(lastTime + (i * FIVE_MINS_IN_MS)), y: 100, sgv: scaleBg(100), color: 'none', type: 'server-forecast' - }); - } + addPlaceholderPoints(); data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); From 73b88b2ac7d31fd0977cd760bf62ad721834897c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 18:35:41 -0700 Subject: [PATCH 175/661] update the README with information about the plugins, treatment profile, and pushover --- README.md | 72 ++++++++++++++++++++++++++++--- env.js | 2 +- lib/plugins/boluswizardpreview.js | 4 +- lib/plugins/treatmentnotify.js | 3 +- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2bf40e19279..fe822be8bcd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -cgm-remote-monitor (a.k.a. Nightscout) +Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== [![Build Status][build-img]][build-url] @@ -19,6 +19,8 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. +#[#WeAreNotWaiting](https://twitter.com/hashtag/WeAreNotWaiting) and [this](https://vimeo.com/109767890) is why. + Community maintained fork of the [original cgm-remote-monitor][original]. @@ -92,7 +94,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. #### Features/Labs - * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob` + * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart @@ -100,8 +102,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/) - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications for Care Portal treatments, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user *(or group)* key, can be found in the top left of the [Pushover](https://pushover.net/) site #### Core @@ -115,7 +117,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `SSL_CERT` - Path to your ssl cert file, so that ssl(https) can be enabled directly in node.js * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js - + #### Predefined values for your browser settings (optional) * `TIME_FORMAT` (`12`)- possible values `12` or `24` * `NIGHT_MODE` (`off`) - possible values `on` or `off` @@ -131,8 +133,64 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled - - + +### Plugins + + Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. + + The built-in/example plugins that are available by default are listed below. The plugins may still need to be `ENABLE`'d. + + **Built-in/Example Plugins:** + + * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). + + * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). + + * `bwp` (Bolus Wizard Preview) ***Example only*** - Calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) + * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. + + * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. + + * `BWP_SNOOZE_MINS` (`10`) - minutes to snooze when there is enough IOB to cover a high BG. + + * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. + + * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. + + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** + + * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. + + * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** + + * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). + +#### 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. + + Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. + +### Treatment Profile + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection with the following fields: + + * `dia` (Insulin duration) - defaults to 3 hours + * `carbs_hr` ([Carbs per hour](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/)) + * `carbratio` - grams per unit of insulin + * `sens` (Insulin sensitivity) field from the treatment profile + * `target_high` - Upper target for correction boluses + * `target_low` - Lower target for correction boluses + +### Pushover + In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. + + To get started install the Pushover application on your iOS or Android device and create an account. + + Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. + + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png) + + Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). + ## Setting environment variables Easy to emulate on the commandline: diff --git a/env.js b/env.js index 9575d754818..86d6e8424e4 100644 --- a/env.js +++ b/env.js @@ -229,7 +229,7 @@ function findExtendedSettings (enables, envs) { if (split > -1 && split <= key.length) { var exts = extended[enable] || {}; extended[enable] = exts; - var ext = _.kebabCase(key.substring(split + 1).toLowerCase()); + var ext = _.camelCase(key.substring(split + 1).toLowerCase()); exts[ext] = value; } } diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 78d08fc558b..a519713c584 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -56,10 +56,12 @@ function init() { var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; var urgentBWP = Number(sbx.extendedSettings.urgent) || 1.00; + var snoozeLength = (sbx.extendedSettings.snoozeMins && Number(sbx.extendedSettings.snoozeMins) * 60 * 1000) || TEN_MINS; + if (results.lastSGV > sbx.thresholds.bg_target_top && results.bolusEstimate < snoozeBWP) { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , lengthMills: FIFTEEN_MINS + , lengthMills: snoozeLength , debug: results }) } else if (results.bolusEstimate > warnBWP) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 72b57fdb3e6..255aa7d9f26 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -39,9 +39,10 @@ function init() { }; function autoSnoozeAlarms(sbx) { + var snoozeLength = (sbx.extendedSettings.snoozeMins && Number(sbx.extendedSettings.snoozeMins) * 60 * 1000) || TIME_10_MINS_MS; sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT - , lengthMills: TIME_10_MINS_MS + , lengthMills: snoozeLength //, debug: results }); } From ba40c9dd4bd418d09013e2cc7c82f62fe78ed55a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 18:43:41 -0700 Subject: [PATCH 176/661] more readme updates --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fe822be8bcd..a9171f795b7 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. -#[#WeAreNotWaiting](https://twitter.com/hashtag/WeAreNotWaiting) and [this](https://vimeo.com/109767890) is why. +#[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. Community maintained fork of the [original cgm-remote-monitor][original]. @@ -132,13 +132,13 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm - * `SHOW_PLUGINS` - enabled plugins that should have their visualisations shown, defaults to all enabled + * `SHOW_PLUGINS` - enabled plugins that should have their visualizations shown, defaults to all enabled ### Plugins Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. - The built-in/example plugins that are available by default are listed below. The plugins may still need to be `ENABLE`'d. + The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding the to the `ENABLE` environment variable. **Built-in/Example Plugins:** @@ -174,8 +174,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection with the following fields: * `dia` (Insulin duration) - defaults to 3 hours - * `carbs_hr` ([Carbs per hour](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/)) - * `carbratio` - grams per unit of insulin + * `carbs_hr` (Carbs per Hour) - The number of carbs that are processed per hour, for more information see [#DIYPS](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/) + * `carbratio` (Carb Ratio) - grams per unit of insulin * `sens` (Insulin sensitivity) field from the treatment profile * `target_high` - Upper target for correction boluses * `target_low` - Lower target for correction boluses From c9f412680f8591680f757f3c52d5d82132057aaf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 19 Jun 2015 19:45:42 -0700 Subject: [PATCH 177/661] more readme fix ups... --- README.md | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7027a4969fd..e84888adb52 100644 --- a/README.md +++ b/README.md @@ -194,29 +194,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `target_high` - Upper target for correction boluses * `target_low` - Lower target for correction boluses - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) - -#### A note on BWP/Bolus wizard preview - * If your ENABLE variable has bwp enabled, and you don't have a profile set up in mongo your Nightscout deployment - likely won't run cause it couldn't find some profile values that bwp is looking for - * To provide the profile information you will have to add a document to the profile collection in you mongo database with the following information, - Data below is provided as an example please ensure you change it to fit. -```json -{ - "carbratio": 7.5, - "carbs_hr": 30, - "dia": 4, - "sens": 35, - "target_low": 95, - "target_high": 120 -} -``` - * The ```carbratio``` value should be the insulin to carb ratio used for BWP. - * The ```dia``` value should be the duration of insulin action you want IOB/BWP to use in calculating how much insulin is left active. - * The ```sens``` value should be the Insulin Sensitivity Factor used by BWP, How much one unit of insulin will normally lower blood glucose. - * The ```target_low``` value should be the low number of the target zone you want BWP calculations to aim for. - * The ```target_high``` value should be the high number of the target zone you want BWP calculations to aim for. - * Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website) + Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). ### Pushover In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. @@ -225,7 +203,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. - You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png) + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). From 2a4434defbca8ebac7a99b4bafaf4fac2cd6d58d Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 20 Jun 2015 10:56:38 +0300 Subject: [PATCH 178/661] Stronger language the BWP plugin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e84888adb52..ba3933ec3f3 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). - * `bwp` (Bolus Wizard Preview) ***Example only*** - Calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) + * `bwp` (Bolus Wizard Preview) - This plugin in intended for the purpose of automatically snoozing alarms when the CGM indicates high blood sugar but there is also insulin on board (IOB) and secondly, alerting to user that it might be beneficial to measure the blood sugar using a glucometer and dosing insulin as calculated by the pump or instructed by trained medicare professionals. ***The values provided by the plugin are provided as a reference based on CGM data and insulin sensitivity you have configured, and are not intended to be used as a reference for bolus calculation.*** The plugin calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. From 735a6f1abdeffa8faf8df558c58b4abc1a1279f8 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:09:32 +0200 Subject: [PATCH 179/661] Added a file which enables to test the REST api by inserting data based on the sine (just like populate.js but using the REST api). --- testing/populate.js | 135 +++++++-------------------------------- testing/populate_rest.js | 41 ++++++++++++ 2 files changed, 63 insertions(+), 113 deletions(-) create mode 100644 testing/populate_rest.js diff --git a/testing/populate.js b/testing/populate.js index f4fff19ae6e..67d133d7599 100644 --- a/testing/populate.js +++ b/testing/populate.js @@ -1,125 +1,34 @@ 'use strict'; -/////////////////////////////////////////////////// -// This script is intended to be run as a cron job -// every n-minutes or whatever the equiv is on windows -// -// Author: John A. [euclidjda](https://github.com/euclidjda) -// Source: https://gist.github.com/euclidjda/4ae207a89921f21382a9 -/////////////////////////////////////////////////// -/////////////////////////////////////////////////// -// DB Connection setup and utils -/////////////////////////////////////////////////// +var mongodb = require('mongodb'); +var software = require('./../package.json'); +var env = require('./../env')(); -var mongodb = require('mongodb'); -var software = require('./package.json'); -var env = require('./env')( ); +var util = require('./helpers/util'); main(); -function main( ) { - +function main() { var MongoClient = mongodb.MongoClient; - - MongoClient.connect(env.mongo, function connected (err, db) { - - console.log("Connected to mongo, ERROR: %j", err); - if (err) { throw err; } - populate_collection( db ); - + MongoClient.connect(env.mongo, function connected(err, db) { + + console.log("Connecting to mongo..."); + if (err) { + console.log("Error occurred: ", err); + throw err; + } + populate_collection(db); }); - -} - -function populate_collection( db ) { - - //console.log( 'mongo = ' + env.mongo ); - //console.log( 'collection = ' + env.mongo_collection ); - - var cgm_collection = db.collection( env.mongo_collection ); - - var new_cgm_record = get_cgm_record(); - - cgm_collection.insert( new_cgm_record, function(err,created) { - - // TODO: Error checking - process.exit( 0 ); - - } ); - - -} - -function get_cgm_record( ) { - - var dateobj = new Date(); - var datemil = dateobj.getTime(); - var datesec = datemil / 1000; - var datestr = getDateString( dateobj ); - - // We put the time in a range from -1 to +1 for every thiry minute period - var range = (datesec % 1800)/900 - 1.0; - - // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) - var sgv = Math.floor(360*(Math.cos( 10.0 * range / 3.14 ) / 2 + 0.5)) + 40; - var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; - - console.log( 'Writing Record: '); - console.log( 'sgv = ' + sgv ); - console.log( 'date = ' + datemil ); - console.log( 'dir = ' + dir ); - console.log( 'str = ' + datestr ); - - var mondo_db = null; - var doc = { 'device' :'dexcom' , - 'date' : datemil , - 'sgv' : sgv , - 'direction' : dir , - 'dateString' : datestr }; - - - return doc; } -function getDateString( d ) { - - // How I wish js had strftime. This would be one line of code! - - var month = d.getMonth(); - var day = d.getDay(); - var year = d.getFullYear(); - - if (month < 10 ) month = '0'+month; - if (day < 10 ) day = '0'+day; - - var hour = d.getHours(); - var min = d.getMinutes(); - var sec = d.getSeconds(); - - var ampm = 'PM'; - - if (hour < 12) - { - ampm = "AM"; - } - else - { - ampm = "PM"; - } - - if (hour == 0) - { - hour = 12; - } - if (hour > 12) - { - hour = hour - 12; - } - - if (hour < 10) hour = '0' + hour; - if (min < 10) min = '0' + min; - if (sec < 10) sec = '0' + sec; - - return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; +function populate_collection(db) { + var cgm_collection = db.collection(env.mongo_collection); + var new_cgm_record = util.get_cgm_record(); + cgm_collection.insert(new_cgm_record, function (err, created) { + if (err) { + throw err; + } + process.exit(0); + }); } diff --git a/testing/populate_rest.js b/testing/populate_rest.js new file mode 100644 index 00000000000..fc8e614d7c9 --- /dev/null +++ b/testing/populate_rest.js @@ -0,0 +1,41 @@ +'use strict'; + +var software = require('./../package.json'); +var env = require('./../env')(); +var http = require('http'); +var util = require('./util'); + +main(); + +function main() { + send_entry_rest(); +} + +function send_entry_rest() { + var new_cgm_record = util.get_cgm_record(); + var new_cgm_record_string = JSON.stringify(new_cgm_record); + + var options = { + host: 'localhost', + port: env.PORT, + path: '/api/v1/entries/', + method: 'POST', + headers: { + 'api-secret' : env.api_secret, + 'Content-Type': 'application/json', + 'Content-Length': new_cgm_record_string.length + } + }; + + var req = http.request(options, function(res) { + console.log("Ok: ", res.statusCode); + }); + + req.on('error', function(e) { + console.error('error'); + console.error(e); + }); + + req.write(new_cgm_record_string); + req.end(); +} \ No newline at end of file From 4697a739bda805e33aa5a588a46a36a0b350f89e Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:11:45 +0200 Subject: [PATCH 180/661] Modified the assertions in the tests a bit, if the .res is not an array, it doest nog have a .length property and throws an unreadable exception. --- tests/api.entries.test.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index b65ec22000f..72c5a76fb2e 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -37,9 +37,8 @@ describe('Entries REST api', function ( ) { .get('/entries.json?count=' + count) .expect(200) .end(function (err, res) { - // console.log('body', res.body); - res.body.length.should.equal(count); - done( ); + res.body.should.be.instanceof(Array).and.have.lengthOf(count); + done(); }); }); @@ -49,8 +48,7 @@ describe('Entries REST api', function ( ) { .get('/entries.json') .expect(200) .end(function (err, res) { - // console.log('body', res.body); - res.body.length.should.equal(defaultCount); + res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount); done( ); }); }); @@ -60,9 +58,8 @@ describe('Entries REST api', function ( ) { .get('/entries/current.json') .expect(200) .end(function (err, res) { - res.body.length.should.equal(1); - done( ); - // console.log('err', err, 'res', res); + res.body.should.be.instanceof(Array).and.have.lengthOf(1); + done(); }); }); @@ -74,7 +71,7 @@ describe('Entries REST api', function ( ) { .get('/entries/'+currentId+'.json') .expect(200) .end(function (err, res) { - res.body.length.should.equal(1); + res.body.should.be.instanceof(Array).and.have.lengthOf(1); res.body[0]._id.should.equal(currentId); done( ); }); @@ -88,10 +85,8 @@ describe('Entries REST api', function ( ) { .send(load('json')) .expect(201) .end(function (err, res) { - // console.log(res.body); - res.body.length.should.equal(30); - done( ); - // console.log('err', err, 'res', res); + res.body.should.be.instanceof(Array).and.have.lengthOf(30); + done(); }); }); }); From 13e93dff7c200093d3067be8ff30fc5552c17e7d Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:13:40 +0200 Subject: [PATCH 181/661] Added test for storage itself. --- tests/storage.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/storage.test.js diff --git a/tests/storage.test.js b/tests/storage.test.js new file mode 100644 index 00000000000..ff9ff06171c --- /dev/null +++ b/tests/storage.test.js @@ -0,0 +1,28 @@ +'use strict'; + +var request = require('supertest'); +var should = require('should'); + +// TODO: It would be better to have something like the dependency injection pattern for testing the datastore +describe('DATABASE_ABSTRACTION', function ( ) { + var env = require('../env')( ); + + it('Should able to connect to the database', function (done) { + require('../lib/storage')(env, function ( err ) { + should(err).be.exactly(null); + done(); + }); + }); + + it('Should throw an error when connecting to the database with an invalid connection string', function (done) { + (function() { + return require('../lib/storage')(env, function (err) { + }, { + connectionUrl: 'this is invalid' + }); + }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); + + done(); + }); +}); + From d018e9d3f2c4dd58c3f973cfc1e625a9a4827a20 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:14:45 +0200 Subject: [PATCH 182/661] Oops, forgot to pull the dev-branch. --- testing/util.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 testing/util.js diff --git a/testing/util.js b/testing/util.js new file mode 100644 index 00000000000..b434fc627a3 --- /dev/null +++ b/testing/util.js @@ -0,0 +1,63 @@ +'use strict'; + +exports.get_cgm_record = function() { + var dateobj = new Date(); + var datemil = dateobj.getTime(); + var datesec = datemil / 1000; + var datestr = getDateString(dateobj); + + // We put the time in a range from -1 to +1 for every thiry minute period + var range = (datesec % 1800) / 900 - 1.0; + + // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) + var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; + var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; + + console.log('Writing Record: '); + console.log('sgv = ' + sgv); + console.log('date = ' + datemil); + console.log('dir = ' + dir); + console.log('str = ' + datestr); + + return { + 'device': 'dexcom', + 'date': datemil, + 'sgv': sgv, + 'direction': dir, + 'dateString': datestr + }; +} + +function getDateString(d) { + + // How I wish js had strftime. This would be one line of code! + + var month = d.getMonth(); + var day = d.getDay(); + var year = d.getFullYear(); + + if (month < 10) month = '0' + month; + if (day < 10) day = '0' + day; + + var hour = d.getHours(); + var min = d.getMinutes(); + var sec = d.getSeconds(); + + var ampm = 'PM'; + if (hour < 12) { + ampm = "AM"; + } + + if (hour == 0) { + hour = 12; + } + if (hour > 12) { + hour = hour - 12; + } + + if (hour < 10) hour = '0' + hour; + if (min < 10) min = '0' + min; + if (sec < 10) sec = '0' + sec; + + return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; +} \ No newline at end of file From 6db10fcdf68916e97b1a8f5aa057697a3c5e1487 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:17:00 +0200 Subject: [PATCH 183/661] Not really relevant. --- tests/storage.test.js | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 tests/storage.test.js diff --git a/tests/storage.test.js b/tests/storage.test.js deleted file mode 100644 index ff9ff06171c..00000000000 --- a/tests/storage.test.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var request = require('supertest'); -var should = require('should'); - -// TODO: It would be better to have something like the dependency injection pattern for testing the datastore -describe('DATABASE_ABSTRACTION', function ( ) { - var env = require('../env')( ); - - it('Should able to connect to the database', function (done) { - require('../lib/storage')(env, function ( err ) { - should(err).be.exactly(null); - done(); - }); - }); - - it('Should throw an error when connecting to the database with an invalid connection string', function (done) { - (function() { - return require('../lib/storage')(env, function (err) { - }, { - connectionUrl: 'this is invalid' - }); - }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); - - done(); - }); -}); - From 7d149bafbda729e46e2d87160c43c193e23819f7 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 20 Jun 2015 20:59:08 +0200 Subject: [PATCH 184/661] Correct MongoDB initalization, and removed the Bootsequence plugin as it did not provide any benefits anymore. --- lib/bootevent.js | 110 ++++++++++++++++++-------------------- lib/storage.js | 10 ++-- package.json | 3 +- server.js | 9 +--- tests/api.entries.test.js | 3 +- tests/api.status.test.js | 7 +-- tests/security.test.js | 3 +- 7 files changed, 64 insertions(+), 81 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index e4af413f07c..abe6fd91659 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,74 +1,68 @@ -var bootevent = require('bootevent'); +function boot(env, booted) { + require('./storage')(env, function ready(err, store) { -function boot (env) { - var store = require('./storage')(env); - var proc = bootevent( ) - .acquire(function db (ctx, next) { - // initialize db connections - store( function ready ( ) { - console.log('storage system ready'); - ctx.store = store; - next( ); - }); - }) - .acquire(function data (ctx, next) { - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); + if(err) + console.warn('Could not succesfully connect to MongoDB.') - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); + console.log('Storage system ready'); + var ctx = { + store: store + }; - ctx.bus = require('./bus')(env, ctx); + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); + console.info("Ensuring indexes"); + store.ensureIndexes(ctx.entries(), ctx.entries.indexedFields); + store.ensureIndexes(ctx.treatments(), ctx.treatments.indexedFields); + store.ensureIndexes(ctx.devicestatus(), ctx.devicestatus.indexedFields); + store.ensureIndexes(ctx.profile(), ctx.profile.indexedFields); - function updateData ( ) { - ctx.data.update(function dataUpdated () { - ctx.bus.emit('data-loaded'); - }); - } + ctx.bus = require('./bus')(env, ctx); - ctx.bus.on('tick', function timedReloadData (tick) { - console.info('tick', tick.now); - updateData(); - }); + ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); - ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); - updateData(); - }); + function updateData() { + ctx.data.update(function dataUpdated() { + ctx.bus.emit('data-loaded'); + }); + } - ctx.bus.on('data-loaded', function updatePlugins ( ) { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); - }); + ctx.bus.on('tick', function timedReloadData(tick) { + console.info('tick', tick.now); + updateData(); + }); - ctx.bus.on('notification', ctx.pushnotify.emitNotification); + ctx.bus.on('data-received', function forceReloadData() { + console.info('got data-received event, reloading now'); + updateData(); + }); + + ctx.bus.on('data-loaded', function updatePlugins() { + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(sbx); + ctx.bus.emit('data-processed'); + }); - ctx.bus.uptime( ); + ctx.bus.on('notification', ctx.pushnotify.emitNotification); - next( ); - }) - ; - return proc; + ctx.bus.uptime(); + booted(ctx); + }); } module.exports = boot; diff --git a/lib/storage.js b/lib/storage.js index 0f8c50ba3fa..2d600dd2801 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -7,20 +7,20 @@ function init (env, cb) { var my = { }; function maybe_connect (cb) { if (my.db) { - console.log("Reusing mongo connection"); + console.log("Reusing MongoDB connection handler"); if (cb && cb.call) { cb(null, mongo); } return; } if (!env.mongo) { - throw new Error("Mongo string is missing"); + throw new Error("MongoDB connection string is missing"); } - console.log("Connecting to mongo"); + console.log("Setting up new connection to MongoDB"); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { - console.log("Error connecting to mongo, ERROR: %j", err); + console.log("Error connecting to MongoDB, ERROR: %j", err); throw err; } else { - console.log("Connected to mongo"); + console.log("Successfully established a connected to mongo"); } my.db = db; mongo.pool.db = my.db = db; diff --git a/package.json b/package.json index 649081ba62c..ff62a4d44b1 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "dependencies": { "async": "^0.9.0", "body-parser": "^1.4.3", - "bootevent": "0.0.1", "bower": "^1.3.8", "browserify-express": "^0.1.4", "compression": "^1.4.2", @@ -56,7 +55,7 @@ "git-rev": "git://github.com/bewest/git-rev.git", "lodash": "^3.9.1", "long": "~2.2.3", - "mongodb": "^1.4.7", + "mongodb": "2.0.34", "moment": "2.8.1", "mqtt": "~0.3.11", "node-cache": "^3.0.0", diff --git a/server.js b/server.js index 8dcecfce491..bb3c1cc61c8 100644 --- a/server.js +++ b/server.js @@ -43,8 +43,7 @@ function create (app) { return transport.createServer(app); } -var bootevent = require('./lib/bootevent'); -bootevent(env).boot(function booted (ctx) { +require('./lib/bootevent')(env, function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); @@ -67,8 +66,4 @@ bootevent(env).boot(function booted (ctx) { ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); - -}) -; - -/////////////////////////////////////////////////// +}); \ No newline at end of file diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 064283950d2..5985188f17a 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -12,8 +12,7 @@ describe('Entries REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { + require('../lib/bootevent')(env, function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/api.status.test.js b/tests/api.status.test.js index 88a9bc23b9a..a8f5c1cf58a 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -9,16 +9,13 @@ describe('Status REST api', function ( ) { env.enable = "careportal rawbg"; env.api_secret = 'this is my long pass phrase'; this.wares = require('../lib/middleware/')(env); - var store = require('../lib/storage')(env); this.app = require('express')( ); this.app.enable('api'); var self = this; - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { - self.app.use('/api', api(env, ctx.entries)); + require('../lib/bootevent')(env, function booted (ctx) { + self.app.use('/api', api(env, ctx.entries)); done(); }); - }); it('should be a module', function ( ) { diff --git a/tests/security.test.js b/tests/security.test.js index 8852aba855b..8154c505c16 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,8 +10,7 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - var bootevent = require('../lib/bootevent'); - bootevent(env).boot(function booted (ctx) { + require('../lib/bootevent')(env, function booted (ctx) { var wares = require('../lib/middleware/')(env); ctx.app = api(env, wares, ctx); scope.app = ctx.app; From 8e87e10a8473c50566f929b9ebdd2b378ede2336 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Jun 2015 19:51:56 -0700 Subject: [PATCH 185/661] clean up bootevent, create more phases --- lib/bootevent.js | 137 +++++++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 58 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index e4af413f07c..f65c039251e 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,72 +1,93 @@ +'use strict'; var bootevent = require('bootevent'); function boot (env) { - var store = require('./storage')(env); - var proc = bootevent( ) - .acquire(function db (ctx, next) { - // initialize db connections - store( function ready ( ) { - console.log('storage system ready'); - ctx.store = store; - next( ); - }); - }) - .acquire(function data (ctx, next) { - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); - - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - - ctx.bus = require('./bus')(env, ctx); - - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); - - function updateData ( ) { - ctx.data.update(function dataUpdated () { - ctx.bus.emit('data-loaded'); - }); - } - - ctx.bus.on('tick', function timedReloadData (tick) { - console.info('tick', tick.now); - updateData(); - }); - ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); - updateData(); - }); + function setupMongo (ctx, next) { + var store = require('./storage')(env); + // initialize db connections + store( function ready ( ) { + console.log('storage system ready'); + ctx.store = store; + + next( ); + }); + } + + function setupInternals (ctx, next) { + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); + ctx.treatments = require('./treatments')(env, ctx); + ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); + ctx.profile = require('./profile')(env.profile_collection, ctx); + ctx.pebble = require('./pebble')(env, ctx); + ctx.bus = require('./bus')(env, ctx); + ctx.data = require('./data')(env, ctx); + ctx.notifications = require('./notifications')(env, ctx); + + next( ); + } + + function ensureIndexes (ctx, next) { + console.info("Ensuring indexes"); + ctx.store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); + ctx.store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); + ctx.store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); + ctx.store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields); - ctx.bus.on('data-loaded', function updatePlugins ( ) { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); + next( ); + } + + function setupListeners (ctx, next) { + function updateData ( ) { + ctx.data.update(function dataUpdated () { + ctx.bus.emit('data-loaded'); }); + } - ctx.bus.on('notification', ctx.pushnotify.emitNotification); + ctx.bus.on('tick', function timedReloadData (tick) { + console.info('tick', tick.now); + updateData(); + }); - ctx.bus.uptime( ); + ctx.bus.on('data-received', function forceReloadData ( ) { + console.info('got data-received event, reloading now'); + updateData(); + }); - next( ); - }) + ctx.bus.on('data-loaded', function updatePlugins ( ) { + var sbx = require('./sandbox')().serverInit(env, ctx); + ctx.plugins.setProperties(sbx); + ctx.notifications.initRequests(); + ctx.plugins.checkNotifications(sbx); + ctx.notifications.process(sbx); + ctx.bus.emit('data-processed'); + }); + + ctx.bus.on('notification', ctx.pushnotify.emitNotification); + + next( ); + } + + function finishBoot (ctx, next) { + ctx.bus.uptime( ); + + next( ); + } + + var proc = bootevent( ) + .acquire(setupMongo) + .acquire(setupInternals) + .acquire(ensureIndexes) + .acquire(setupListeners) + .acquire(finishBoot) ; + return proc; } From 7af04ee422aece54484c381db815a294eaff775e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 20 Jun 2015 21:02:18 -0700 Subject: [PATCH 186/661] made BG delta a normal plugin --- env.js | 9 ++++++++- lib/plugins/delta.js | 42 +++++++++++++++++++++++++++++++++++++++ lib/plugins/index.js | 3 ++- lib/plugins/pluginbase.js | 29 +++++++++++++++++++-------- static/js/client.js | 34 ------------------------------- tests/delta.test.js | 32 +++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 lib/plugins/delta.js create mode 100644 tests/delta.test.js diff --git a/env.js b/env.js index 86d6e8424e4..7f17514defc 100644 --- a/env.js +++ b/env.js @@ -93,6 +93,11 @@ function config ( ) { env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); + //TODO: figure out something for some plugins to have them shown by default + if (env.defaults.showPlugins != '') { + env.defaults.showPlugins += ' delta' + } + //console.log(JSON.stringify(env.defaults)); env.SSL_KEY = readENV('SSL_KEY'); @@ -183,7 +188,9 @@ function config ( ) { //TODO: after config changes are documented this shouldn't be auto enabled env.enable += ' treatmentnotify'; } - env.enable += ' errorcodes'; + + //TODO: figure out something for default plugins, how can they be disabled? + env.enable += ' delta errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js new file mode 100644 index 00000000000..ae8c4e97187 --- /dev/null +++ b/lib/plugins/delta.js @@ -0,0 +1,42 @@ +'use strict'; + +function init() { + + function delta() { + return delta; + } + + delta.label = 'BG Delta'; + delta.pluginType = 'pill-major'; + delta.pillFlip = true; + + delta.setProperties = function setProperties (sbx) { + sbx.offerProperty('delta', function setDelta ( ) { + + var result = { display: null }; + + if (sbx.data.sgvs.length < 2) { return result; } + + var lastSVG = sbx.data.sgvs[sbx.data.sgvs.length - 1].y; + var prevSVG = sbx.data.sgvs[sbx.data.sgvs.length - 2].y; + + if (lastSVG < 40 || prevSVG < 40) { return result; } + + result.value = sbx.scaleBg(lastSVG) - sbx.scaleBg(prevSVG); + result.display = (result.value >= 0 ? '+' : '') + result.value; + + return result; + }); + }; + + delta.updateVisualisation = function updateVisualisation (sbx) { + var info = null; + var prop = sbx.properties.delta; + sbx.pluginBase.updatePillText(delta, prop.display, sbx.unitsLabel, info); + }; + + return delta(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5a7c3ce27c9..c6f596f322f 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -14,7 +14,8 @@ function init() { plugins.base = require('./pluginbase'); var clientDefaultPlugins = [ - require('./iob')() + require('./delta')() + , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 9ef44913970..82d748a5dd7 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -8,23 +8,36 @@ function init (majorPills, minorPills, tooltip) { return pluginBase; } - pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { - - var pillName = "span.pill." + plugin.name; - + function findOrCreatePill (plugin, label) { var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; - + var pillName = "span.pill." + plugin.name; var pill = container.find(pillName); if (!pill || pill.length == 0) { - pill = $(''); + pill = $(''); + var pillLabel = $(''); + var pillValue = $(''); + if (plugin.pillFlip) { + pill.append(pillValue); + pill.append(pillLabel); + } else { + pill.append(pillLabel); + pill.append(pillValue); + } + container.append(pill); } - pill.find('em').text(updatedText); + return pill; + } - if (info) { + pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { + var pill = findOrCreatePill(plugin, label); + + pill.find('em').toggle(updatedText != null).text(updatedText); + + if (info) { var html = _.map(info, function mapInfo (i) { return '' + i.label + ' ' + i.value; }).join('
    \n'); diff --git a/static/js/client.js b/static/js/client.js index 4c0f0bc1c38..299eada6b03 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -469,30 +469,6 @@ function nsArrayDiff(oldArray, newArray) { } - function updateBGDelta(prevEntry, currentEntry) { - - var pill = majorPills.find('span.pill.bgdelta'); - if (!pill || pill.length == 0) { - pill = $(''); - majorPills.append(pill); - } - - var deltaDisplay = calcDeltaDisplay(prevEntry, currentEntry); - - if (deltaDisplay == null) { - pill.children('em').hide(); - } else { - pill.children('em').text(deltaDisplay).show(); - } - - if (browserSettings.units == 'mmol') { - pill.children('label').text('mmol/L'); - } else { - pill.children('label').text('mg/dL'); - } - - } - function updatePlugins(sgvs, time) { var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); @@ -543,15 +519,7 @@ function nsArrayDiff(oldArray, newArray) { if (focusPoint) { updateCurrentSGV(focusPoint); currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); - - var prevfocusPoint = nowData.length > lookback ? nowData[nowData.length - 2] : null; - if (prevfocusPoint) { - updateBGDelta(prevfocusPoint, focusPoint); - } else { - updateBGDelta(); - } } else { - updateBGDelta(); currentBG.text('---'); currentDirection.text('-'); rawNoise.hide(); @@ -591,8 +559,6 @@ function nsArrayDiff(oldArray, newArray) { $('#uploaderBattery').hide(); } - updateBGDelta(prevSGV, latestSGV); - updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); diff --git a/tests/delta.test.js b/tests/delta.test.js new file mode 100644 index 00000000000..788811ad01f --- /dev/null +++ b/tests/delta.test.js @@ -0,0 +1,32 @@ +'use strict'; + +var should = require('should'); + +describe('Delta', function ( ) { + var delta = require('../lib/plugins/delta')(); + var sandbox = require('../lib/sandbox')(); + + var pluginBase = {}; + var data = {sgvs: [{y: 100}, {y: 105}]}; + var app = { }; + var clientSettings = { + units: 'mg/dl' + }; + + it('should calculate BG Delta', function (done) { + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('delta'); + var result = setter(); + result.value.should.equal(5); + result.display.should.equal('+5'); + done() + }; + + delta.setProperties(sbx); + + }); + + +}); From 0e45c3f1d4113435f17fd2d7a0b5a55bf0081a52 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 09:09:32 +0200 Subject: [PATCH 187/661] Restored bootevent in the package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ff62a4d44b1..463eca41490 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "async": "^0.9.0", "body-parser": "^1.4.3", "bower": "^1.3.8", + "bootevent": "0.0.1", "browserify-express": "^0.1.4", "compression": "^1.4.2", "errorhandler": "^1.1.1", From 6b8a17b7818c6fa7d63180570ada0e0ef09ac7c6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 00:21:40 -0700 Subject: [PATCH 188/661] converted the uploader battery pill to a plugin; some refactoring and cleanup --- env.js | 4 +- lib/plugins/boluswizardpreview.js | 17 +++++---- lib/plugins/cannulaage.js | 6 ++- lib/plugins/cob.js | 6 ++- lib/plugins/delta.js | 6 ++- lib/plugins/index.js | 1 + lib/plugins/iob.js | 6 ++- lib/plugins/pluginbase.js | 43 +++++++++++++++++----- lib/plugins/upbat.js | 61 +++++++++++++++++++++++++++++++ static/css/main.css | 52 ++++++++++++-------------- static/index.html | 9 ++--- static/js/client.js | 22 ++--------- tests/cannulaage.test.js | 4 +- tests/upbat.test.js | 49 +++++++++++++++++++++++++ 14 files changed, 207 insertions(+), 79 deletions(-) create mode 100644 lib/plugins/upbat.js create mode 100644 tests/upbat.test.js diff --git a/env.js b/env.js index 7f17514defc..6321bbee4fa 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,7 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins != '') { - env.defaults.showPlugins += ' delta' + env.defaults.showPlugins += ' delta upbat' } //console.log(JSON.stringify(env.defaults)); @@ -190,7 +190,7 @@ function config ( ) { } //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta errorcodes'; + env.enable += ' delta upbat errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a519713c584..5afb1000bc6 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -94,14 +94,15 @@ function init() { var results = bwp.calc(sbx); - // display text - var info = [ - {label: 'Insulin on Board', value: results.displayIOB + 'U'} - , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} - , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} - ]; - - sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); + sbx.pluginBase.updatePillText(bwp, { + value: results.bolusEstimateDisplay + 'U' + , label: 'BWP' + , info: [ + {label: 'Insulin on Board', value: results.displayIOB + 'U'} + , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} + , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} + ] + }); }; diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 41fd6c75344..50182984ccd 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -40,7 +40,11 @@ function init() { var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; if (message != '') info.push({label: 'Notes:', value: message}); - sbx.pluginBase.updatePillText(cage, age + 'h', 'CAGE', info); + sbx.pluginBase.updatePillText(cage, { + value: age + 'h' + , label: 'CAGE' + , info: info + }); }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 7ce5e71619e..4877a5e464d 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -158,7 +158,11 @@ function init() { info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(sbx, displayCob + " g", 'COB', info); + sbx.pluginBase.updatePillText(sbx, { + value: displayCob + " g" + , label: 'COB' + , info: info + }); }; return cob(); diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index ae8c4e97187..f5a10c0fe06 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -30,9 +30,11 @@ function init() { }; delta.updateVisualisation = function updateVisualisation (sbx) { - var info = null; var prop = sbx.properties.delta; - sbx.pluginBase.updatePillText(delta, prop.display, sbx.unitsLabel, info); + sbx.pluginBase.updatePillText(delta, { + value: prop.display + , label: sbx.unitsLabel + }); }; return delta(); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index c6f596f322f..c874db5004b 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -19,6 +19,7 @@ function init() { , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() + , require('./upbat')() ]; var serverDefaultPlugins = [ diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 806107f61c2..5df2f9a56dc 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -99,7 +99,11 @@ function init() { info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] } - sbx.pluginBase.updatePillText(iob, sbx.roundInsulinForDisplayFormat(prop.display) + 'U', 'IOB', info); + sbx.pluginBase.updatePillText(iob, { + value: sbx.roundInsulinForDisplayFormat(prop.display) + 'U' + , label: 'IOB' + , info: info + }); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 82d748a5dd7..55e4599935b 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,20 +2,31 @@ var _ = require('lodash'); -function init (majorPills, minorPills, tooltip) { +function init (majorPills, minorPills, statusPills, tooltip) { function pluginBase ( ) { return pluginBase; } - function findOrCreatePill (plugin, label) { - var container = plugin.pluginType == 'pill-major' ? majorPills : minorPills; + function findOrCreatePill (plugin) { + var container = null; + + if (plugin.pluginType == 'pill-major') { + container = majorPills; + } else if (plugin.pluginType == 'pill-status') { + container = statusPills; + } else { + container = minorPills + } + var pillName = "span.pill." + plugin.name; var pill = container.find(pillName); + var classes = 'pill ' + plugin.name; + if (!pill || pill.length == 0) { - pill = $(''); - var pillLabel = $(''); + pill = $(''); + var pillLabel = $(''); var pillValue = $(''); if (plugin.pillFlip) { pill.append(pillValue); @@ -26,19 +37,31 @@ function init (majorPills, minorPills, tooltip) { } container.append(pill); + } else { + //reset in case a pill class was added and needs to be removed + pill.attr('class', classes); } return pill; } - pluginBase.updatePillText = function updatePillText (plugin, updatedText, label, info) { + pluginBase.updatePillText = function updatePillText (plugin, options) { + + var pill = findOrCreatePill(plugin); + + pill.toggle(!options.hide); + pill.addClass(options.pillClass); - var pill = findOrCreatePill(plugin, label); + pill.find('label').attr('class', options.labelClass).text(options.label); - pill.find('em').toggle(updatedText != null).text(updatedText); + pill.find('em') + .attr('class', options.valueClass) + .toggle(options.value != null) + .text(options.value) + ; - if (info) { - var html = _.map(info, function mapInfo (i) { + if (options.info) { + var html = _.map(options.info, function mapInfo (i) { return '' + i.label + ' ' + i.value; }).join('
    \n'); diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js new file mode 100644 index 00000000000..2adb4df912e --- /dev/null +++ b/lib/plugins/upbat.js @@ -0,0 +1,61 @@ +'use strict'; + +function init() { + + function upbat() { + return upbat; + } + + upbat.label = 'Uploader Battery'; + upbat.pluginType = 'pill-status'; + upbat.pillFlip = true; + + upbat.setProperties = function setProperties (sbx) { + sbx.offerProperty('upbat', function setUpbat ( ) { + + var result = { display: null }; + + var battery = sbx.data.uploaderBattery; + + if (battery) { + result.value = battery; + result.display = battery + '%'; + + if (battery >= 95) { + result.level = 100; + } else if (battery < 95 && battery >= 55) { + result.level = 75; + } else if (battery < 55 && battery >= 30) { + result.level = 50; + } else { + result.level = 25; + } + + if (battery <= 30 && battery > 20) { + result.status = 'warn'; + } else if (battery <= 20) { + result.status = 'urgent'; + } else { + result.status = null; + } + } + + return result; + }); + }; + + upbat.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.upbat; + + sbx.pluginBase.updatePillText(upbat, { + value: prop && prop.display + , labelClass: prop && prop.level && 'icon-battery-' + prop.level + , pillClass: prop && prop.status + }); + }; + + return upbat(); + +} + +module.exports = init; \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index 4f1bc73b5e9..bb119162bb4 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -27,14 +27,14 @@ body { z-index: 2; } -.status { +.primary { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; height: 180px; vertical-align: middle; clear: left right; } -.has-minor-pills .status { +.has-minor-pills .primary { height: 200px; } @@ -145,44 +145,40 @@ body { text-decoration: line-through; } -.time { +.status { color: #808080; font-size: 100px; line-height: 100px; } -.timebox { +.statusBox { text-align: center; width: 250px; } -.timeOther { +.statusPills { font-size: 20px; line-height: 30px; } -.loading .timeOther { +.loading .statusPills { display: none; } -.timeOther .pill { +.statusPills .pill { background: #808080; border-color: #808080; } -.timeOther .pill em { +.statusPills .pill em { color: #bdbdbd; background: #000; border-radius: 4px 0 0 4px; } -.timeOther .pill label { +.statusPills .pill label { background: #808080; } -#uploaderBattery { - display: none; -} - -#uploaderBattery label { +.pill.upbat label { padding: 0 !important; } @@ -373,18 +369,18 @@ div.tooltip { font-size: 16px; } - .time { + .status { font-size: 70px; line-height: 60px; padding-top: 8px; } - .timeOther { + .statusPills { font-size: 15px; line-height: 40px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -455,7 +451,7 @@ div.tooltip { font-size: 12px; } - .time { + .status { font-size: 50px; line-height: 35px; width: 250px; @@ -471,7 +467,7 @@ div.tooltip { } @media (max-width: 400px) { - .status { + .primary { text-align: center; margin-bottom: 0; height: 152px; @@ -517,12 +513,12 @@ div.tooltip { font-size: 20px; } - .time { + .status { padding-top: 0; font-size: 20px !important; } - .timebox { + .statusBox { width: auto; } @@ -531,13 +527,13 @@ div.tooltip { vertical-align: middle; } - .timeOther { + .statusPills { display: inline; margin-left: auto; font-size: 15px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -639,17 +635,17 @@ div.tooltip { font-size: 12px; } - .time { + .status { font-size: 50px; line-height: 40px; padding-top: 5px; } - .timeOther { + .statusPills { font-size: 15px; } - #uploaderBattery label { + .pill.upbat label { font-size: 15px !important; } @@ -703,12 +699,12 @@ div.tooltip { display: block; } - .time { + .status { position: absolute; top: 90px; } - .timebox { + .statusBox { width: 220px; } diff --git a/static/index.html b/static/index.html index b422ed627bb..e8c5d0bae0c 100644 --- a/static/index.html +++ b/static/index.html @@ -44,7 +44,7 @@

    Nightscout

    -
    +
    @@ -61,12 +61,11 @@

    Nightscout

    -
    -
    +
    +
    ---
    -
    +
    -
    diff --git a/static/js/client.js b/static/js/client.js index 299eada6b03..045d92dcddc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -419,6 +419,7 @@ function nsArrayDiff(oldArray, newArray) { , currentDirection = $('.bgStatus .currentDirection') , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') + , statusPills = $('.status .statusPills') , rawNoise = bgButton.find('.rawnoise') , rawbg = rawNoise.find('em') , noiseLevel = rawNoise.find('label') @@ -471,12 +472,13 @@ function nsArrayDiff(oldArray, newArray) { function updatePlugins(sgvs, time) { - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, tooltip); + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, tooltip); var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs , treatments: treatments , profile: profile + , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery }); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -541,24 +543,6 @@ function nsArrayDiff(oldArray, newArray) { updateCurrentSGV(latestSGV); updateClockDisplay(); updateTimeAgo(); - - var battery = devicestatusData && devicestatusData.uploaderBattery; - if (battery) { - $('#uploaderBattery em').text(battery + '%'); - $('#uploaderBattery label') - .toggleClass('icon-battery-100', battery >= 95) - .toggleClass('icon-battery-75', battery < 95 && battery >= 55) - .toggleClass('icon-battery-50', battery < 55 && battery >= 30) - .toggleClass('icon-battery-25', battery < 30); - - $('#uploaderBattery') - .show() - .toggleClass('warn', battery <= 30 && battery > 20) - .toggleClass('urgent', battery <= 20); - } else { - $('#uploaderBattery').hide(); - } - updatePlugins(nowData, nowDate); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 2db06434073..2140b645391 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -14,8 +14,8 @@ describe('cage', function ( ) { }; var pluginBase = { - updatePillText: function mockedUpdatePillText (plugin, updatedText, label, info) { - updatedText.should.equal('24h'); + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('24h'); done(); } }; diff --git a/tests/upbat.test.js b/tests/upbat.test.js new file mode 100644 index 00000000000..9b71d965c21 --- /dev/null +++ b/tests/upbat.test.js @@ -0,0 +1,49 @@ +'use strict'; + +var should = require('should'); + +describe('Uploader Battery', function ( ) { + var data = {uploaderBattery: 20}; + var app = { }; + var clientSettings = {}; + + it('display uploader battery status', function (done) { + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), {}, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('upbat'); + var result = setter(); + result.value.should.equal(20); + result.display.should.equal('20%'); + result.status.should.equal('urgent'); + result.level.should.equal(25); + done() + }; + + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + + }); + + it('set a pill to the uploader battery status', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('20%'); + options.labelClass.should.equal('icon-battery-25'); + options.pillClass.should.equal('urgent'); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + + }); + + + +}); From c9546bb167e8cb011c597e0c6d923f66d64a0c79 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 09:23:36 +0200 Subject: [PATCH 189/661] Restored the init-code. Now run some tests. --- lib/bootevent.js | 72 ++------------------------------------- tests/api.entries.test.js | 2 +- tests/api.status.test.js | 2 +- tests/security.test.js | 2 +- 4 files changed, 5 insertions(+), 73 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index ed43eb51882..91006b1c049 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,68 +1,5 @@ 'use strict'; -function boot(env, booted) { - require('./storage')(env, function ready(err, store) { - -<<<<<<< HEAD - if(err) - console.warn('Could not succesfully connect to MongoDB.') - - console.log('Storage system ready'); - var ctx = { - store: store - }; - - /////////////////////////////////////////////////// - // api and json object variables - /////////////////////////////////////////////////// - ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushnotify = require('./pushnotify')(env, ctx); - ctx.entries = require('./entries')(env, ctx); - ctx.treatments = require('./treatments')(env, ctx); - ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); - ctx.profile = require('./profile')(env.profile_collection, ctx); - ctx.pebble = require('./pebble')(env, ctx); - - console.info("Ensuring indexes"); - store.ensureIndexes(ctx.entries(), ctx.entries.indexedFields); - store.ensureIndexes(ctx.treatments(), ctx.treatments.indexedFields); - store.ensureIndexes(ctx.devicestatus(), ctx.devicestatus.indexedFields); - store.ensureIndexes(ctx.profile(), ctx.profile.indexedFields); - - ctx.bus = require('./bus')(env, ctx); - - ctx.data = require('./data')(env, ctx); - ctx.notifications = require('./notifications')(env, ctx); - - function updateData() { - ctx.data.update(function dataUpdated() { - ctx.bus.emit('data-loaded'); - }); - } - - ctx.bus.on('tick', function timedReloadData(tick) { - console.info('tick', tick.now); - updateData(); - }); - - ctx.bus.on('data-received', function forceReloadData() { - console.info('got data-received event, reloading now'); - updateData(); - }); - - ctx.bus.on('data-loaded', function updatePlugins() { - var sbx = require('./sandbox')().serverInit(env, ctx); - ctx.plugins.setProperties(sbx); - ctx.notifications.initRequests(); - ctx.plugins.checkNotifications(sbx); - ctx.notifications.process(sbx); - ctx.bus.emit('data-processed'); - }); - - ctx.bus.on('notification', ctx.pushnotify.emitNotification); - - ctx.bus.uptime(); -======= function boot (env) { function setupMongo (ctx, next) { @@ -141,19 +78,14 @@ function boot (env) { next( ); } - var proc = bootevent( ) + var proc = require('bootevent')( ) .acquire(setupMongo) .acquire(setupInternals) .acquire(ensureIndexes) .acquire(setupListeners) - .acquire(finishBoot) - ; + .acquire(finishBoot); return proc; ->>>>>>> 8e87e10a8473c50566f929b9ebdd2b378ede2336 - - booted(ctx); - }); } module.exports = boot; diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 5985188f17a..39a7c435bb8 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -12,7 +12,7 @@ describe('Entries REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); self.archive.create(load('json'), done); diff --git a/tests/api.status.test.js b/tests/api.status.test.js index a8f5c1cf58a..435e6d82246 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -12,7 +12,7 @@ describe('Status REST api', function ( ) { this.app = require('express')( ); this.app.enable('api'); var self = this; - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/api', api(env, ctx.entries)); done(); }); diff --git a/tests/security.test.js b/tests/security.test.js index 8154c505c16..d5a5b193763 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -10,7 +10,7 @@ describe('API_SECRET', function ( ) { var scope = this; function setup_app (env, fn) { - require('../lib/bootevent')(env, function booted (ctx) { + require('../lib/bootevent')(env).boot(function booted (ctx) { var wares = require('../lib/middleware/')(env); ctx.app = api(env, wares, ctx); scope.app = ctx.app; From 5bf9ad96b97ada18b3f02c0f732b08b250cfb7fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 02:47:21 -0700 Subject: [PATCH 190/661] converted rawbg/noise into a plugin, refactoring, tests, etc --- lib/plugins/delta.js | 33 ++++++----- lib/plugins/index.js | 14 +++-- lib/plugins/pluginbase.js | 4 +- lib/plugins/rawbg.js | 104 +++++++++++++++++++++++++++++++++ lib/sandbox.js | 2 + static/css/main.css | 12 ++-- static/index.html | 2 +- static/js/client.js | 120 +++++++------------------------------- tests/plugins.test.js | 33 +++++++++++ tests/rawbg.test.js | 35 +++++++++++ 10 files changed, 233 insertions(+), 126 deletions(-) create mode 100644 lib/plugins/rawbg.js create mode 100644 tests/plugins.test.js create mode 100644 tests/rawbg.test.js diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index f5a10c0fe06..2bae298a952 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -12,20 +12,11 @@ function init() { delta.setProperties = function setProperties (sbx) { sbx.offerProperty('delta', function setDelta ( ) { - - var result = { display: null }; - - if (sbx.data.sgvs.length < 2) { return result; } - - var lastSVG = sbx.data.sgvs[sbx.data.sgvs.length - 1].y; - var prevSVG = sbx.data.sgvs[sbx.data.sgvs.length - 2].y; - - if (lastSVG < 40 || prevSVG < 40) { return result; } - - result.value = sbx.scaleBg(lastSVG) - sbx.scaleBg(prevSVG); - result.display = (result.value >= 0 ? '+' : '') + result.value; - - return result; + return delta.calc( + sbx.data.sgvs.length >= 2 ? sbx.data.sgvs[sbx.data.sgvs.length - 2].y : null + , sbx.data.sgvs.length >= 1 ? sbx.data.sgvs[sbx.data.sgvs.length - 1].y : null + , sbx + ); }); }; @@ -37,6 +28,20 @@ function init() { }); }; + delta.calc = function calc(prevSVG, currentSGV, sbx) { + var result = { display: null }; + + if (!prevSVG || !currentSGV) { return result; } + + if (currentSGV < 40 || prevSVG < 40) { return result; } + if (currentSGV > 400 || prevSVG > 400) { return result; } + + result.value = sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG); + result.display = (result.value >= 0 ? '+' : '') + result.value; + + return result; + }; + return delta(); } diff --git a/lib/plugins/index.js b/lib/plugins/index.js index c874db5004b..e6976738594 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -7,14 +7,19 @@ function init() { var allPlugins = [] , enabledPlugins = []; - function plugins() { - return plugins; + function plugins(name) { + if (name) { + return _.find(allPlugins, {name: name}); + } else { + return plugins; + } } plugins.base = require('./pluginbase'); var clientDefaultPlugins = [ - require('./delta')() + require('./rawbg')() + , require('./delta')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() @@ -23,7 +28,8 @@ function init() { ]; var serverDefaultPlugins = [ - require('./ar2')() + require('./rawbg')() + , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() , require('./iob')() diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 55e4599935b..2cac7e6c447 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -2,7 +2,7 @@ var _ = require('lodash'); -function init (majorPills, minorPills, statusPills, tooltip) { +function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { function pluginBase ( ) { return pluginBase; @@ -15,6 +15,8 @@ function init (majorPills, minorPills, statusPills, tooltip) { container = majorPills; } else if (plugin.pluginType == 'pill-status') { container = statusPills; + } else if (plugin.pluginType == 'pill-alt') { + container = bgStatus; } else { container = minorPills } diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js new file mode 100644 index 00000000000..f7017070c88 --- /dev/null +++ b/lib/plugins/rawbg.js @@ -0,0 +1,104 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function rawbg() { + return rawbg; + } + + rawbg.label = 'Raw BG'; + rawbg.pluginType = 'pill-alt'; + rawbg.pillFlip = true; + + rawbg.setProperties = function setProperties (sbx) { + sbx.offerProperty('rawbg', function setRawBG ( ) { + var result = { }; + + var currentSGV = _.last(sbx.data.sgvs); + var currentCal = _.last(sbx.data.cals); + + if (currentSGV && currentCal) { + result.value = rawbg.calc(currentSGV, currentCal, sbx); + result.noiseLabel = rawbg.noiseCodeToDisplay(currentSGV.y, currentSGV.noise); + result.sgv = currentSGV; + result.cal = currentCal; + } + + return result; + }); + }; + + rawbg.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.rawbg; + + var options = rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { + hide: !prop || !prop.value + , value: prop.value + , label: prop.noiseLabel + } : { + hide: true + }; + + sbx.pluginBase.updatePillText(rawbg, options); + }; + + rawbg.calc = function calc(sgv, cal, sbx) { + var raw = 0 + , unfiltered = parseInt(sgv.unfiltered) || 0 + , filtered = parseInt(sgv.filtered) || 0 + , sgv = sgv.y + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; + + + if (slope == 0 || unfiltered == 0 || scale == 0) { + raw = 0; + } else if (filtered == 0 || sgv < 40) { + raw = scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + raw = scale * ( unfiltered - intercept) / slope / ratio; + } + + return sbx.scaleBg(Math.round(raw)); + }; + + rawbg.isEnabled = function isEnabled(sbx) { + return sbx.enable && sbx.enable.indexOf('rawbg') > -1; + }; + + rawbg.showRawBGs = function showRawBGs(sgv, noise, cal, sbx) { + return cal + && rawbg.isEnabled(sbx) + && (sbx.defaults.showRawbg == 'always' + || (sbx.defaults.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + ); + }; + + rawbg.noiseCodeToDisplay = function noiseCodeToDisplay(sgv, noise) { + var display; + switch (parseInt(noise)) { + case 0: display = '---'; break; + case 1: display = 'Clean'; break; + case 2: display = 'Light'; break; + case 3: display = 'Medium'; break; + case 4: display = 'Heavy'; break; + default: + if (sgv < 40) { + display = 'Heavy'; + } else { + display = '~~~'; + } + break; + } + return display; + }; + + return rawbg(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index 74315083cf8..889def13230 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -46,6 +46,7 @@ function init ( ) { sbx.time = Date.now(); sbx.units = env.DISPLAY_UNITS; sbx.defaults = env.defaults; + sbx.enable = env.enable; sbx.thresholds = env.thresholds; sbx.alarm_types = env.alarm_types; sbx.data = ctx.data.clone(); @@ -84,6 +85,7 @@ function init ( ) { sbx.units = clientSettings.units; sbx.defaults = clientSettings; //TODO: strip out extra stuff sbx.thresholds = app.thresholds; + sbx.enable = app.enabledOptions; sbx.alarm_types = clientSettings.alarm_types; sbx.showPlugins = clientSettings.showPlugins; sbx.time = time; diff --git a/static/css/main.css b/static/css/main.css index bb119162bb4..585f0b3390c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -243,24 +243,24 @@ body { width: 360px; } -.loading .bgButton .rawnoise { +.loading .bgButton .pill.rawbg { display: none; } -.bgButton .rawnoise { +.bgButton .pill.rawbg { display: inline-block; border-radius: 2px; border: 1px solid #808080; } -.bgButton .rawnoise em { +.bgButton .pill.rawbg em { color: white; background-color: black; display: block; font-size: 20px; } -.bgButton .rawnoise label { +.bgButton .pill.rawbg label { display: block; font-size: 14px; } @@ -421,11 +421,11 @@ div.tooltip { padding: 0; } - .bgButton .rawnoise em { + .bgButton .pill.rawbg em { font-size: 14px; } - .bgButton .rawnoise label { + .bgButton .pill.rawbg label { font-size: 12px; } diff --git a/static/index.html b/static/index.html index e8c5d0bae0c..9a01e73a811 100644 --- a/static/index.html +++ b/static/index.html @@ -47,7 +47,7 @@

    Nightscout

    - + --- -
    diff --git a/static/js/client.js b/static/js/client.js index 045d92dcddc..b5594841f0e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -57,6 +57,10 @@ function nsArrayDiff(oldArray, newArray) { , alarmSound = 'alarm.mp3' , urgentAlarmSound = 'alarm2.mp3'; + var sbx + , rawbg + , delta; + var jqWindow , tooltip , tickValues @@ -148,7 +152,7 @@ function nsArrayDiff(oldArray, newArray) { if (currentMgdl < 39) { bg_title = s(errorCodeToDisplay(currentMgdl), ' - ') + bg_title; } else { - var deltaDisplay = calcDeltaDisplay(prevSGV, latestSGV); + var deltaDisplay = delta.calc(prevSGV && prevSGV.y, latestSGV && latestSGV.y, sbx).display; bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(decodeEntities(latestSGV.direction)) + bg_title; } } @@ -156,81 +160,6 @@ function nsArrayDiff(oldArray, newArray) { $(document).attr('title', bg_title); } - function calcDeltaDisplay(prevEntry, currentEntry) { - var delta = null - , prevMgdl = prevEntry && prevEntry.y - , currentMgdl = currentEntry && currentEntry.y; - - if (prevMgdl === undefined || currentMgdl == undefined || prevMgdl < 40 || prevMgdl > 400 || currentMgdl < 40 || currentMgdl > 400) { - //TODO consider using raw data here - delta = null; - } else { - delta = scaleBg(currentMgdl) - scaleBg(prevMgdl); - if (browserSettings.units == 'mmol') { - delta = delta.toFixed(1); - } - - delta = (delta >= 0 ? '+' : '') + delta; - } - - return delta; - } - - function isRawBGEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; - } - - function showRawBGs(sgv, noise, cal) { - return cal - && isRawBGEnabled() - && (browserSettings.showRawbg == 'always' - || (browserSettings.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) - ); - } - - function noiseCodeToDisplay(sgv, noise) { - var display; - switch (parseInt(noise)) { - case 0: display = '---'; break; - case 1: display = 'Clean'; break; - case 2: display = 'Light'; break; - case 3: display = 'Medium'; break; - case 4: display = 'Heavy'; break; - default: - if (sgv < 40) { - display = 'Heavy'; - } else { - display = '~~~'; - } - break; - } - - return display; - } - - function rawIsigToRawBg(entry, cal) { - - var raw = 0 - , unfiltered = parseInt(entry.unfiltered) || 0 - , filtered = parseInt(entry.filtered) || 0 - , sgv = entry.y - , scale = parseFloat(cal.scale) || 0 - , intercept = parseFloat(cal.intercept) || 0 - , slope = parseFloat(cal.slope) || 0; - - - if (slope == 0 || unfiltered == 0 || scale == 0) { - raw = 0; - } else if (filtered == 0 || sgv < 40) { - raw = scale * (unfiltered - intercept) / slope; - } else { - var ratio = scale * (filtered - intercept) / slope / sgv; - raw = scale * ( unfiltered - intercept) / slope / ratio; - } - - return Math.round(raw); - } - // initial setup of chart when data is first made available function initializeCharts() { @@ -420,9 +349,6 @@ function nsArrayDiff(oldArray, newArray) { , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') , statusPills = $('.status .statusPills') - , rawNoise = bgButton.find('.rawnoise') - , rawbg = rawNoise.find('em') - , noiseLevel = rawNoise.find('label') , lastEntry = $('#lastEntry'); @@ -453,15 +379,6 @@ function nsArrayDiff(oldArray, newArray) { } } - if (showRawBGs(entry.y, entry.noise, cal)) { - rawNoise.css('display', 'inline-block'); - rawbg.text(scaleBg(rawIsigToRawBg(entry, cal))); - noiseLevel.text(noiseCodeToDisplay(entry.y, entry.noise)); - } else { - rawNoise.hide(); - } - - currentBG.toggleClass('icon-hourglass', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); @@ -472,10 +389,11 @@ function nsArrayDiff(oldArray, newArray) { function updatePlugins(sgvs, time) { - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, tooltip); + var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs + , cals: [cal] , treatments: treatments , profile: profile , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery @@ -524,7 +442,6 @@ function nsArrayDiff(oldArray, newArray) { } else { currentBG.text('---'); currentDirection.text('-'); - rawNoise.hide(); bgButton.removeClass('urgent warning inrange'); } @@ -609,20 +526,20 @@ function nsArrayDiff(oldArray, newArray) { if (d.type != 'sgv' && d.type != 'mbg') return; var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawBG = 0 + , rawbgValue = 0 , noiseLabel = ''; if (d.type == 'sgv') { - if (showRawBGs(d.y, d.noise, cal)) { - rawBG = scaleBg(rawIsigToRawBg(d, cal)); + if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); } - noiseLabel = noiseCodeToDisplay(d.y, d.noise); + noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); } tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('' + bgType + ' BG: ' + d.sgv + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawBG ? '
    Raw BG: ' + rawBG : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + '
    Time: ' + formatTime(d.date)) .style('left', (d3.event.pageX) + 'px') @@ -1644,11 +1561,11 @@ function nsArrayDiff(oldArray, newArray) { var temp1 = [ ]; - if (cal && isRawBGEnabled()) { + if (cal && rawbg.isEnabled(sbx)) { temp1 = SGVdata.map(function (entry) { - var rawBg = showRawBGs(entry.y, entry.noise, cal) ? rawIsigToRawBg(entry, cal) : 0; - if (rawBg > 0) { - return { date: new Date(entry.x - 2000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg' }; + var rawbgValue = rawbg.showRawBGs(entry.y, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; + if (rawbgValue > 0) { + return { date: new Date(entry.x - 2000), y: rawbgValue, sgv: scaleBg(rawbgValue), color: 'white', type: 'rawbg' }; } else { return null; } @@ -1778,6 +1695,9 @@ function nsArrayDiff(oldArray, newArray) { $('#treatmentDrawerToggle').toggle(app.careportalEnabled); Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); + delta = Nightscout.plugins('delta'); + rawbg = Nightscout.plugins('rawbg'); $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); init(); }); diff --git a/tests/plugins.test.js b/tests/plugins.test.js new file mode 100644 index 00000000000..c7fa5f11618 --- /dev/null +++ b/tests/plugins.test.js @@ -0,0 +1,33 @@ +'use strict'; + +var should = require('should'); + +describe('Plugins', function ( ) { + + + it('should find client plugins, but not server only plugins', function (done) { + var plugins = require('../lib/plugins/')().registerClientDefaults(); + + plugins('delta').name.should.equal('delta'); + plugins('rawbg').name.should.equal('rawbg'); + + //server only plugin + should.not.exist(plugins('treatmentnotify')); + + done( ); + }); + + it('should find sever plugins, but not client only plugins', function (done) { + var plugins = require('../lib/plugins/')().registerServerDefaults() + + plugins('rawbg').name.should.equal('rawbg'); + plugins('treatmentnotify').name.should.equal('treatmentnotify'); + + //client only plugin + should.not.exist(plugins('cannulaage')); + + done( ); + }); + + +}); diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js new file mode 100644 index 00000000000..7695c01c076 --- /dev/null +++ b/tests/rawbg.test.js @@ -0,0 +1,35 @@ +'use strict'; + +var should = require('should'); + +describe('Raw BG', function ( ) { + var rawbg = require('../lib/plugins/rawbg')(); + var sandbox = require('../lib/sandbox')(); + + var pluginBase = {}; + var data = { + sgvs: [{unfiltered: 113680, filtered: 111232, y: 110, noise: 1}] + , cals: [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}] + }; + var app = { }; + var clientSettings = { + units: 'mg/dl' + }; + + it('should calculate Raw BG', function (done) { + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + + sbx.offerProperty = function mockedOfferProperty (name, setter) { + name.should.equal('rawbg'); + var result = setter(); + result.value.should.equal(113); + result.noiseLabel.should.equal('Clean'); + done() + }; + + rawbg.setProperties(sbx); + + }); + + +}); From 00b5c50f98cceea0dd3f9d93c629ff8a56a1adaa Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 14:27:31 +0200 Subject: [PATCH 191/661] Small change to bootevent --- lib/bootevent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 91006b1c049..bc5bc728d48 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -5,7 +5,7 @@ function boot (env) { function setupMongo (ctx, next) { var store = require('./storage')(env); // initialize db connections - store( function ready ( ) { + store( function ready ( err, store ) { console.log('storage system ready'); ctx.store = store; From 03a98c1c9df4e82ee506eb7e2f427773a623055e Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 21 Jun 2015 15:56:27 +0200 Subject: [PATCH 192/661] I thought I had rewritten this to the old situation, but somehow it was lost. --- lib/bootevent.js | 4 +--- server.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index bc5bc728d48..6c42e81d4a5 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -78,14 +78,12 @@ function boot (env) { next( ); } - var proc = require('bootevent')( ) + return require('bootevent')( ) .acquire(setupMongo) .acquire(setupInternals) .acquire(ensureIndexes) .acquire(setupListeners) .acquire(finishBoot); - - return proc; } module.exports = boot; diff --git a/server.js b/server.js index bb3c1cc61c8..2621cf10b81 100644 --- a/server.js +++ b/server.js @@ -43,7 +43,7 @@ function create (app) { return transport.createServer(app); } -require('./lib/bootevent')(env, function booted (ctx) { +require('./lib/bootevent')(env).boot(function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); From ee872499d1795dfff63b4c6b9f4ed07c8d32882c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 10:44:02 -0700 Subject: [PATCH 193/661] update readme for the rawbg, delta, and upbat plugins; some other clean up; and another env hack --- README.md | 13 +++---------- env.js | 5 ++++- static/js/client.js | 38 +++++++++++++------------------------- 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index ba3933ec3f3..10ee92a7bc5 100644 --- a/README.md +++ b/README.md @@ -143,27 +143,20 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. **Built-in/Example Plugins:** + * `rawbg` (Raw BG) - Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). - * `cob` (Carbs-on-Board) - Adds the COB pill visualization in the client and calculates values that used by other plugins. Uses treatments with carb doses and the `carbs_hr`, `carbratio`, and `sens` fields from the [treatment profile](#treatment-profile). - * `bwp` (Bolus Wizard Preview) - This plugin in intended for the purpose of automatically snoozing alarms when the CGM indicates high blood sugar but there is also insulin on board (IOB) and secondly, alerting to user that it might be beneficial to measure the blood sugar using a glucometer and dosing insulin as calculated by the pump or instructed by trained medicare professionals. ***The values provided by the plugin are provided as a reference based on CGM data and insulin sensitivity you have configured, and are not intended to be used as a reference for bolus calculation.*** The plugin calculates the bolus amount when above your target, generates alarms when you should consider checking and bolusing, and snoozes alarms when there is enough IOB to cover a high BG. Uses the results of the `iob` plugin and `sens`, `target_high`, and `target_low` fields from the [treatment profile](#treatment-profile). Defaults that can be adjusted with [extended setting](#extended-settings) * `BWP_WARN` (`0.50`) - If `BWP` is > `BWP_WARN` a warning alarm will be triggered. - * `BWP_URGENT` (`1.00`) - If `BWP` is > `BWP_URGENT` an urgent alarm will be triggered. - * `BWP_SNOOZE_MINS` (`10`) - minutes to snooze when there is enough IOB to cover a high BG. - * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. - * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. - + * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** + * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** - * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. - * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** - * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). #### Extended Settings diff --git a/env.js b/env.js index 6321bbee4fa..f879572221f 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,10 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins != '') { - env.defaults.showPlugins += ' delta upbat' + env.defaults.showPlugins += ' delta upbat'; + if (env.defaults.showRawbg == 'always' || env.defaults.showRawbg == 'noise') { + env.defaults.showPlugins += 'rawbg'; + } } //console.log(JSON.stringify(env.defaults)); diff --git a/static/js/client.js b/static/js/client.js index b5594841f0e..17cdec5c5f3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,18 +1,6 @@ //TODO: clean up var app = {}, browserSettings = {}, browserStorage = $.localStorage; -var timeAgo = Nightscout.utils.timeAgo; - -function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; -} - (function () { 'use strict'; @@ -58,8 +46,9 @@ function nsArrayDiff(oldArray, newArray) { , urgentAlarmSound = 'alarm2.mp3'; var sbx - , rawbg - , delta; + , rawbg = Nightscout.plugins('rawbg') + , delta = Nightscout.plugins('delta') + , timeAgo = Nightscout.utils.timeAgo; var jqWindow , tooltip @@ -1510,16 +1499,18 @@ function nsArrayDiff(oldArray, newArray) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket = io.connect(); - function recordAlreadyStored(array,record) { - var l = array.length -1; - for (var i = 0; i <= l; i++) { - var oldRecord = array[i]; - if (record.x == oldRecord.x) return true; - } - } - function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].x] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].x)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + // If there was no delta data, just return the original data if (!receivedDataArray) return cachedDataArray; @@ -1559,7 +1550,6 @@ function nsArrayDiff(oldArray, newArray) { prevSGV = SGVdata[SGVdata.length - 2]; } - var temp1 = [ ]; if (cal && rawbg.isEnabled(sbx)) { temp1 = SGVdata.map(function (entry) { @@ -1696,8 +1686,6 @@ function nsArrayDiff(oldArray, newArray) { Nightscout.plugins.init(app); browserSettings = getBrowserSettings(browserStorage); sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); - delta = Nightscout.plugins('delta'); - rawbg = Nightscout.plugins('rawbg'); $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); init(); }); From 4d6cc814a52ba1d1578b9d07e833fa2bbb6366a1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 16:08:04 -0700 Subject: [PATCH 194/661] add initial style guide and dev tips to CONTRIBUTING.md --- CONTRIBUTING.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c63417fa918..965a258a450 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,23 @@ design. We develop on the `dev` branch. You can get the dev branch checked out using `git checkout dev`. +## Style Guide + +Some simple rules, that will make it easier to maintain our codebase: + +* All indenting should use 2 space where possible (js, css, html, etc) +* A space before function parameters, such as: `function boom (name, callback) { }`, this makes searching for calls easier +* Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }` +* Don't include author names in the header of your files, if you need to give credit to someone else do it in the commit comment. +* Use the comma first style, for example: + javascript``` + var data = { + value: 'the value' + , detail: 'the details...' + , time: Date.now() + }; + ``` + ## Create a prototype Fork cgm-remote-monitor and create a branch. @@ -77,3 +94,13 @@ the version correctly. See sem-ver for versioning strategy. Every commit is tested by travis. We encourage adding tests to validate your design. We encourage discussing your use cases to help everyone get a better understanding of your design. + +## Other Dev Tips + +* Join the [Gitter chat][gitter-url] +* Get a local dev environment setup if you haven't already +* Try breaking up big features/improvements into small parts. It's much easier to accept small PR's +* Create tests for your new code, and for the old code too. We are aiming for a full test coverage. +* If your going to be working in old code that needs lots of reformatting consider doing the clean as a separate PR. +* If you can find others to help test your PR is will help get them merged in sooner. + From 40f99702061a79deee68c0ff9670da3631c78dc5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 21 Jun 2015 16:14:25 -0700 Subject: [PATCH 195/661] Update CONTRIBUTING.md corrected markdown --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 965a258a450..1dd0ba4d427 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,8 @@ Some simple rules, that will make it easier to maintain our codebase: * Name your callback functions, such as `boom('the name', function afterBoom ( result ) { }` * Don't include author names in the header of your files, if you need to give credit to someone else do it in the commit comment. * Use the comma first style, for example: - javascript``` + + ```javascript var data = { value: 'the value' , detail: 'the details...' From 8323758685ca55711aadd9df993d100059a45e53 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 22 Jun 2015 09:23:48 +0200 Subject: [PATCH 196/661] Eagle eye @jasoncalabrese, it is fixed now --- lib/bootevent.js | 7 +++---- lib/storage.js | 13 +++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/bootevent.js b/lib/bootevent.js index 6c42e81d4a5..ffe90969661 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -3,10 +3,9 @@ function boot (env) { function setupMongo (ctx, next) { - var store = require('./storage')(env); - // initialize db connections - store( function ready ( err, store ) { - console.log('storage system ready'); + require('./storage')(env, function ready ( err, store ) { + // FIXME, error is always null, if there is an error, the storage.js will throw an exception + console.log('Storage system ready'); ctx.store = store; next( ); diff --git a/lib/storage.js b/lib/storage.js index 2d600dd2801..f6567ab12fd 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -6,26 +6,34 @@ function init (env, cb) { var my = { }; function maybe_connect (cb) { + if (my.db) { console.log("Reusing MongoDB connection handler"); + // If there is a valid callback, then return the Mongo-object if (cb && cb.call) { cb(null, mongo); } return; } + if (!env.mongo) { throw new Error("MongoDB connection string is missing"); } + console.log("Setting up new connection to MongoDB"); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { console.log("Error connecting to MongoDB, ERROR: %j", err); throw err; } else { - console.log("Successfully established a connected to mongo"); + console.log("Successfully established a connected to MongoDB"); } + + // FIXME Fokko: I would suggest to just create a private db variable instead of the separate my, pool construction. my.db = db; mongo.pool.db = my.db = db; - if (cb && cb.call) { cb(err, mongo); } + // If there is a valid callback, then invoke the function to perform the callback + if (cb && cb.call) + cb(err, mongo); }); } @@ -55,6 +63,7 @@ function init (env, cb) { } return this; }; + mongo.ensureIndexes = function(collection, fields) { fields.forEach(function (field) { console.info("ensuring index for: " + field); From 2fde2052ca80657dc95a38993fe70bad73a5c36f Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Mon, 22 Jun 2015 19:40:23 +0300 Subject: [PATCH 197/661] Small fix to re-enable the ar2 LOW predictions --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 15ad1a20c3b..ab8f64c0bad 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -53,7 +53,7 @@ function init() { var avg = _.sum(predicted) / predicted.length; var max = _.max([first, last, avg]); - var min = _.max([first, last, avg]); + var min = _.min([first, last, avg]); var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { From 1399b9fe3cf8166956fbdc4081d0d4ca592203c4 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 22 Jun 2015 21:17:07 +0200 Subject: [PATCH 198/661] Integrating docker support --- Dockerfile | 10 ++++++++++ app.js | 2 -- docker/docker-build.sh | 8 ++++++++ docker/docker-compose.yml | 20 ++++++++++++++++++++ env.js | 1 - setup.sh | 2 ++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Dockerfile create mode 100755 docker/docker-build.sh create mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..a1b8ded65d3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM node:0.10.38-slim + +MAINTAINER Nightscout + +# Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running +RUN apt-get update && apt-get -y install netcat +RUN npm install . + +EXPOSE 1337 +CMD ["sh", "docker/docker-start.sh"] \ No newline at end of file diff --git a/app.js b/app.js index c15b5e1a6df..a1fffb49e2f 100644 --- a/app.js +++ b/app.js @@ -41,8 +41,6 @@ function create (env, ctx) { var bundle = require('./bundle')(); app.use(bundle); -// Handle errors with express's errorhandler, to display more readable error messages. - // Handle errors with express's errorhandler, to display more readable error messages. var errorhandler = require('errorhandler'); //if (process.env.NODE_ENV === 'development') { diff --git a/docker/docker-build.sh b/docker/docker-build.sh new file mode 100755 index 00000000000..216e90bf504 --- /dev/null +++ b/docker/docker-build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd ../ + +docker-compose -p nightscout_build -f docker/docker-compose.yml stop +docker-compose -p nightscout_build -f docker/docker-compose.yml rm --force -v +docker-compose -p nightscout_build -f docker/docker-compose.yml build +docker-compose -p nightscout_build -f docker/docker-compose.yml up \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000000..509d285ba8c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,20 @@ +nightscout: + build: ../ + links: + - database + - broker + ports: + - "1337:1337" + environment: + - MONGO_CONNECTION=mongodb://database/nightscout + - API_SECRET=mylittlesecret + - MQTT_MONITOR=mqtt://broker + - PORT=1337 +database: + image: mongo:3.0.3 + ports: + - "27017" +broker: + image: prologic/mosquitto + ports: + - "1883" \ No newline at end of file diff --git a/env.js b/env.js index 86d6e8424e4..827b5bdf2fd 100644 --- a/env.js +++ b/env.js @@ -7,7 +7,6 @@ var consts = require('./lib/constants'); var fs = require('fs'); // Module to constrain all config and environment parsing to one spot. function config ( ) { - /* * First inspect a bunch of environment variables: * * PORT - serve http on this port diff --git a/setup.sh b/setup.sh index 62a9a5f186c..e520a97a025 100755 --- a/setup.sh +++ b/setup.sh @@ -1,3 +1,5 @@ +#!/bin/sh + sudo apt-get update sudo apt-get install -y python-software-properties python g++ make git sudo add-apt-repository ppa:chris-lea/node.js From 49b4d2600b71a95a4696b8892a124b223985aea2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:05:48 +0300 Subject: [PATCH 199/661] New profile object, which support the old data format and new time of day based values for all contained data structures, including basal. Converted existing profile code to use the profile object. Added basal plugin, which shows current basal rate and other profile information. Added temporary basal adjustment calculation to BWP, if basal profile is present. Fixed a significant bug in how COB called IOB (passed a date to IOB instead of profile). Test for profile code. --- lib/plugins/basalprofile.js | 52 ++++++++ lib/plugins/boluswizardpreview.js | 53 +++++++-- lib/plugins/cob.js | 24 ++-- lib/plugins/index.js | 1 + lib/plugins/iob.js | 17 +-- lib/profilefunctions.js | 99 ++++++++++------ lib/sandbox.js | 4 +- static/js/client.js | 7 +- tests/profile.test.js | 189 ++++++++++++++++++++++++++++++ 9 files changed, 377 insertions(+), 69 deletions(-) create mode 100644 lib/plugins/basalprofile.js create mode 100644 tests/profile.test.js diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js new file mode 100644 index 00000000000..5c01f078ead --- /dev/null +++ b/lib/plugins/basalprofile.js @@ -0,0 +1,52 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function basal() { + return basal; + } + + basal.label = 'Basal Profile'; + basal.pluginType = 'pill-minor'; + + function hasRequiredInfo (sbx) { + + if (!sbx.data.profile) return false; + + if (!sbx.data.profile.hasData()) { + console.warn('For the Basal plugin to function you need a treatment profile'); + return false; + } + + if (!sbx.data.profile.getBasal()) { + console.warn('For the Basal plugin to function you need a basal profile'); + return false; + } + + return true; + } + + basal.updateVisualisation = function updateVisualisation (sbx) { + + if (!hasRequiredInfo(sbx)) { + return; + } + + var basalValue = sbx.data.profile.getBasal(sbx.time); + + var info = [{label: 'Current basal:', value: basalValue + ' IU'} + , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ IU'} + , {label: 'Current carb ratio:', value: '1 IU /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} + ]; + + sbx.pluginBase.updatePillText(basal, basalValue + 'U', 'BASAL', info); + + }; + + return basal(); +} + + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a519713c584..cc66c052b05 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -16,12 +16,15 @@ function init() { bwp.pluginType = 'pill-minor'; function hasRequiredInfo (sbx) { - if (!sbx.data.profile) { + + if (!sbx.data.profile) return false; + + if (!sbx.data.profile.hasData()) { console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); return false; } - if (!sbx.data.profile.sens || !sbx.data.profile.target_high || !sbx.data.profile.target_low) { + if (!sbx.data.profile.getSensitivity(sbx.time) || !sbx.data.profile.getHighBGTarget(sbx.time) || !sbx.data.profile.getLowBGTarget(sbx.time)) { console.warn('For the BolusWizardPreview plugin to function your treatment profile must have both sens, target_high, and target_low fields'); return false; } @@ -50,7 +53,7 @@ function init() { var results = bwp.calc(sbx); - if (results.lastSGV < sbx.data.profile.target_high) return; + if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; @@ -100,6 +103,19 @@ function init() { , {label: 'Expected effect', value: '-' + results.effectDisplay + ' ' + sbx.units} , {label: 'Expected outcome', value: results.outcomeDisplay + ' ' + sbx.units} ]; + + if (results.tempBasalAdjustment) { + if (results.tempBasalAdjustment.thirtymin > 0) { + info.push( {label: '30m temp basal', value: results.tempBasalAdjustment.thirtymin + '%'}); + } else { + info.push( {label: '30m temp basal', value: 'too large adjustment needed, give carbs?'}); + } + if (results.tempBasalAdjustment.onehour > 0) { + info.push( {label: '1h temp basal', value: results.tempBasalAdjustment.onehour + '%'}); + } else { + info.push( {label: '1h temp basal', value: 'too large adjustment needed, give carbs?'}); + } + } sbx.pluginBase.updatePillText(bwp, results.bolusEstimateDisplay + 'U', 'BWP', info); @@ -124,18 +140,35 @@ function init() { var profile = sbx.data.profile; var iob = results.iob = sbx.properties.iob.iob; - results.effect = iob * profile.sens; + results.effect = iob * profile.getSensitivity(sbx.time); results.outcome = sgv - results.effect; var delta = 0; + + var target_high = profile.getHighBGTarget(sbx.time); + var sens = profile.getSensitivity(sbx.time); - if (results.outcome > profile.target_high) { - delta = results.outcome - profile.target_high; - results.bolusEstimate = delta / profile.sens; + if (results.outcome > target_high) { + delta = results.outcome - target_high; + results.bolusEstimate = delta / sens; } - if (results.outcome < profile.target_low) { - delta = Math.abs(results.outcome - profile.target_low); - results.bolusEstimate = delta / profile.sens * -1; + var target_low = profile.getLowBGTarget(sbx.time); + + if (results.outcome < target_low) { + delta = Math.abs(results.outcome - target_low); + results.bolusEstimate = delta / sens * -1; + } + + if (results.bolusEstimate != 0 && sbx.data.profile.getBasal()) { + // Basal profile exists, calculate % change + var basal = sbx.data.profile.getBasal(sbx.time); + + var thirtyMinAdjustment = Math.round((basal/2 + results.bolusEstimate) / (basal / 2) * 100); + var oneHourAdjustment = Math.round((basal + results.bolusEstimate) / basal * 100); + + results.tempBasalAdjustment = { + 'thirtymin': thirtyMinAdjustment + ,'onehour': oneHourAdjustment}; } results.bolusEstimateDisplay = sbx.roundInsulinForDisplayFormat(results.bolusEstimate); diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 7ce5e71619e..ec4702fb4a0 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -22,19 +22,17 @@ function init() { cob.cobTotal = function cobTotal(treatments, profile, time) { - if (!profile) { + if (!profile || !profile.hasData()) { console.warn('For the COB plugin to function you need a treatment profile'); return {}; } - if (!profile.sens || !profile.carbratio) { + if (!profile.getSensitivity(time) || !profile.getCarbRatio(time)) { console.warn('For the CPB plugin to function your treatment profile must have both sens and carbratio fields'); return {}; } var liverSensRatio = 1; - var sens = profile.sens; - var carbratio = profile.carbratio; var totalCOB = 0; var lastCarbs = null; if (!treatments) return {}; @@ -44,7 +42,6 @@ function init() { var isDecaying = 0; var lastDecayedBy = new Date('1/1/1970'); - var carbs_hr = profile.carbs_hr; _.forEach(treatments, function eachTreatment(treatment) { if (treatment.carbs && treatment.created_at < time) { @@ -52,11 +49,11 @@ function init() { var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { - var actStart = iob.calcTotal(treatments, lastDecayedBy).activity; - var actEnd = iob.calcTotal(treatments, cCalc.decayedBy).activity; + var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; + var actEnd = iob.calcTotal(treatments, profile, cCalc.decayedBy).activity; var avgActivity = (actStart + actEnd) / 2; - var delayedCarbs = avgActivity * liverSensRatio * sens / carbratio; - var delayMinutes = Math.round(delayedCarbs / carbs_hr * 60); + var delayedCarbs = avgActivity * liverSensRatio * profile.getSensitivity(treatment.created_at) / profile.getCarbRatio(treatment.created_at); + var delayMinutes = Math.round(delayedCarbs / profile.getCarbAbsorptionRate(treatment.created_at) * 60); if (delayMinutes > 0) { cCalc.decayedBy.setMinutes(cCalc.decayedBy.getMinutes() + delayMinutes); decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; @@ -79,12 +76,12 @@ function init() { } }); - var rawCarbImpact = isDecaying * sens / carbratio * carbs_hr / 60; + var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; return { decayedBy: lastDecayedBy, isDecaying: isDecaying, - carbs_hr: carbs_hr, + carbs_hr: profile.getCarbAbsorptionRate(time), rawCarbImpact: rawCarbImpact, cob: totalCOB, lastCarbs: lastCarbs @@ -106,14 +103,15 @@ function init() { cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { - var carbs_hr = profile.carbs_hr; var delay = 20; - var carbs_min = carbs_hr / 60; var isDecaying = 0; var initialCarbs; if (treatment.carbs) { var carbTime = new Date(treatment.created_at); + + var carbs_hr = profile.getCarbAbsorptionRate(treatment.created_at); + var carbs_min = carbs_hr / 60; var decayedBy = new Date(carbTime); var minutesleft = (lastDecayedBy - carbTime) / 1000 / 60; diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 5a7c3ce27c9..9479f270473 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -18,6 +18,7 @@ function init() { , require('./cob')() , require('./boluswizardpreview')() , require('./cannulaage')() + , require('./basalprofile')() ]; var serverDefaultPlugins = [ diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 806107f61c2..7eeb8dc66f4 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -26,11 +26,6 @@ function init() { if (!treatments) return {}; - if (profile === undefined) { - //if there is no profile default to 3 hour dia - profile = {dia: 3, sens: 0}; - } - if (time === undefined) { time = new Date(); } @@ -58,10 +53,16 @@ function init() { iob.calcTreatment = function calcTreatment(treatment, profile, time) { - var dia = profile.dia - , scaleFactor = 3.0 / dia + var dia = 3 + , sens = 0; + + if (profile !== undefined) { + dia = profile.getDIA(); + sens = profile.getSensitivity(time); + } + + var scaleFactor = 3.0 / dia , peak = 75 - , sens = profile.sens , result = { iobContrib: 0 , activityContrib: 0 diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index ff684472b7f..024f617e937 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -1,34 +1,49 @@ 'use strict'; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // multiple profile support for predictions - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +var _ = require('lodash'); +var moment = require('moment'); - function timeStringToSeconds(time) { +function init(profileData) { + + function profile() { + return profile; + } + + profile.loadData = function loadData(profileData) { + profile.data = _.cloneDeep(profileData); + profile.preprocessProfileOnLoad(profile.data[0]); + } + + profile.timeStringToSeconds = function timeStringToSeconds(time) { var split = time.split(":"); return parseInt(split[0])*3600 + parseInt(split[1])*60; } // preprocess the timestamps to seconds for a couple orders of magnitude faster operation - function preprocessProfileOnLoad(container) + profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { for (var key in container) { var value = container[key]; + if( Object.prototype.toString.call(value) === '[object Array]' ) { - preprocessProfileOnLoad(value); - } else { - if (value.time) { - var sec = timeStringToSeconds(value.time); - if (!isNaN(sec)) value.timeAsSeconds = sec; - } + profile.preprocessProfileOnLoad(value); + } + + if (value.time) { + var sec = profile.timeStringToSeconds(value.time); + if (!isNaN(sec)) value.timeAsSeconds = sec; } + } container.timestampsPreProcessed = true; } + + if (profileData) profile.loadData(profileData); - - function getValueByTime(profile, time, valueContainer) + profile.getValueByTime = function getValueByTime(time, valueContainer) { + if (!time) time = new Date(); + // If the container is an Array, assume it's a valid timestamped value container var returnValue = valueContainer; @@ -37,7 +52,7 @@ var timeAsDate = new Date(time); var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; - + for (var t in valueContainer) { var value = valueContainer[t]; if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { @@ -49,33 +64,47 @@ return returnValue; } - function getDIA(profile, time) - { - return getValueByTime(profile, time,profile.dia); + profile.getCurrentProfile = function getCurrentProfile() { + if (profile.hasData()) return profile.data[0]; + return {}; } - function getSensitivity(profile, time) - { - return getValueByTime(profile, time,profile.sens); - } + profile.hasData = function hasData() { + var rVal = false; + if (profile.data) rVal = true; + return (rVal); + } - function getCarbRatio(profile, time) - { - return getValueByTime(profile, time,profile.carbratio); - } - - function getCarbAbsorptionRate(profile, time) - { - return getValueByTime(profile, time,profile.carbs_hr); - } + profile.getDIA = function getDIA(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['dia']); + } + profile.getSensitivity = function getSensitivity(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['sens']); + } -function Profile(opts) { + profile.getCarbRatio = function getCarbRatio(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['carbratio']); + } + + profile.getCarbAbsorptionRate = function getCarbAbsorptionRate(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['carbs_hr']); + } + + profile.getLowBGTarget = function getLowBGTarget(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['target_low']); + } - return { - preprocessProfileOnLoad: preprocessProfileOnLoad - }; + profile.getHighBGTarget = function getHighBGTarget(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['target_high']); + } + profile.getBasal = function getBasal(time) { + return profile.getValueByTime(time,profile.getCurrentProfile()['basal']); + } + + + return profile(); } -module.exports = Profile; \ No newline at end of file +module.exports = init; \ No newline at end of file diff --git a/lib/sandbox.js b/lib/sandbox.js index 74315083cf8..aa9a6814ce2 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var units = require('./units')(); var utils = require('./utils'); +var profile = require('./profilefunctions')(); function init ( ) { var sbx = {}; @@ -54,7 +55,8 @@ function init ( ) { sbx.notifications = _.pick(ctx.notifications, ['levels', 'requestNotify', 'requestSnooze', 'requestClear']); //Plugins will expect the right profile based on time - sbx.data.profile = _.first(ctx.data.profiles); + profile.loadData(ctx.data.profiles); + sbx.data.profile = profile; delete sbx.data.profiles; sbx.properties = []; diff --git a/static/js/client.js b/static/js/client.js index 4c0f0bc1c38..be71f6aeaad 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -500,7 +500,7 @@ function nsArrayDiff(oldArray, newArray) { var sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { sgvs: sgvs , treatments: treatments - , profile: profile + , profile: Nightscout.profile }); //all enabled plugins get a chance to set properties, even if they aren't shown @@ -1675,7 +1675,10 @@ function nsArrayDiff(oldArray, newArray) { SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); treatments = mergeDataUpdate(d.delta,treatments, d.treatments); - if (d.profiles) profile = d.profiles[0]; + if (d.profiles) { + profile = d.profiles[0]; + Nightscout.profile.loadData(d.profiles); + } if (d.cals) cal = d.cals[d.cals.length-1]; if (d.devicestatus) devicestatusData = d.devicestatus; diff --git a/tests/profile.test.js b/tests/profile.test.js new file mode 100644 index 00000000000..b49bd5cae33 --- /dev/null +++ b/tests/profile.test.js @@ -0,0 +1,189 @@ +var should = require('should'); + +describe('Profile', function ( ) { + + var env = require('../env')(); + + var profile_empty = require('../lib/profilefunctions')(); + + it('should say it does not have data before it has data', function() { + var hasData = profile_empty.hasData(); + hasData.should.equal(false); + }); + + it('should return undefined if asking for keys before init', function() { + var dia = profile_empty.getDIA(now); + should.not.exist(dia); + }); + + var profileDataPartial = { + "dia": 3, + "carbs_hr": 30, + }; + + var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); + + it('should return undefined if asking for missing keys', function() { + var sens = profile_empty.getSensitivity(now); + should.not.exist(sens); + }); + + var profileData = { + "dia": 3, + "carbs_hr": 30, + "carbratio": 7, + "sens": 35, + "target_low": 95, + "target_high": 120 + }; + + var profile = require('../lib/profilefunctions')([profileData]); +// console.log(profile); + + var now = new Date(); + + it('should know what the DIA is with old style profiles', function() { + var dia = profile.getDIA(now); + dia.should.equal(3); + }); + + it('should know what the DIA is with old style profiles, with missing date argument', function() { + var dia = profile.getDIA(); + dia.should.equal(3); + }); + + it('should know what the carbs_hr is with old style profiles', function() { + var carbs_hr = profile.getCarbAbsorptionRate(now); + carbs_hr.should.equal(30); + }); + + it('should know what the carbratio is with old style profiles', function() { + var carbRatio = profile.getCarbRatio(now); + carbRatio.should.equal(7); + }); + + it('should know what the sensitivity is with old style profiles', function() { + var dia = profile.getSensitivity(now); + dia.should.equal(35); + }); + + it('should know what the low target is with old style profiles', function() { + var dia = profile.getLowBGTarget(now); + dia.should.equal(95); + }); + + it('should know what the high target is with old style profiles', function() { + var dia = profile.getHighBGTarget(now); + dia.should.equal(120); + }); + + it('should know how to reload data and still know what the low target is with old style profiles', function() { + + var profileData2 = { + "dia": 3, + "carbs_hr": 30, + "carbratio": 7, + "sens": 35, + "target_low": 50, + "target_high": 120 + }; + + profile.loadData([profileData2]); + var dia = profile.getLowBGTarget(now); + dia.should.equal(50); + }); + + var complexProfileData = + { + "sens": [ + { + "time": "00:00", + "value": 10 + }, + { + "time": "02:00", + "value": 10 + }, + { + "time": "07:00", + "value": 9 + } + ], + "dia": 3, + "carbratio": [ + { + "time": "00:00", + "value": 16 + }, + { + "time": "06:00", + "value": 15 + }, + { + "time": "14:00", + "value": 16 + } + ], + "carbs_hr": 30, + "startDate": "2015-06-21", + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.1 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.3 + }, + { + "time": "22:00", + "value": 0.225 + } + ], + "target_low": 4.5, + "target_high": 8 +}; + + var complexProfile = require('../lib/profilefunctions')([complexProfileData]); + + var noon = new Date('2015-06-22 12:00:00'); + var threepm = new Date('2015-06-22 15:00:00'); + + it('should know what the basal rate is at 12:00 with complex style profiles', function() { + var value = complexProfile.getBasal(noon); + value.should.equal(0.1); + }); + + it('should know what the basal rate is at 15:00 with complex style profiles', function() { + var value = complexProfile.getBasal(threepm); + value.should.equal(0.125); + }); + + it('should know what the carbratio is at 12:00 with complex style profiles', function() { + var carbRatio = complexProfile.getCarbRatio(noon); + carbRatio.should.equal(15); + }); + + it('should know what the sensitivity is at 12:00 with complex style profiles', function() { + var dia = complexProfile.getSensitivity(noon); + dia.should.equal(9); + }); + + +}); \ No newline at end of file From d3c548ebf4ce8a794e4a8e4655fa50f3fac445d2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:14:23 +0300 Subject: [PATCH 200/661] Removed setting timestampsPreProcessed --- lib/profilefunctions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 024f617e937..8167bb309e3 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -35,7 +35,6 @@ function init(profileData) { } } - container.timestampsPreProcessed = true; } if (profileData) profile.loadData(profileData); From 6b92df90c57fa4602f6e971802a3910fc6198eb7 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 00:36:23 +0300 Subject: [PATCH 201/661] Fixed IOB tests and a bug in the COB code from my changes. The COB test is still broken, probably as a result of the IOB call change I did, where COB was passing a date instead of profile, which must have caused miscalculation in IOB code. --- lib/plugins/cob.js | 2 +- tests/cob.test.js | 6 ++++-- tests/iob.test.js | 31 +++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index ec4702fb4a0..1640b3be935 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -66,7 +66,7 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * carbs_hr); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbRatio(treatment.created_at)); //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); isDecaying = cCalc.isDecaying; } else { diff --git a/tests/cob.test.js b/tests/cob.test.js index db2cc9fbef3..64eb510a331 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -4,13 +4,15 @@ var should = require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); - - var profile = { + + var profileData = { sens: 95 , carbratio: 18 , carbs_hr: 30 }; + var profile = require('../lib/profilefunctions')([profileData]); + it('should calculate IOB, multiple treatments', function() { var treatments = [ diff --git a/tests/iob.test.js b/tests/iob.test.js index 042141dd7cc..466fe87923c 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -5,6 +5,7 @@ var FIVE_MINS = 10 * 60 * 1000; describe('IOB', function ( ) { var iob = require('../lib/plugins/iob')(); + it('should calculate IOB', function() { var time = new Date() @@ -12,11 +13,14 @@ describe('IOB', function ( ) { created_at: time - 1, insulin: "1.00" } - ] - , profile = { - dia: 3, - sens: 0 - }; + ]; + + + var profileData = { + dia: 3, + sens: 0}; + + var profile = require('../lib/profilefunctions')([profileData]); var rightAfterBolus = iob.calcTotal(treatments, profile, time); @@ -69,12 +73,15 @@ describe('IOB', function ( ) { created_at: time - 1, insulin: "1.00" } - ] - , profile = { - dia: 4, - sens: 0 - }; + ]; + + var profileData = { + dia: 4, + sens: 0}; + + var profile = require('../lib/profilefunctions')([profileData]); + var rightAfterBolus = iob.calcTotal(treatments, profile, time); rightAfterBolus.display.should.equal('1.00'); @@ -88,9 +95,9 @@ describe('IOB', function ( ) { after3hDIA.iob.should.greaterThan(0); - var after4hDIA = iob.calcTotal(treatments, profile, new Date(time.getTime() + (3 * 60 * 60 * 1000))); + var after4hDIA = iob.calcTotal(treatments, profile, new Date(time.getTime() + (4 * 60 * 60 * 1000))); - after4hDIA.iob.should.greaterThan(0); + after4hDIA.iob.should.equal(0); }); From 32969ec345a6a45631019d43aa76dd35944340df Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 07:53:43 +0300 Subject: [PATCH 202/661] Found the bug introduced in changing the profile API --- lib/plugins/cob.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1640b3be935..4a3c0a3ad01 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -47,6 +47,7 @@ function init() { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); + console.log(cCalc); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; @@ -66,8 +67,8 @@ function init() { if (decaysin_hr > 0) { //console.info('Adding ' + delayMinutes + ' minutes to decay of ' + treatment.carbs + 'g bolus at ' + treatment.created_at); - totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbRatio(treatment.created_at)); - //console.log("cob: " + Math.min(cCalc.initialCarbs, decaysin_hr * carbs_hr)); + totalCOB += Math.min(Number(treatment.carbs), decaysin_hr * profile.getCarbAbsorptionRate(treatment.created_at)); + //console.log("cob:", Math.min(cCalc.initialCarbs, decaysin_hr * profile.getCarbAbsorptionRate(treatment.created_at)),cCalc.initialCarbs,decaysin_hr,profile.getCarbAbsorptionRate(treatment.created_at)); isDecaying = cCalc.isDecaying; } else { totalCOB = 0; From 711b292861c57ca3933bb7da37a39402c6fdbdb5 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 08:10:16 +0300 Subject: [PATCH 203/661] Removing a logging line --- lib/plugins/cob.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 4a3c0a3ad01..dba62c81b26 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -47,7 +47,6 @@ function init() { if (treatment.carbs && treatment.created_at < time) { lastCarbs = treatment; var cCalc = cob.cobCalc(treatment, profile, lastDecayedBy, time); - console.log(cCalc); var decaysin_hr = (cCalc.decayedBy - time) / 1000 / 60 / 60; if (decaysin_hr > -10) { var actStart = iob.calcTotal(treatments, profile, lastDecayedBy).activity; From 73c08428df4a7108dfd546d51bd80afea5b6bf9e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 23 Jun 2015 08:39:51 +0300 Subject: [PATCH 204/661] Fix Pebble API. This really needs to start using the sbx --- lib/pebble.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index 8f7bf1ee241..b13280a9a52 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -16,6 +16,7 @@ var DIRECTIONS = { var iob = require("./plugins/iob")(); var async = require('async'); var units = require('./units')(); +var profileObject = require("./profilefunctions")(); function directionToTrend (direction) { var trend = 8; @@ -83,7 +84,7 @@ function pebble (req, res) { profileResults.forEach(function (profile) { if (profile) { if (profile.dia) { - profileResult = profile; + profileResult = profileObject.loadData([profile]); } } }); From c5408bc412cb7ac3a3c8352b7ea25c855a2fe463 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 23 Jun 2015 17:49:45 -0700 Subject: [PATCH 205/661] some plugins such as delta and upbat are always shown for now --- lib/plugins/index.js | 4 +++- static/js/ui-utils.js | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 09024bddc06..3da8783b863 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -80,9 +80,11 @@ function init() { _.forEach(enabledPlugins, f); }; + plugins.alwaysShown = 'delta upbat'; + plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1; + return plugins.alwaysShown.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); }); }; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index ec744ae1318..d52ce7fd7ca 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -99,10 +99,14 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - showPluginsSettings.append(dd); - dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + if (Nightscout.plugins.alwaysShown.indexOf(plugin.name) > -1) { + //ignore these, they are always on for now + } else { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + } }); From 2cb7009f94a0bbc40dda29d246b2458925bdffd1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 23 Jun 2015 21:17:49 -0700 Subject: [PATCH 206/661] add a little special handling for rawbg, similar to delta and uploader battery --- lib/plugins/index.js | 5 +++-- lib/plugins/pluginbase.js | 7 ++++++- static/css/main.css | 16 ++++++++++------ static/index.html | 2 +- static/js/ui-utils.js | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 3da8783b863..f6bb629bf4e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -80,11 +80,12 @@ function init() { _.forEach(enabledPlugins, f); }; - plugins.alwaysShown = 'delta upbat'; + //these plugins are either always on or have custom settings + plugins.specialPlugins = 'delta upbat rawbg'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { - return plugins.alwaysShown.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); + return plugins.specialPlugins.indexOf(plugin.name) > -1 || (sbx && sbx.showPlugins && sbx.showPlugins.indexOf(plugin.name) > -1); }); }; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 2cac7e6c447..18800551b8a 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -51,7 +51,12 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var pill = findOrCreatePill(plugin); - pill.toggle(!options.hide); + if (options.hide) { + pill.addClass('hidden'); + } else { + pill.removeClass('hidden'); + } + pill.addClass(options.pillClass); pill.find('label').attr('class', options.labelClass).text(options.label); diff --git a/static/css/main.css b/static/css/main.css index 585f0b3390c..bb17a8fda3a 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -243,24 +243,24 @@ body { width: 360px; } -.loading .bgButton .pill.rawbg { +.loading .pill.rawbg { display: none; } -.bgButton .pill.rawbg { +.pill.rawbg { display: inline-block; border-radius: 2px; border: 1px solid #808080; } -.bgButton .pill.rawbg em { +.pill.rawbg em { color: white; background-color: black; display: block; font-size: 20px; } -.bgButton .pill.rawbg label { +.pill.rawbg label { display: block; font-size: 14px; } @@ -338,6 +338,10 @@ div.tooltip { background: #808080; } +.pill.hidden { + display: none; +} + @media (max-width: 800px) { .bgStatus { width: 300px; @@ -421,11 +425,11 @@ div.tooltip { padding: 0; } - .bgButton .pill.rawbg em { + .pill.rawbg em { font-size: 14px; } - .bgButton .pill.rawbg label { + .pill.rawbg label { font-size: 12px; } diff --git a/static/index.html b/static/index.html index 9a01e73a811..2b2d40ab14b 100644 --- a/static/index.html +++ b/static/index.html @@ -47,7 +47,7 @@

    Nightscout

    - + --- -
    diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index d52ce7fd7ca..5667070df51 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -99,7 +99,7 @@ function getBrowserSettings(storage) { json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - if (Nightscout.plugins.alwaysShown.indexOf(plugin.name) > -1) { + if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { //ignore these, they are always on for now } else { var id = 'plugin-' + plugin.name; From b85cd25b5386d64f994fbcede840ece84e02b171 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 20:07:54 +0200 Subject: [PATCH 207/661] Added the dockerfile --- .dockerignore | 17 +++++++++++++++++ Dockerfile | 10 ++++++++-- docker-build.sh | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 docker-build.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..9bd34f41610 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +bower_components/ +node_modules/ + +.idea/ +*.iml + +*.env +static/bower_components/ +.*.sw? +.DS_Store + +.vagrant +/iisnode + +# istanbul output +coverage/ +npm-debug.log diff --git a/Dockerfile b/Dockerfile index a1b8ded65d3..9e91a997d0d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,15 @@ -FROM node:0.10.38-slim +FROM node:0.12.38-slim MAINTAINER Nightscout # Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -RUN apt-get update && apt-get -y install netcat +RUN apt-get update && apt-get -y install netcat git + +# Got this from the setup.sh +RUN apt-get install -y python-software-properties python g++ make git + +RUN apt-get upgrade -y + RUN npm install . EXPOSE 1337 diff --git a/docker-build.sh b/docker-build.sh new file mode 100644 index 00000000000..05ed0164c72 --- /dev/null +++ b/docker-build.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +sudo docker build -t local/nightscout . +sudo docker run -t local/nightscout \ No newline at end of file From 6153e6305bd0b83137c8b729627a028e95a136c7 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 22:24:26 +0200 Subject: [PATCH 208/661] stash --- lib/devicestatus.js | 2 +- lib/entries.js | 2 +- lib/profile.js | 2 +- lib/storage.js | 97 ++++++++++++++++++++----------------------- lib/treatments.js | 2 +- tests/storage.test.js | 58 ++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 57 deletions(-) create mode 100644 tests/storage.test.js diff --git a/lib/devicestatus.js b/lib/devicestatus.js index a831e936bab..ace6a6e3b32 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -33,7 +33,7 @@ function storage (collection, ctx) { } function api() { - return ctx.store.pool.db.collection(collection); + return ctx.store.db.collection(collection); } diff --git a/lib/entries.js b/lib/entries.js index e271272ff3c..07e336b91b8 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -143,7 +143,7 @@ function storage(env, ctx) { function api ( ) { // obtain handle usable for querying the collection associated // with these records - return ctx.store.pool.db.collection(env.mongo_collection); + return ctx.store.db.collection(env.mongo_collection); } // Expose all the useful functions diff --git a/lib/profile.js b/lib/profile.js index 7a7486411cb..ca97ff6d841 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -28,7 +28,7 @@ function storage (collection, ctx) { } function api () { - return ctx.store.pool.db.collection(collection); + return ctx.store.db.collection(collection); } api.list = list; diff --git a/lib/storage.js b/lib/storage.js index f6567ab12fd..a3e7c71548c 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -4,32 +4,49 @@ var mongodb = require('mongodb'); function init (env, cb) { var MongoClient = mongodb.MongoClient; - var my = { }; - function maybe_connect (cb) { - - if (my.db) { - console.log("Reusing MongoDB connection handler"); - // If there is a valid callback, then return the Mongo-object - if (cb && cb.call) { cb(null, mongo); } - return; - } + var mongo = { + collection: function get_collection (name) { + return mongo.db.collection(name); + }, + with_collection: function with_collection (name) { + return function use_collection(fn) { + fn(null, mongo.db.collection(name)); + } + }, + limit: function limit (opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); + } + return this; + }, + ensureIndexes: function(collection, fields) { + fields.forEach(function (field) { + console.info('Ensuring index for: ' + field); + collection.ensureIndex(field, function (err) { + if (err) { + console.error('unable to ensureIndex for: ' + field + ' - ' + err); + } + }); + }) + }, + db: null // Yet to be assigned + }; + function newConnection (cb) { if (!env.mongo) { - throw new Error("MongoDB connection string is missing"); + throw new Error('MongoDB connection string is missing'); } - console.log("Setting up new connection to MongoDB"); + console.log('Setting up new connection to MongoDB'); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { - console.log("Error connecting to MongoDB, ERROR: %j", err); + console.log('Error connecting to MongoDB, ERROR: %j', err); throw err; } else { - console.log("Successfully established a connected to MongoDB"); + console.log('Successfully established a connected to MongoDB'); } - // FIXME Fokko: I would suggest to just create a private db variable instead of the separate my, pool construction. - my.db = db; - mongo.pool.db = my.db = db; + mongo.db = db; // If there is a valid callback, then invoke the function to perform the callback if (cb && cb.call) @@ -37,45 +54,19 @@ function init (env, cb) { }); } - function mongo (cb) { - maybe_connect(cb); - mongo.pool.db = my.db; - return mongo; - } - - mongo.pool = function ( ) { - return my; - }; - - mongo.collection = function get_collection (name) { - return mongo.pool( ).db.collection(name); - }; - - mongo.with_collection = function with_collection (name) { - return function use_collection (fn) { - fn(null, mongo.pool( ).db.collection(name)); - }; - }; - - mongo.limit = function limit (opts) { - if (opts && opts.count) { - return this.limit(parseInt(opts.count)); + return function mongo (cb) { + if (mongo.db != null) { + console.log('Reusing MongoDB connection handler'); + // If there is a valid callback, then return the Mongo-object + if (cb && cb.call) { + cb(null, mongo); + } + } else { + newConnection(cb); } - return this; - }; - mongo.ensureIndexes = function(collection, fields) { - fields.forEach(function (field) { - console.info("ensuring index for: " + field); - collection.ensureIndex(field, function (err) { - if (err) { - console.error("unable to ensureIndex for: " + field + " - " + err); - } - }); - }); - }; - - return mongo(cb); + return mongo; + } } module.exports = init; diff --git a/lib/treatments.js b/lib/treatments.js index 8e92128dcc8..98821fd858a 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -66,7 +66,7 @@ function storage (env, ctx) { } function api ( ) { - return ctx.store.pool.db.collection(env.treatments_collection); + return ctx.store.db.collection(env.treatments_collection); } api.list = list; diff --git a/tests/storage.test.js b/tests/storage.test.js new file mode 100644 index 00000000000..7698f94d606 --- /dev/null +++ b/tests/storage.test.js @@ -0,0 +1,58 @@ +'use strict'; + +var request = require('supertest'); +var should = require('should'); +var assert = require('assert'); +var load = require('./fixtures/load'); + +describe('STORAGE', function () { + var env = require('../env')( ); + + before(function (done) { + delete env.api_secret; + done(); + }); + + it('The storage class should be OK.', function (done) { + require('../lib/storage').should.be.ok; + done(); + }); + + it('After initializing the storage class it should re-use the open connection', function (done) { + var store = require('../lib/storage'); + store(env, function (err1, db1) { + should.not.exist(err1); + + store(env, function (err2, db2) { + should.not.exist(err2); + + console.log(db1 == db2) + + done(); + }); + }); + }); + + it('When no connection-string is given the storage-class should throw an error.', function (done) { + delete env.mongo; + should.not.exist(env.mongo); + + (function(){ + return require('../lib/storage')(env); + }).should.throw('MongoDB connection string is missing'); + + done(); + }); + + it('An invalid connection-string should throw an error.', function (done) { + env.mongo = 'This is not a MongoDB connection-string'; + + (function(){ + return require('../lib/storage')(env); + }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); + + done(); + }); + +}); + From 1c28e073497e7e405047bad7f3158bc7dca3f790 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Wed, 24 Jun 2015 23:47:45 +0200 Subject: [PATCH 209/661] Fixed the re-use of the socket and written tests which hit the few missed lines --- lib/storage.js | 113 ++++++++++++++++++++++-------------------- tests/storage.test.js | 13 +++-- 2 files changed, 65 insertions(+), 61 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index a3e7c71548c..ac8f745c40c 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,72 +1,77 @@ 'use strict'; + var mongodb = require('mongodb'); -function init (env, cb) { +var connection = null; + +function init(env, cb, forceNewConnection) { var MongoClient = mongodb.MongoClient; + var mongo = {}; + + function maybe_connect(cb) { + + if (connection != null && !forceNewConnection) { + console.log('Reusing MongoDB connection handler'); + // If there is a valid callback, then return the Mongo-object + mongo.db = connection; - var mongo = { - collection: function get_collection (name) { - return mongo.db.collection(name); - }, - with_collection: function with_collection (name) { - return function use_collection(fn) { - fn(null, mongo.db.collection(name)); + if (cb && cb.call) { + cb(null, mongo); } - }, - limit: function limit (opts) { - if (opts && opts.count) { - return this.limit(parseInt(opts.count)); + } else { + if (!env.mongo) { + throw new Error('MongoDB connection string is missing'); } - return this; - }, - ensureIndexes: function(collection, fields) { - fields.forEach(function (field) { - console.info('Ensuring index for: ' + field); - collection.ensureIndex(field, function (err) { - if (err) { - console.error('unable to ensureIndex for: ' + field + ' - ' + err); - } - }); - }) - }, - db: null // Yet to be assigned - }; - function newConnection (cb) { - if (!env.mongo) { - throw new Error('MongoDB connection string is missing'); - } + console.log('Setting up new connection to MongoDB'); + MongoClient.connect(env.mongo, function connected(err, db) { + if (err) { + console.log('Error connecting to MongoDB, ERROR: %j', err); + throw err; + } else { + console.log('Successfully established a connected to MongoDB'); + } - console.log('Setting up new connection to MongoDB'); - MongoClient.connect(env.mongo, function connected (err, db) { - if (err) { - console.log('Error connecting to MongoDB, ERROR: %j', err); - throw err; - } else { - console.log('Successfully established a connected to MongoDB'); - } + connection = db; - mongo.db = db; + mongo.db = connection; - // If there is a valid callback, then invoke the function to perform the callback - if (cb && cb.call) + // If there is a valid callback, then invoke the function to perform the callback + if (cb && cb.call) cb(err, mongo); - }); + }); + } } - return function mongo (cb) { - if (mongo.db != null) { - console.log('Reusing MongoDB connection handler'); - // If there is a valid callback, then return the Mongo-object - if (cb && cb.call) { - cb(null, mongo); - } - } else { - newConnection(cb); + mongo.collection = function get_collection(name) { + return connection.collection(name); + }; + + mongo.with_collection = function with_collection(name) { + return function use_collection(fn) { + fn(null, connection.collection(name)); + }; + }; + + mongo.limit = function limit(opts) { + if (opts && opts.count) { + return this.limit(parseInt(opts.count)); } + return this; + }; - return mongo; - } + mongo.ensureIndexes = function (collection, fields) { + fields.forEach(function (field) { + console.info('ensuring index for: ' + field); + collection.ensureIndex(field, function (err) { + if (err) { + console.error('unable to ensureIndex for: ' + field + ' - ' + err); + } + }); + }); + }; + + return maybe_connect(cb); } -module.exports = init; +module.exports = init; \ No newline at end of file diff --git a/tests/storage.test.js b/tests/storage.test.js index 7698f94d606..a4a5d8152a5 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -6,7 +6,7 @@ var assert = require('assert'); var load = require('./fixtures/load'); describe('STORAGE', function () { - var env = require('../env')( ); + var env = require('../env')(); before(function (done) { delete env.api_secret; @@ -25,8 +25,7 @@ describe('STORAGE', function () { store(env, function (err2, db2) { should.not.exist(err2); - - console.log(db1 == db2) + assert(db1.db, db2.db, 'Check if the handlers are the same.') done(); }); @@ -37,8 +36,8 @@ describe('STORAGE', function () { delete env.mongo; should.not.exist(env.mongo); - (function(){ - return require('../lib/storage')(env); + (function () { + return require('../lib/storage')(env, false, true); }).should.throw('MongoDB connection string is missing'); done(); @@ -47,8 +46,8 @@ describe('STORAGE', function () { it('An invalid connection-string should throw an error.', function (done) { env.mongo = 'This is not a MongoDB connection-string'; - (function(){ - return require('../lib/storage')(env); + (function () { + return require('../lib/storage')(env, false, true); }).should.throw('URL must be in the format mongodb://user:pass@host:port/dbname'); done(); From 4d888696cb9a064166b18e7951c14ba3101fe881 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 24 Jun 2015 15:25:03 -0700 Subject: [PATCH 210/661] make sure we really have a rawbg prop before trying to display it --- lib/plugins/rawbg.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index f7017070c88..17814c2a92d 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -33,7 +33,7 @@ function init() { rawbg.updateVisualisation = function updateVisualisation (sbx) { var prop = sbx.properties.rawbg; - var options = rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { + var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value , value: prop.value , label: prop.noiseLabel From 1b871b97f431618aa425ed82f4f834f717f9a6d5 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 25 Jun 2015 09:55:08 +0300 Subject: [PATCH 211/661] Fix delta rounding for mmol users to prevent values such as -0.2999999999997 from being shown --- lib/plugins/delta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/delta.js b/lib/plugins/delta.js index 2bae298a952..f8c9112e9d1 100644 --- a/lib/plugins/delta.js +++ b/lib/plugins/delta.js @@ -36,7 +36,7 @@ function init() { if (currentSGV < 40 || prevSVG < 40) { return result; } if (currentSGV > 400 || prevSVG > 400) { return result; } - result.value = sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG); + result.value = sbx.roundBGToDisplayFormat(sbx.scaleBg(currentSGV) - sbx.scaleBg(prevSVG)); result.display = (result.value >= 0 ? '+' : '') + result.value; return result; From 855e95145cbddda9c8fe119ee7cef0f17c88f16e Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 25 Jun 2015 09:56:23 +0300 Subject: [PATCH 212/661] IU -> U on basal display --- lib/plugins/basalprofile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index adc8c04134b..2d0851becbd 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -36,9 +36,9 @@ function init() { var basalValue = sbx.data.profile.getBasal(sbx.time); - var info = [{label: 'Current basal:', value: basalValue + ' IU'} - , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ IU'} - , {label: 'Current carb ratio:', value: '1 IU /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} + var info = [{label: 'Current basal:', value: basalValue + ' U'} + , {label: 'Current sensitivity:', value: sbx.data.profile.getSensitivity(sbx.time) + ' ' + sbx.units + '/ U'} + , {label: 'Current carb ratio:', value: '1 U /' + sbx.data.profile.getCarbRatio(sbx.time) + 'g'} ]; sbx.pluginBase.updatePillText(basal, { From ebaa72096b67776dc310f187a5d6ab02c48d846b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 25 Jun 2015 19:37:02 -0700 Subject: [PATCH 213/661] better BWP and AR2 pushover messages; send all pushover alarms as emergengy so we get the full ack support --- lib/plugins/ar2.js | 12 +++++++- lib/plugins/boluswizardpreview.js | 46 ++++++++++++++++++++++--------- lib/pushnotify.js | 29 +++++++++++-------- tests/boluswizardpreview.test.js | 3 ++ 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index ab8f64c0bad..0654db56a0f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -72,9 +72,19 @@ function init() { , ['15m', predicted[2], sbx.unitsLabel].join(' ') ]; + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; + if (bwp) { + lines.push(['BWP:', bwp, 'U'].join(' ')); + } + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { - lines.unshift(['\nIOB:', iob, 'U'].join(' ')); + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.cob; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); } var message = lines.join('\n'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6adcdcdc8ce..a76fc4af016 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -45,13 +45,19 @@ function init() { } - bwp.checkNotifications = function checkNotifications (sbx) { + bwp.setProperties = function setProperties(sbx) { + sbx.offerProperty('bwp', function setBWP ( ) { + if (hasRequiredInfo(sbx)) { + return bwp.calc(sbx); + } + }); + }; - if (!hasRequiredInfo(sbx)) { - return; - } - var results = bwp.calc(sbx); + bwp.checkNotifications = function checkNotifications (sbx) { + + var results = sbx.properties.bwp; + if (results == undefined) return; if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; @@ -71,15 +77,32 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var message = [levelLabel, results.lastSGV, sbx.unitsLabel].join(' '); + + var lines = [ + ['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ') + , ['BWP:', results.bolusEstimate, 'U'].join(' ') + ]; + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { - message += ['\nIOB:', iob, 'U'].join(' '); + lines.push(['\nIOB:', iob, 'U'].join(' ')); } + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.cob; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); + } + + var message = lines.join('\n'); + sbx.notifications.requestNotify({ level: level - , title: 'Check BG, time to bolus?' + , title: levelLabel + 'Check BG, time to bolus?' , message: message , pushoverSound: sound , plugin: bwp @@ -91,11 +114,8 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { - if (!hasRequiredInfo(sbx)) { - return; - } - - var results = bwp.calc(sbx); + var results = sbx.properties.bwp; + if (results == undefined) return; // display text var info = [ diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 2de184fac8d..6f9f23ce754 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -9,8 +9,12 @@ function init(env, ctx) { var pushover = require('./pushover')(env); + var PUSHOVER_EMERGENCY = 2; + var PUSHOVER_NORMAL = 0; + // declare local constants for time differences - var TIME_15_MINS_S = 15 * 60 + var TIME_2_MINS_S = 120 + , TIME_15_MINS_S = 15 * 60 , TIME_15_MINS_MS = TIME_15_MINS_S * 1000 , TIME_30_MINS_MS = 30 * 60 * 1000 ; @@ -46,23 +50,26 @@ function init(env, ctx) { , message: notify.message , sound: notify.pushoverSound || 'gamelan' , timestamp: new Date() - , priority: notify.level + //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks + , priority: notify.level > ctx.notifications.levels.WARN ? PUSHOVER_EMERGENCY : PUSHOVER_NORMAL }; - if (notify.level == ctx.notifications.levels.URGENT) { - msg.retry = 120; + if (notify.level >= ctx.notifications.levels.WARN) { + //ADJUST RETRY TIME based on WARN or URGENT + msg.retry = notify.level == ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; if (env.baseUrl) { msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; } - } else if (notify.level == ctx.notifications.levels.WARN && env.baseUrl) { - var now = Date.now(); - var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - if (sig) { - msg.url_title = 'Snooze for 30 minutes'; - msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - } } + // if we want to have a callback snooze url this is the way, but emergency ack work better + // var now = Date.now(); + // var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + // if (sig) { + // msg.url_title = 'Snooze for 30 minutes'; + // msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + // } + //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); pushover.send(msg, function(err, result) { diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 7338e15c98c..9677c9d2c5e 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -27,6 +27,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); should.not.exist(ctx.notifications.findHighestAlarm()); @@ -43,6 +44,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); @@ -59,6 +61,7 @@ describe('boluswizardpreview', function ( ) { return {iob: 0} }); + boluswizardpreview.setProperties(sbx); boluswizardpreview.checkNotifications(sbx); ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); From 151b952a537f0e9786cee465215f271e3f8a68fc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 00:02:43 -0700 Subject: [PATCH 214/661] even better notification messages, added option to use raw bgs for ar2 forecast --- lib/notifications.js | 2 +- lib/plugins/ar2.js | 59 +++++++++++++++++++++++-------- lib/plugins/boluswizardpreview.js | 24 ++++++++----- lib/plugins/cob.js | 13 +++---- lib/plugins/rawbg.js | 4 +-- lib/plugins/simplealarms.js | 6 ++++ lib/plugins/treatmentnotify.js | 5 +-- 7 files changed, 77 insertions(+), 36 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 1b219a0dc52..4ac61fe82a5 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -36,7 +36,7 @@ function init (env, ctx) { case 2: return 'Urgent'; case 1: - return 'Warn'; + return 'Warning'; case 0: return 'Info'; case -1: diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 0654db56a0f..fa64d71170f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var rawbg = require('./rawbg')(); function init() { @@ -11,6 +12,9 @@ function init() { ar2.label = 'AR2'; ar2.pluginType = 'forecast'; + var BG_REF = 140; //Central tendency + var BG_MIN = 36; //Not 39, but why? + var BG_MAX = 400; var WARN_THRESHOLD = 0.05; var URGENT_THRESHOLD = 0.10; @@ -31,7 +35,7 @@ function init() { ; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - forecast = ar2.forecast(sbx.data.sgvs); + forecast = ar2.forecast(sbx.data.sgvs, sbx); if (forecast.avgLoss > URGENT_THRESHOLD) { trigger = true; level = 2; @@ -66,14 +70,26 @@ function init() { rangeLabel = ''; } + var rawbgProp = sbx.properties.rawbg; + var useRaw = rawbgProp && sbx.extendedSettings.useRaw; + var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var lines = [ - ['Now', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ') - , ['15m', predicted[2], sbx.unitsLabel].join(' ') - ]; + var lines = []; + + if (useRaw) { + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } else { + lines.push(['BG Now:', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ')); + } + + lines.push([useRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + + if (rawbgProp && !useRaw) { //If we're using raw for the forecast, don't add dupe line to the message + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp) { + if (bwp && bwp > 0) { lines.push(['BWP:', bwp, 'U'].join(' ')); } @@ -82,7 +98,7 @@ function init() { lines.push(['IOB:', iob, 'U'].join(' ')); } - var cob = sbx.properties.cob && sbx.properties.cob.cob; + var cob = sbx.properties.cob && sbx.properties.cob.display; if (cob) { lines.push(['COB:', cob, 'g'].join(' ')); } @@ -105,7 +121,7 @@ function init() { } }; - ar2.forecast = function forecast(sgvs) { + ar2.forecast = function forecast(sgvs, sbx) { var lastIndex = sgvs.length - 1; @@ -114,16 +130,31 @@ function init() { , avgLoss: 0 }; - if (lastIndex > 0 && sgvs[lastIndex].y > 39 && sgvs[lastIndex - 1].y > 39) { + var current = sgvs[lastIndex].y; + var prev = sgvs[lastIndex - 1].y; + + var useRaw = sbx && sbx.extendedSettings.useRaw; + + if (useRaw) { + var cal = _.last(sbx.data.cals); + var currentRaw = rawbg.calc(sgvs[lastIndex], cal); + if (currentRaw) { + var prevRaw = rawbg.calc(sgvs[lastIndex - 1], cal); + if (prevRaw) { + current = currentRaw; + prev = prevRaw; + console.info('Using raw value for forecasting :)', prev, current); + } + } + } + + if (lastIndex > 0 && current > BG_MIN && sgvs[lastIndex - 1].y > BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; - var BG_REF = 140; - var BG_MIN = 36; - var BG_MAX = 400; - var y = Math.log(sgvs[lastIndex].y / BG_REF); + var y = Math.log(current / BG_REF); if (elapsedMins < 5.1) { - y = [Math.log(sgvs[lastIndex - 1].y / BG_REF), y]; + y = [Math.log(prev / BG_REF), y]; } else { y = [y, y]; } diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index a76fc4af016..282e479aa5a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -78,22 +78,28 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = [ - ['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ') - , ['BWP:', results.bolusEstimate, 'U'].join(' ') - ]; + var lines = []; - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['\nIOB:', iob, 'U'].join(' ')); + var rawbgProp = sbx.properties.rawbg; + + lines.push(['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ')); + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines.push('Delta: ' + delta); + } + + lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); + var iob = sbx.properties.iob && sbx.properties.iob.display; if (iob) { lines.push(['IOB:', iob, 'U'].join(' ')); } - var cob = sbx.properties.cob && sbx.properties.cob.cob; + var cob = sbx.properties.cob && sbx.properties.cob.display; if (cob) { lines.push(['COB:', cob, 'g'].join(' ')); } @@ -102,7 +108,7 @@ function init() { sbx.notifications.requestNotify({ level: level - , title: levelLabel + 'Check BG, time to bolus?' + , title: levelLabel + ', Check BG, time to bolus?' , message: message , pushoverSound: sound , plugin: bwp diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index cc443d62f64..e2b9c0c6648 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -79,12 +79,13 @@ function init() { var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; return { - decayedBy: lastDecayedBy, - isDecaying: isDecaying, - carbs_hr: profile.getCarbAbsorptionRate(time), - rawCarbImpact: rawCarbImpact, - cob: totalCOB, - lastCarbs: lastCarbs + decayedBy: lastDecayedBy + , isDecaying: isDecaying + , carbs_hr: profile.getCarbAbsorptionRate(time) + , rawCarbImpact: rawCarbImpact + , cob: totalCOB + , display: Math.round(totalCOB * 10) / 10 + , lastCarbs: lastCarbs }; }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 17814c2a92d..cbd3410bc0a 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -44,7 +44,7 @@ function init() { sbx.pluginBase.updatePillText(rawbg, options); }; - rawbg.calc = function calc(sgv, cal, sbx) { + rawbg.calc = function calc(sgv, cal) { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 @@ -63,7 +63,7 @@ function init() { raw = scale * ( unfiltered - intercept) / slope / ratio; } - return sbx.scaleBg(Math.round(raw)); + return Math.round(raw); }; rawbg.isEnabled = function isEnabled(sbx) { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 8db1aea089d..506326ff4a3 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -54,6 +54,12 @@ function init() { pushoverSound = 'magic'; } + var message = lastSGV + ' ' + sbx.unitsLabel; + + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + message += '\nDelta: ' + delta; + } if (trigger) { diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 255aa7d9f26..c68346b152a 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -43,7 +43,6 @@ function init() { sbx.notifications.requestSnooze({ level: sbx.notifications.levels.URGENT , lengthMills: snoozeLength - //, debug: results }); } @@ -53,10 +52,9 @@ function init() { level: sbx.notifications.levels.INFO , title: 'Calibration' //assume all MGBs are calibrations for now //TODO: figure out why mbg is y here #CleanUpDataModel - , message: '\nMeter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel + , message: 'Meter BG: ' + sbx.scaleBg(lastMBG.y) + ' ' + sbx.unitsLabel , plugin: treatmentnotify , pushoverSound: 'magic' - //, debug: results }); } @@ -81,7 +79,6 @@ function init() { , title: lastTreatment.eventType , message: message , plugin: treatmentnotify -// , debug: results }); } From ff439acd7be19eac70b70759d676c8b2864320a5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 00:22:02 -0700 Subject: [PATCH 215/661] update readme with new plugin/teatment profile info --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 10ee92a7bc5..9d4d99a4f49 100644 --- a/README.md +++ b/README.md @@ -154,10 +154,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** - * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended setting](#extended-settings) `AR2_USE_RAW=true` to forecast using `rawbg` values. * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). + * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). #### 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. @@ -165,7 +166,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. For example (change it to fit you): + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): ```json { @@ -173,19 +174,72 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. "carbs_hr": 30, "carbratio": 7.5, "sens": 35, + "basal": 1.00 "target_low": 95, "target_high": 120 } ``` + + Profiles can also use time periods for any field, for example: + + ```json + { + "carbratio": [ + { + "time": "00:00", + "value": 16 + }, + { + "time": "06:00", + "value": 15 + }, + { + "time": "14:00", + "value": 16 + } + ], + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.1 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.3 + }, + { + "time": "22:00", + "value": 0.225 + } + ] + } + ``` Treatment Profile Fields: - * `dia` (Insulin duration) - value should be the duration of insulin action to use in calculating how much insulin is left active. Defaults to 3 hours - * `carbs_hr` (Carbs per Hour) - The number of carbs that are processed per hour, for more information see [#DIYPS](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/) - * `carbratio` (Carb Ratio) - grams per unit of insulin + * `dia` (Insulin duration) - value should be the duration of insulin action to use in calculating how much insulin is left active. Defaults to 3 hours. + * `carbs_hr` (Carbs per Hour) - The number of carbs that are processed per hour, for more information see [#DIYPS](http://diyps.org/2014/05/29/determining-your-carbohydrate-absorption-rate-diyps-lessons-learned/). + * `carbratio` (Carb Ratio) - grams per unit of insulin. * `sens` (Insulin sensitivity) How much one unit of insulin will normally lower blood glucose. - * `target_high` - Upper target for correction boluses - * `target_low` - Lower target for correction boluses + * `basal` The basal rate set on the pump. + * `target_high` - Upper target for correction boluses. + * `target_low` - Lower target for correction boluses. Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). From 6dce8e1c2387716cd19432d707078e296711ba7b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:25:25 -0700 Subject: [PATCH 216/661] only use raw for ar2 if an alarms wasn't triggered for the sgvs; more message and test improvements --- lib/plugins/ar2.js | 121 ++++++++++++++++-------------- lib/plugins/boluswizardpreview.js | 15 ++-- lib/plugins/simplealarms.js | 8 +- tests/ar2.test.js | 67 +++++++++++++++-- tests/simplealarms.test.js | 10 ++- 5 files changed, 143 insertions(+), 78 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index fa64d71170f..15e4dc7f8dc 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -26,31 +26,32 @@ function init() { ar2.checkNotifications = function checkNotifications(sbx) { - var trigger = false - , lastSGVEntry = _.last(sbx.data.sgvs) - , forecast = null - , level = 0 - , levelLabel = '' - , pushoverSound = null - ; + var lastSGVEntry = _.last(sbx.data.sgvs); + + var result = {}; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - forecast = ar2.forecast(sbx.data.sgvs, sbx); - if (forecast.avgLoss > URGENT_THRESHOLD) { - trigger = true; - level = 2; - levelLabel = 'Urgent'; - pushoverSound = 'persistent'; - } else if (forecast.avgLoss > WARN_THRESHOLD) { - trigger = true; - level = 1; - levelLabel = 'Warning'; + result = ar2.forcastAndCheck(sbx.data.sgvs) + } + + var usingRaw = false; + if (!result.trigger && sbx.extendedSettings.useRaw) { + var cal = _.last(sbx.data.cals); + if (cal) { + var rawSGVs = _.map(_.takeRight(sbx.data.sgvs, 2), function eachSGV (sgv) { + return { + x: sgv.x + , y: Math.max(rawbg.calc(sgv, cal), BG_MIN) //stay above BG_MIN + }; + }); + result = ar2.forcastAndCheck(rawSGVs, true); + usingRaw = true; } } - if (trigger) { + if (result.trigger) { - var predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); + var predicted = _.map(result.forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ); var first = _.first(predicted); var last = _.last(predicted); @@ -62,32 +63,36 @@ function init() { var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; - if (!pushoverSound) pushoverSound = 'climb' } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { - rangeLabel = 'LOW'; - if (!pushoverSound) pushoverSound = 'falling' + rangeLabel = 'LOW'; } else { - rangeLabel = ''; + rangeLabel = 'Check BG'; } - var rawbgProp = sbx.properties.rawbg; - var useRaw = rawbgProp && sbx.extendedSettings.useRaw; + var title = sbx.notifications.levels.toString(result.level) + ', ' + rangeLabel; + title += ' BG'; + if (lastSGVEntry.y > sbx.thresholds.bg_target_bottom && lastSGVEntry.y < sbx.thresholds.bg_target_top) { + title += ' predicted'; + } + if (usingRaw) { + title += ' w/raw'; + } - var title = [levelLabel, rangeLabel, 'predicted'].join(' ').replace(' ', ' '); - var lines = []; + var lines = ['BG Now: ' + sbx.scaleBg(sbx.data.lastSGV())]; - if (useRaw) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } else { - lines.push(['BG Now:', sbx.scaleBg(sbx.data.lastSGV()), sbx.unitsLabel].join(' ')); + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines[0] += ' ' + delta; } + lines[0] += ' ' + sbx.unitsLabel; - lines.push([useRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); - - if (rawbgProp && !useRaw) { //If we're using raw for the forecast, don't add dupe line to the message - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + var rawbgProp = sbx.properties.rawbg; + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } + lines.push([usingRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; if (bwp && bwp > 0) { lines.push(['BWP:', bwp, 'U'].join(' ')); @@ -105,23 +110,40 @@ function init() { var message = lines.join('\n'); - forecast.predicted = _.map(forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', '); - sbx.notifications.requestNotify({ - level: level + level: result.level , title: title , message: message - , pushoverSound: pushoverSound + , pushoverSound: result.pushoverSound , plugin: ar2 , debug: { - forecast: forecast + forecast: { + predicted: _.map(result.forecast.predicted, function(p) { return sbx.scaleBg(p.y) } ).join(', ') + } , thresholds: sbx.thresholds } }); } }; - ar2.forecast = function forecast(sgvs, sbx) { + ar2.forcastAndCheck = function forcastAndCheck(sgvs, usingRaw) { + var result = { + forecast: ar2.forecast(sgvs) + }; + + if (result.forecast.avgLoss > URGENT_THRESHOLD && !usingRaw) { + result.trigger = true; + result.level = 2; + result.pushoverSound = 'persistent'; + } else if (result.forecast.avgLoss > WARN_THRESHOLD) { + result.trigger = true; + result.level = 1; + } + + return result; + }; + + ar2.forecast = function forecast(sgvs) { var lastIndex = sgvs.length - 1; @@ -133,22 +155,7 @@ function init() { var current = sgvs[lastIndex].y; var prev = sgvs[lastIndex - 1].y; - var useRaw = sbx && sbx.extendedSettings.useRaw; - - if (useRaw) { - var cal = _.last(sbx.data.cals); - var currentRaw = rawbg.calc(sgvs[lastIndex], cal); - if (currentRaw) { - var prevRaw = rawbg.calc(sgvs[lastIndex - 1], cal); - if (prevRaw) { - current = currentRaw; - prev = prevRaw; - console.info('Using raw value for forecasting :)', prev, current); - } - } - } - - if (lastIndex > 0 && current > BG_MIN && sgvs[lastIndex - 1].y > BG_MIN) { + if (lastIndex > 0 && current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 282e479aa5a..c575afce812 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -78,20 +78,19 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = []; + var lines = ['BG Now: ' + results.lastSGV]; - var rawbgProp = sbx.properties.rawbg; + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + lines[0] += ' ' + delta; + } + lines[0] += ' ' + sbx.unitsLabel; - lines.push(['BG NOW:', results.lastSGV, sbx.unitsLabel].join(' ')); + var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines.push('Delta: ' + delta); - } - lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); var iob = sbx.properties.iob && sbx.properties.iob.display; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 506326ff4a3..fc4d61c7364 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -54,19 +54,19 @@ function init() { pushoverSound = 'magic'; } - var message = lastSGV + ' ' + sbx.unitsLabel; + var message = 'BG Now: ' + lastSGV; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { - message += '\nDelta: ' + delta; + message += ' ' + delta; } - + message += ' ' + sbx.unitsLabel; if (trigger) { sbx.notifications.requestNotify({ level: level , title: title - , message: [lastSGV, sbx.unitsLabel].join(' ') + , message: message , plugin: simplealarms , pushoverSound: pushoverSound , debug: { diff --git a/tests/ar2.test.js b/tests/ar2.test.js index a84f8febf66..c94cfe74e57 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -3,8 +3,12 @@ var should = require('should'); describe('ar2', function ( ) { var ar2 = require('../lib/plugins/ar2')(); + var delta = require('../lib/plugins/delta')(); var env = require('../env')(); + var envRaw = require('../env')(); + envRaw.extendedSettings = {'ar2': {useRaw: true}}; + var ctx = {}; ctx.data = require('../lib/data')(env, ctx); ctx.notifications = require('../lib/notifications')(env, ctx); @@ -29,8 +33,12 @@ describe('ar2', function ( ) { ctx.data.sgvs = [{y: 150, x: before}, {y: 170, x: now}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); + delta.setProperties(sbx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, HIGH BG predicted'); + highest.message.should.startWith('BG Now: 170 +20 mg/dl'); done(); }); @@ -41,7 +49,9 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.URGENT); + highest.title.should.equal('Urgent, HIGH BG'); done(); }); @@ -52,18 +62,63 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG'); + + done(); + }); + + it('should trigger a warning when almost below target', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{y: 90, x: before}, {y: 83, x: now}]; + + var sbx = require('../lib/sandbox')().serverInit(env, ctx); + ar2.checkNotifications(sbx); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG predicted'); done(); }); - it('should trigger a urgent alarm when low fast', function (done) { + it('should trigger a urgent alarm when falling fast', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{y: 120, x: before}, {y: 80, x: now}]; + ctx.data.sgvs = [{y: 120, x: before}, {y: 85, x: now}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); ar2.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.URGENT); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.URGENT); + highest.title.should.equal('Urgent, LOW BG predicted'); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, LOW BG predicted w/raw'); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is rising really fast, but sgv is steady', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 183680, filtered: 111232, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.title.should.equal('Warning, HIGH BG predicted w/raw'); done(); }); diff --git a/tests/simplealarms.test.js b/tests/simplealarms.test.js index 1aac1ea2043..95b82c465ad 100644 --- a/tests/simplealarms.test.js +++ b/tests/simplealarms.test.js @@ -3,6 +3,7 @@ var should = require('should'); describe('simplealarms', function ( ) { var simplealarms = require('../lib/plugins/simplealarms')(); + var delta = require('../lib/plugins/delta')(); var env = require('../env')(); var ctx = {}; @@ -10,6 +11,7 @@ describe('simplealarms', function ( ) { ctx.notifications = require('../lib/notifications')(env, ctx); var now = Date.now(); + var before = now - (5 * 60 * 1000); it('Not trigger an alarm when in range', function (done) { @@ -25,12 +27,14 @@ describe('simplealarms', function ( ) { it('should trigger a warning when above target', function (done) { ctx.notifications.initRequests(); - ctx.data.sgvs = [{x: now, y: 181}]; + ctx.data.sgvs = [{x: before, y: 171}, {x: now, y: 181}]; var sbx = require('../lib/sandbox')().serverInit(env, ctx); + delta.setProperties(sbx); simplealarms.checkNotifications(sbx); - ctx.notifications.findHighestAlarm().level.should.equal(ctx.notifications.levels.WARN); - + var highest = ctx.notifications.findHighestAlarm(); + highest.level.should.equal(ctx.notifications.levels.WARN); + highest.message.should.equal('BG Now: 181 +10 mg/dl'); done(); }); From 2e0e976c64de268f0c83b27ed7a1c973250b5aef Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:48:59 -0700 Subject: [PATCH 217/661] removed some debug --- tests/cob.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/cob.test.js b/tests/cob.test.js index 64eb510a331..c674ea4ee91 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -30,10 +30,6 @@ describe('COB', function ( ) { var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); - console.info('>>>>after100:', after100); - console.info('>>>>before10:', before10); - console.info('>>>>after2nd:', after10); - after100.cob.should.equal(100); Math.round(before10.cob).should.equal(59); Math.round(after10.cob).should.equal(69); //WTF == 128 From 7a1f0edfce87d0d3b99f2a0451549324b751fa43 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 26 Jun 2015 23:49:20 -0700 Subject: [PATCH 218/661] fix ar2 test when run as full suite --- tests/ar2.test.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/ar2.test.js b/tests/ar2.test.js index c94cfe74e57..57073edefea 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -6,8 +6,6 @@ describe('ar2', function ( ) { var delta = require('../lib/plugins/delta')(); var env = require('../env')(); - var envRaw = require('../env')(); - envRaw.extendedSettings = {'ar2': {useRaw: true}}; var ctx = {}; ctx.data = require('../lib/data')(env, ctx); @@ -95,12 +93,18 @@ describe('ar2', function ( ) { done(); }); + function rawSandbox(ctx) { + var envRaw = require('../env')(); + envRaw.extendedSettings = {'ar2': {useRaw: true}}; + return require('../lib/sandbox')().serverInit(envRaw, ctx); + } + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; - var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + var sbx = rawSandbox(ctx); ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); @@ -114,7 +118,7 @@ describe('ar2', function ( ) { ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 183680, filtered: 111232, y: 100, x: now, noise: 1}]; ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; - var sbx = require('../lib/sandbox')().serverInit(envRaw, ctx); + var sbx = rawSandbox(ctx); ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); @@ -123,5 +127,4 @@ describe('ar2', function ( ) { done(); }); - }); \ No newline at end of file From f5d8340c14145244c5a2d98e6585496198ba1336 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 27 Jun 2015 10:29:38 +0200 Subject: [PATCH 219/661] Modified the Dockerfile for building the image, the rest will be in the separate repository as discussed with @bewest and @jasoncalabrese. --- Dockerfile | 16 ++++++++++++---- docker-build.sh | 4 ---- docker/docker-build.sh | 8 -------- docker/docker-compose.yml | 20 -------------------- 4 files changed, 12 insertions(+), 36 deletions(-) delete mode 100644 docker-build.sh delete mode 100755 docker/docker-build.sh delete mode 100644 docker/docker-compose.yml diff --git a/Dockerfile b/Dockerfile index 9e91a997d0d..de11c7eb878 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,24 @@ -FROM node:0.12.38-slim +FROM node:latest -MAINTAINER Nightscout +MAINTAINER fokko@driesprong.frl + +WORKDIR /opt/app +ADD . /opt/app # Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -RUN apt-get update && apt-get -y install netcat git +# RUN apt-get update && apt-get -y install netcat git + +RUN apt-get update && apt-get -y install git # Got this from the setup.sh RUN apt-get install -y python-software-properties python g++ make git +# Upgrade RUN apt-get upgrade -y +# Install using NPM RUN npm install . +# Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. EXPOSE 1337 -CMD ["sh", "docker/docker-start.sh"] \ No newline at end of file +CMD ["node", "server.js"] \ No newline at end of file diff --git a/docker-build.sh b/docker-build.sh deleted file mode 100644 index 05ed0164c72..00000000000 --- a/docker-build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -sudo docker build -t local/nightscout . -sudo docker run -t local/nightscout \ No newline at end of file diff --git a/docker/docker-build.sh b/docker/docker-build.sh deleted file mode 100755 index 216e90bf504..00000000000 --- a/docker/docker-build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -cd ../ - -docker-compose -p nightscout_build -f docker/docker-compose.yml stop -docker-compose -p nightscout_build -f docker/docker-compose.yml rm --force -v -docker-compose -p nightscout_build -f docker/docker-compose.yml build -docker-compose -p nightscout_build -f docker/docker-compose.yml up \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index 509d285ba8c..00000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -nightscout: - build: ../ - links: - - database - - broker - ports: - - "1337:1337" - environment: - - MONGO_CONNECTION=mongodb://database/nightscout - - API_SECRET=mylittlesecret - - MQTT_MONITOR=mqtt://broker - - PORT=1337 -database: - image: mongo:3.0.3 - ports: - - "27017" -broker: - image: prologic/mosquitto - ports: - - "1883" \ No newline at end of file From 8317a528ae2cdfb6d1e4bc302abf7cf037cd9824 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sat, 27 Jun 2015 15:43:55 +0200 Subject: [PATCH 220/661] Obvious mistake.. --- Dockerfile | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index de11c7eb878..abadf434d79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,18 +5,13 @@ MAINTAINER fokko@driesprong.frl WORKDIR /opt/app ADD . /opt/app -# Netcat is required to poll the database, so Nightscout starts when MongoDB is up and running -# RUN apt-get update && apt-get -y install netcat git - -RUN apt-get update && apt-get -y install git - -# Got this from the setup.sh -RUN apt-get install -y python-software-properties python g++ make git +# Installing the required packages. +RUN apt-get update && apt-get install -y python-software-properties python g++ make git # Upgrade RUN apt-get upgrade -y -# Install using NPM +# Install Nightscout using NPM RUN npm install . # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. From e5cf3cd0bd9e0a3e8632d12d9ed2794fd00d07d5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 11:11:42 -0700 Subject: [PATCH 221/661] added support for canceling pushover alarms; more refactoring and tests --- lib/api/notifications-api.js | 2 +- lib/bootevent.js | 1 + lib/plugins/ar2.js | 3 + lib/plugins/simplealarms.js | 5 -- lib/pushnotify.js | 32 +++++++--- lib/pushover.js | 36 +++++++---- package.json | 1 + tests/pushnotify.test.js | 115 +++++++++++++++++++++++++++++++++++ 8 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 tests/pushnotify.test.js diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js index 5e502bc24c4..933396db187 100644 --- a/lib/api/notifications-api.js +++ b/lib/api/notifications-api.js @@ -18,7 +18,7 @@ function configure (app, wares, ctx) { notifications.post('/notifications/pushovercallback', function (req, res) { console.info('GOT Pushover callback', req.body); - var result = ctx.pushnotify.ack(req.body); + var result = ctx.pushnotify.pushoverAck(req.body); res.redirect(302, '/result/?ok=' + result); }); diff --git a/lib/bootevent.js b/lib/bootevent.js index ffe90969661..7d508f2ea08 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,6 +17,7 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); + ctx.pushover = require('./pushover')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 15e4dc7f8dc..59bfb7cd90b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -63,8 +63,10 @@ function init() { var rangeLabel = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; + if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; + if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; } @@ -138,6 +140,7 @@ function init() { } else if (result.forecast.avgLoss > WARN_THRESHOLD) { result.trigger = true; result.level = 1; + result.pushoverSound = 'persistent'; } return result; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index fc4d61c7364..02a75d1118d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -47,11 +47,6 @@ function init() { title = 'Low warning'; pushoverSound = 'falling'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); - } else if (sbx.thresholds.bg_magic && lastSVG == sbx.thresholds.bg_magic && lastSGVEntry.direction == 'Flat') { - trigger = true; - level = o; - title = 'Perfect'; - pushoverSound = 'magic'; } var message = 'BG Now: ' + lastSGV; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6f9f23ce754..89db5fb7bb4 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -7,11 +7,6 @@ var NodeCache = require( "node-cache" ); function init(env, ctx) { - var pushover = require('./pushover')(env); - - var PUSHOVER_EMERGENCY = 2; - var PUSHOVER_NORMAL = 0; - // declare local constants for time differences var TIME_2_MINS_S = 120 , TIME_15_MINS_S = 15 * 60 @@ -27,8 +22,25 @@ function init(env, ctx) { var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { - if (!pushover) return; - if (notify.clear) return; + if (!ctx.pushover) return; + + if (notify.clear) { + console.info('got a notify clear'); + var receiptKeys = receipts.keys(); + + _.forEach(receiptKeys, function eachKey (receipt) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + if (err) { + console.error('error canceling receipt, err: ', err); + } else { + console.info('got a receipt cancel response'); + } + }); + receipts.del(receipt); + }); + + return; + } var key = null; if (notify.level >= ctx.notifications.levels.WARN) { @@ -51,7 +63,7 @@ function init(env, ctx) { , sound: notify.pushoverSound || 'gamelan' , timestamp: new Date() //USE PUSHOVER_EMERGENCY for WARN and URGENT so we get the acks - , priority: notify.level > ctx.notifications.levels.WARN ? PUSHOVER_EMERGENCY : PUSHOVER_NORMAL + , priority: notify.level >= ctx.notifications.levels.WARN ? ctx.pushover.PRIORITY_EMERGENCY : ctx.pushover.PRIORITY_NORMAL }; if (notify.level >= ctx.notifications.levels.WARN) { @@ -72,7 +84,7 @@ function init(env, ctx) { //add the key to the cache before sending, but with a short TTL recentlySent.set(key, notify, 30); - pushover.send(msg, function(err, result) { + ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { console.error('unable to send pushover notification', err); } else { @@ -91,7 +103,7 @@ function init(env, ctx) { }; - pushnotify.ack = function ack (response) { + pushnotify.pushoverAck = function pushoverAck (response) { if (!response.receipt) return false; var notify = receipts.get(response.receipt); diff --git a/lib/pushover.js b/lib/pushover.js index ce30c40cb26..8f8ba745cf7 100644 --- a/lib/pushover.js +++ b/lib/pushover.js @@ -1,19 +1,33 @@ 'use strict'; var Pushover = require('pushover-notifications'); +var request = require('request'); -function init(env) { - if (env.pushover_api_token && env.pushover_user_key) { - return new Pushover({ - token: env.pushover_api_token, - user: env.pushover_user_key, - onerror: function (err) { - console.log(err); - } +function init (env) { + var pushover = null; + + if (env.pushover_api_token && env.pushover_user_key) { + pushover = new Pushover({ + token: env.pushover_api_token, + user: env.pushover_user_key + }); + + pushover.PRIORITY_NORMAL = 0; + pushover.PRIORITY_EMERGENCY = 2; + + pushover.cancelWithReceipt = function cancelWithReceipt (receipt, callback) { + request + .get('https://api.pushover.net/1/receipts/' + receipt + '/cancel.json?token=' + env.pushover_api_token) + .on('response', function(response) { + callback(null, response); + }) + .on('error', function(err) { + callback(err); }); - } else { - return null; - } + }; + } + + return pushover; } module.exports = init; \ No newline at end of file diff --git a/package.json b/package.json index 463eca41490..da5c4c58c36 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "mqtt": "~0.3.11", "node-cache": "^3.0.0", "pushover-notifications": "0.2.0", + "request": "^2.58.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "socket.io": "^1.3.5" }, diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js new file mode 100644 index 00000000000..0f81a5a5755 --- /dev/null +++ b/tests/pushnotify.test.js @@ -0,0 +1,115 @@ +var should = require('should'); + +describe('pushnotify', function ( ) { + + it('send a pushover alarm, but only 1 time', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Warning, this is a test!' + , message: 'details details details details' + , level: ctx.notifications.levels.WARN + , pushoverSound: 'climb' + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(2); + msg.sound.should.equal('climb'); + callback(null, JSON.stringify({receipt: 'abcd12345'})); + done() + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + ctx.pushnotify.emitNotification(notify); + + //call again, but should be deduped, or fail with 'done() called multiple times' + ctx.pushnotify.emitNotification(notify); + + }); + + it('send a pushover notification, but only 1 time', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Sent from a test' + , message: 'details details details details' + , level: ctx.notifications.levels.INFO + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(0); + msg.sound.should.equal('gamelan'); + callback(null, JSON.stringify({})); + done() + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + ctx.pushnotify.emitNotification(notify); + + //call again, but should be deduped, or fail with 'done() called multiple times' + ctx.pushnotify.emitNotification(notify); + + }); + + it('send a pushover alarm, and then cancel', function (done) { + var env = require('../env')(); + var ctx = {}; + + ctx.notifications = require('../lib/notifications')(env, ctx); + + var notify = { + title: 'Warning, this is a test!' + , message: 'details details details details' + , level: ctx.notifications.levels.WARN + , pushoverSound: 'climb' + , plugin: function test () {} + }; + + ctx.pushover = { + PRIORITY_NORMAL: 0 + , PRIORITY_EMERGENCY: 2 + , send: function mockedSend (msg, callback) { + msg.title.should.equal(notify.title); + msg.priority.should.equal(2); + msg.sound.should.equal('climb'); + callback(null, JSON.stringify({receipt: 'abcd12345'})); + } + , cancelWithReceipt: function mockedCancel (receipt) { + receipt.should.equal('abcd12345'); + done(); + } + }; + + ctx.pushnotify = require('../lib/pushnotify')(env, ctx); + + //first send the warning + ctx.pushnotify.emitNotification(notify); + + //then pretend is was acked from the web + ctx.pushnotify.emitNotification({clear: true}); + + }); + + + +}); From e5a2c84b6765ad0dcd084f07f18de8b74ca6ea16 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 12:11:28 -0700 Subject: [PATCH 222/661] fix COB pill formatting; add more tests --- lib/plugins/cob.js | 2 +- tests/cob.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index e2b9c0c6648..1507ce4c96d 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -158,7 +158,7 @@ function init() { } sbx.pluginBase.updatePillText(sbx, { - value: displayCob + " g" + value: displayCob + 'g' , label: 'COB' , info: info }); diff --git a/tests/cob.test.js b/tests/cob.test.js index c674ea4ee91..60eef9db15c 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -63,5 +63,29 @@ describe('COB', function ( ) { result5.cob.should.equal(0); }); + it('set a pill to the current COB', function (done) { + + var app = {}; + var clientSettings = {}; + + var data = { + treatments: [{carbs: "8", "created_at": new Date()}] + , profile: require('../lib/profilefunctions')([profileData]) + }; + + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.value.should.equal('8g'); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + cob.setProperties(sbx); + cob.updateVisualisation(sbx); + + }); + }); \ No newline at end of file From 0dc909e36d00fef3835481f512d839a1655baaf0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 12:34:15 -0700 Subject: [PATCH 223/661] fix cob test and a bug --- lib/plugins/cob.js | 7 ++++--- tests/cob.test.js | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1507ce4c96d..8969d2e1246 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -145,10 +145,11 @@ function init() { cob.updateVisualisation = function updateVisualisation(sbx) { - var prop = sbx.properties.cob.cob; - if (prop == undefined) return; + var prop = sbx.properties.cob; - var displayCob = Math.round(prop * 10) / 10; + if (prop == undefined || prop.cob == undefined) return; + + var displayCob = Math.round(prop.cob * 10) / 10; var info = null; if (prop.lastCarbs) { diff --git a/tests/cob.test.js b/tests/cob.test.js index 60eef9db15c..c34237d0823 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -69,8 +69,11 @@ describe('COB', function ( ) { var clientSettings = {}; var data = { - treatments: [{carbs: "8", "created_at": new Date()}] - , profile: require('../lib/profilefunctions')([profileData]) + treatments: [{ + carbs: "8" + , "created_at": Date.now() - 60000 //1m ago + }] + , profile: profile }; var pluginBase = { From 6f206930bc5efbfda54f8f4afaddf51439a56826 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 14:57:13 -0700 Subject: [PATCH 224/661] added delta to the default server plugins for use in the notifications --- lib/plugins/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index f6bb629bf4e..2f45802de78 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -30,6 +30,7 @@ function init() { var serverDefaultPlugins = [ require('./rawbg')() + , require('./delta')() , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() From 665bf2a5389cc9a30dda580c274b74c11f9a3515 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 27 Jun 2015 22:35:23 -0700 Subject: [PATCH 225/661] fix typo --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 59bfb7cd90b..96f4c35ebd4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -93,7 +93,7 @@ function init() { lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } - lines.push([usingRaw ? 'Raw BG' : 'BG Now', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); + lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; if (bwp && bwp > 0) { From f805633f52df8fb1c181ace97fa0d357b153fe9b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 03:05:53 -0700 Subject: [PATCH 226/661] initial IFTTT Maker integration --- lib/bootevent.js | 1 + lib/maker.js | 51 ++++++++++++++++++++++++++++ lib/pushnotify.js | 84 +++++++++++++++++++++++++++++++---------------- 3 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 lib/maker.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 7d508f2ea08..1865077230f 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -18,6 +18,7 @@ function boot (env) { /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); ctx.pushover = require('./pushover')(env); + ctx.maker = require('./maker')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); diff --git a/lib/maker.js b/lib/maker.js new file mode 100644 index 00000000000..165df71a673 --- /dev/null +++ b/lib/maker.js @@ -0,0 +1,51 @@ +'use strict'; + +var request = require('request'); + +function init (env) { + + console.info('>>>env.extendedSettings', env.extendedSettings); + var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; + + function maker() { + return maker; + } + + maker.sendEvent = function sendEvent (event, callback) { + + if (!event || !event.name) { + callback('No event name found'); + } else { + var query = ''; + + if (event.values && event.values.length) { + _.forEach(event.values, function eachValue (value, index) { + if (query) { + query += '&'; + } else { + query += '?'; + } + query += 'value' + (index + 1) + '=\'' + encodeURIComponent(value) + '\''; + }); + } + + request + .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + query) + .on('response', function (response) { + callback(null, response); + }) + .on('error', function (err) { + callback(err); + }); + } + }; + + if (key) { + return maker(); + } else { + return null; + } + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 89db5fb7bb4..52172055200 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -22,22 +22,9 @@ function init(env, ctx) { var recentlySent = new NodeCache({ stdTTL: TIME_15_MINS_MS, checkperiod: 20 }); pushnotify.emitNotification = function emitNotification (notify) { - if (!ctx.pushover) return; - if (notify.clear) { console.info('got a notify clear'); - var receiptKeys = receipts.keys(); - - _.forEach(receiptKeys, function eachKey (receipt) { - ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { - if (err) { - console.error('error canceling receipt, err: ', err); - } else { - console.info('got a receipt cancel response'); - } - }); - receipts.del(receipt); - }); + if (ctx.pushover) cancelPushoverNotifications(); return; } @@ -51,11 +38,49 @@ function init(env, ctx) { key = notifyToHash(notify); } + notify.key = key; + if (recentlySent.get(key)) { console.info('notify: ' + key + ' has ALREADY been sent'); + return; } + recentlySent.set(key, notify, 30); + + if (ctx.pushover) sendPushoverNotifications(notify); + if (ctx.maker) sendMakerEvent(notify); + + }; + + pushnotify.pushoverAck = function pushoverAck (response) { + if (!response.receipt) return false; + + var notify = receipts.get(response.receipt); + console.info('push ack, response: ', response, ', notify: ', notify); + if (notify) { + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + } + return !!notify; + }; + + function cancelPushoverNotifications ( ) { + var receiptKeys = receipts.keys(); + + _.forEach(receiptKeys, function eachKey (receipt) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + if (err) { + console.error('error canceling receipt, err: ', err); + } else { + console.info('got a receipt cancel response'); + } + }); + receipts.del(receipt); + }); + + } + + function sendPushoverNotifications (notify, key) { var msg = { expire: TIME_15_MINS_S , title: notify.title @@ -83,7 +108,6 @@ function init(env, ctx) { // } //add the key to the cache before sending, but with a short TTL - recentlySent.set(key, notify, 30); ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { console.error('unable to send pushover notification', err); @@ -92,7 +116,7 @@ function init(env, ctx) { result = JSON.parse(result); console.info('sent pushover notification: ', msg, 'result: ', result); //after successfully sent, increase the TTL - recentlySent.ttl(key, TIME_15_MINS_S); + recentlySent.ttl(notify.key, TIME_15_MINS_S); if (result.receipt) { //if this was an emergency alarm, also hold on to the receipt/notify mapping, for later acking @@ -100,21 +124,23 @@ function init(env, ctx) { } } }); + } - }; - - pushnotify.pushoverAck = function pushoverAck (response) { - if (!response.receipt) return false; - - var notify = receipts.get(response.receipt); - console.info('push ack, response: ', response, ', notify: ', notify); - if (notify) { - ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) - } - return !!notify; - }; + function sendMakerEvent (notify) { + ctx.maker.sendEvent({ + name: notify.makerEvent || notify.plugin.name + , values: notify.makerValues + }, function makerCallback (err, result) { + if (err) { + console.error('unable to send maker event', err, notify); + } else { + console.info('sent maker event: ', notify); + recentlySent.ttl(notify.key, TIME_15_MINS_S); + } + }) + } - function notifyToHash(notify) { + function notifyToHash (notify) { var hash = crypto.createHash('sha1'); var info = JSON.stringify(_.pick(notify, ['title', 'message'])); hash.update(info); From 80e3c2be2a34d9361f848ae180354a0da74e0dc7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 03:12:36 -0700 Subject: [PATCH 227/661] remove debug --- lib/maker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/maker.js b/lib/maker.js index 165df71a673..11d20f8b5c8 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -4,7 +4,6 @@ var request = require('request'); function init (env) { - console.info('>>>env.extendedSettings', env.extendedSettings); var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; function maker() { From 1b9cb01fe679f8340b100a326b54cecec2cc8316 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Sun, 28 Jun 2015 13:25:21 +0200 Subject: [PATCH 228/661] Bower did not kick in because of permission-issue, the Docker-build now switches to a separate user for the node build instead of root. --- Dockerfile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index abadf434d79..847f2391b2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,24 @@ FROM node:latest MAINTAINER fokko@driesprong.frl -WORKDIR /opt/app -ADD . /opt/app - # Installing the required packages. RUN apt-get update && apt-get install -y python-software-properties python g++ make git # Upgrade RUN apt-get upgrade -y -# Install Nightscout using NPM +# https://github.com/jspm/jspm-cli/issues/865 +ENV user node + +RUN groupadd --system $user && useradd --system --create-home --gid $user $user + +COPY . /home/$user/ +WORKDIR /home/$user + +# We don't wat to run in root. +RUN chown $user --recursive . +USER $user + RUN npm install . # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. From c931d03a57a3c3accdee5e1d957e5fb8e4083945 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 12:45:58 -0700 Subject: [PATCH 229/661] added more maker event names and tests --- lib/maker.js | 79 +++++++++++++++++++++++-------- lib/plugins/ar2.js | 4 ++ lib/plugins/boluswizardpreview.js | 2 + lib/plugins/errorcodes.js | 4 ++ lib/plugins/simplealarms.js | 7 +++ lib/pushnotify.js | 29 ++++++++---- tests/maker.test.js | 35 ++++++++++++++ 7 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 tests/maker.test.js diff --git a/lib/maker.js b/lib/maker.js index 11d20f8b5c8..901575fa567 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,44 +1,81 @@ 'use strict'; +var _ = require('lodash'); var request = require('request'); function init (env) { var key = env.extendedSettings && env.extendedSettings.maker && env.extendedSettings.maker.key; + var TIME_30_MINS_MS = 30 * 60 * 1000; + function maker() { return maker; } - maker.sendEvent = function sendEvent (event, callback) { + var lastAllClear = 0; + + maker.sendAllClear = function sendAllClear (callback) { + if (Date.now() - lastAllClear > TIME_30_MINS_MS) { + lastAllClear = Date.now(); + maker.makeRequest('ns-allclear', function allClearCallback (err) { + if (err) { + lastAllClear = 0; + callback(err); + } else { + callback && callback(null, {sent: true}); + } + }); + } else { + callback && callback(null, {sent: false}); + } + }; + maker.sendEvent = function sendEvent (event, callback) { if (!event || !event.name) { callback('No event name found'); } else { - var query = ''; - - if (event.values && event.values.length) { - _.forEach(event.values, function eachValue (value, index) { - if (query) { - query += '&'; - } else { - query += '?'; - } - query += 'value' + (index + 1) + '=\'' + encodeURIComponent(value) + '\''; - }); - } - - request - .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + query) - .on('response', function (response) { - callback(null, response); - }) - .on('error', function (err) { + maker.makeRequest(event, function sendCallback (err, response) { + if (err) { callback(err); - }); + } else { + lastAllClear = 0; + callback && callback(null, response); + } + }); } }; + //exposed for testing + maker.valuesToQuery = function valuesToQuery (values) { + var query = ''; + + if (values && values.length) { + lastAllClear = 0; + _.forEach(values, function eachValue (value, index) { + if (query) { + query += '&'; + } else { + query += '?'; + } + query += 'value' + (index + 1) + '=' + encodeURIComponent(value); + }); + } + + return query; + }; + + maker.makeRequest = function makeRequest(event, callback) { + request + .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + maker.valuesToQuery(event.values)) + .on('response', function (response) { + callback && callback(null, response); + }) + .on('error', function (err) { + callback && callback(err); + }); + }; + if (key) { return maker(); } else { diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 96f4c35ebd4..58c53a4e454 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -61,11 +61,14 @@ function init() { var min = _.min([first, last, avg]); var rangeLabel = ''; + var eventName = 'ns-' + sbx.notifications.levels.toString(result.level).toLowerCase(); if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; + eventName += '-high'; if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; + eventName += '-low'; if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; @@ -116,6 +119,7 @@ function init() { level: result.level , title: title , message: message + , eventName: eventName , pushoverSound: result.pushoverSound , plugin: ar2 , debug: { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index c575afce812..c2c464ca766 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -77,6 +77,7 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; + var eventName = 'ns-bwp-' + levelLabel.toLowerCase(); var lines = ['BG Now: ' + results.lastSGV]; @@ -109,6 +110,7 @@ function init() { level: level , title: levelLabel + ', Check BG, time to bolus?' , message: message + , eventName: eventName , pushoverSound: sound , plugin: bwp , debug: results diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 2462dd2a5de..2b0d3a1ee5e 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -22,6 +22,7 @@ function init() { var errorDisplay; var pushoverSound = null; var notifyLevel = sbx.notifications.levels.LOW; + var eventName = 'ns-cgm-errorcode'; switch (parseInt(lastSGV.y)) { case 0: //None @@ -52,11 +53,13 @@ function init() { errorDisplay = '?AD'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; + eventName += '-hourglass'; break; case 10: //POWER_DEVIATION errorDisplay = '???'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; + eventName += '-???'; break; case 12: //BAD_RF errorDisplay = '?RF'; @@ -72,6 +75,7 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay + , eventName: eventName , plugin: errorcodes , pushoverSound: pushoverSound , debug: { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 02a75d1118d..ccbc993cea1 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -22,30 +22,36 @@ function init() { , pushoverSound = null ; + var eventName = ''; + if (lastSGV && lastSGVEntry && lastSGVEntry.y > 39 && Date.now() - lastSGVEntry.x < TIME_10_MINS_MS) { if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_high)) { trigger = true; level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; + eventName = 'ns-urgent-high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; + eventName = 'ns-warning-high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; + eventName = 'ns-urgent-low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; + eventName = 'ns-warning-low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } @@ -62,6 +68,7 @@ function init() { level: level , title: title , message: message + , eventName: eventName , plugin: simplealarms , pushoverSound: pushoverSound , debug: { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 52172055200..37724069b43 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -25,6 +25,7 @@ function init(env, ctx) { if (notify.clear) { console.info('got a notify clear'); if (ctx.pushover) cancelPushoverNotifications(); + if (ctx.maker) sendMakerAllClear(); return; } @@ -77,10 +78,9 @@ function init(env, ctx) { }); receipts.del(receipt); }); - } - function sendPushoverNotifications (notify, key) { + function sendPushoverNotifications (notify) { var msg = { expire: TIME_15_MINS_S , title: notify.title @@ -126,18 +126,29 @@ function init(env, ctx) { }); } + function sendMakerAllClear ( ) { + ctx.maker.sendAllClear(function makerCallback (err, result) { + if (err) { + console.error('unable to send maker allclear', err); + } else if (result && result.sent) { + console.info('sent maker allclear'); + } + }); + } + function sendMakerEvent (notify) { - ctx.maker.sendEvent({ - name: notify.makerEvent || notify.plugin.name - , values: notify.makerValues - }, function makerCallback (err, result) { + var event = { + name: notify.eventName || 'ns-' + notify.plugin.name + , values: [notify.title, notify.message] + }; + ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { - console.error('unable to send maker event', err, notify); + console.error('unable to send maker event', err, event); } else { - console.info('sent maker event: ', notify); + console.info('sent maker event: ', event); recentlySent.ttl(notify.key, TIME_15_MINS_S); } - }) + }); } function notifyToHash (notify) { diff --git a/tests/maker.test.js b/tests/maker.test.js new file mode 100644 index 00000000000..9fd1d310908 --- /dev/null +++ b/tests/maker.test.js @@ -0,0 +1,35 @@ +var should = require('should'); + +describe('maker', function ( ) { + var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); + + //prevent any calls to iftt + maker.makeRequest = function noOpMakeRequest (event, callback) {}; + + it('turn values to a query', function (done) { + maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); + done(); + }); + + it('send a request', function (done) { + maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.sendEvent({name: 'test'}, function sendCallback (err) { + should.not.exist(err); + }); + }); + + it('send a allclear, but only once', function (done) { + maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.sendAllClear(function sendCallback (err, result) { + should.not.exist(err); + result.sent.should.equal(true); + }); + + //send again, if done is called again test will fail + maker.sendAllClear(function sendCallback (err, result) { + should.not.exist(err); + result.sent.should.equal(false); + }); + }); + +}); From 7d02d7a21e0bc6b99b68ef89ae2db050a1a261d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 12:56:02 -0700 Subject: [PATCH 230/661] a little better maker tests --- tests/maker.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/maker.test.js b/tests/maker.test.js index 9fd1d310908..1b0605f8b13 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,7 +4,7 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, callback) {}; + maker.makeRequest = function noOpMakeRequest (event, callback) { callback && callback()}; it('turn values to a query', function (done) { maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); @@ -12,14 +12,14 @@ describe('maker', function ( ) { }); it('send a request', function (done) { - maker.makeRequest = function mockedMakeRequest ( ) { done(); }; maker.sendEvent({name: 'test'}, function sendCallback (err) { should.not.exist(err); + done(); }); }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedMakeRequest ( ) { done(); }; + maker.makeRequest = function mockedToTestSingleDone (event, callback) { callback(); done(); }; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); From 6999b87c2491c11aa2c3f52c0b1e6b26097d2833 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 14:59:16 -0700 Subject: [PATCH 231/661] send snooze link to maker; add new line before message --- lib/pushnotify.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 37724069b43..77f9cddc3a5 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -99,14 +99,6 @@ function init(env, ctx) { } } - // if we want to have a callback snooze url this is the way, but emergency ack work better - // var now = Date.now(); - // var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - // if (sig) { - // msg.url_title = 'Snooze for 30 minutes'; - // msg.url = env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - // } - //add the key to the cache before sending, but with a short TTL ctx.pushover.send(msg, function pushoverCallback (err, result) { if (err) { @@ -137,9 +129,24 @@ function init(env, ctx) { } function sendMakerEvent (notify) { + var snoozeLink = ''; + if (notify.level < ctx.notifications.levels.WARN) { + //add the key to the cache before sending, but with a short TTL + var now = Date.now(); + var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); + if (sig) { + snoozeLink = 'Snooze for 30 minutes: ' + + env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; + } + } var event = { name: notify.eventName || 'ns-' + notify.plugin.name - , values: [notify.title, notify.message] + , values: [ + notify.title + , notify.message && '\n' + notify.message + , snoozeLink + ] }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { From b7bc3a35774a8bd1cff90e9f269e1c66e91f7dca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 28 Jun 2015 18:35:04 -0700 Subject: [PATCH 232/661] no more newline hack --- lib/pushnotify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 77f9cddc3a5..6a3a485887a 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -144,7 +144,7 @@ function init(env, ctx) { name: notify.eventName || 'ns-' + notify.plugin.name , values: [ notify.title - , notify.message && '\n' + notify.message + , notify.message , snoozeLink ] }; From f6acad50d4ba4f6d2383b4becb8e9167c5f738ae Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:26:39 -0700 Subject: [PATCH 233/661] send multiple events so we can create recipes at the right level; fix snooze url level bug; message formatting --- lib/maker.js | 43 +++++++++++++++++++++++-------- lib/notifications.js | 4 +++ lib/plugins/ar2.js | 8 +++--- lib/plugins/boluswizardpreview.js | 6 ++--- lib/plugins/errorcodes.js | 6 +---- lib/plugins/simplealarms.js | 11 ++++---- lib/pushnotify.js | 13 +++++----- lib/sandbox.js | 11 ++++++++ tests/maker.test.js | 9 ++++--- 9 files changed, 73 insertions(+), 38 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 901575fa567..44734ba37ef 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var async = require('async'); var request = require('request'); function init (env) { @@ -18,7 +19,7 @@ function init (env) { maker.sendAllClear = function sendAllClear (callback) { if (Date.now() - lastAllClear > TIME_30_MINS_MS) { lastAllClear = Date.now(); - maker.makeRequest('ns-allclear', function allClearCallback (err) { + maker.makeRequest({}, 'allclear', function allClearCallback (err) { if (err) { lastAllClear = 0; callback(err); @@ -35,7 +36,7 @@ function init (env) { if (!event || !event.name) { callback('No event name found'); } else { - maker.makeRequest(event, function sendCallback (err, response) { + maker.makeRequests(event, function sendCallback (err, response) { if (err) { callback(err); } else { @@ -47,35 +48,55 @@ function init (env) { }; //exposed for testing - maker.valuesToQuery = function valuesToQuery (values) { + maker.valuesToQuery = function valuesToQuery (event) { var query = ''; - if (values && values.length) { + for (var i = 1; i <= 3; i++) { + var name = 'value' + i; + var value = event[name]; lastAllClear = 0; - _.forEach(values, function eachValue (value, index) { + if (value) { if (query) { query += '&'; } else { query += '?'; } - query += 'value' + (index + 1) + '=' + encodeURIComponent(value); - }); + query += name + '=' + encodeURIComponent(value); + } } return query; }; - maker.makeRequest = function makeRequest(event, callback) { + maker.makeRequest = function makeRequest(event, eventName, callback) { + var url = 'https://maker.ifttt.com/trigger/' + eventName + '/with/key/' + key + maker.valuesToQuery(event); request - .get('https://maker.ifttt.com/trigger/' + event.name + '/with/key/' + key + maker.valuesToQuery(event.values)) + .get(url) .on('response', function (response) { - callback && callback(null, response); + callback(null, response); }) .on('error', function (err) { - callback && callback(err); + callback(err); }); }; + maker.makeRequests = function makeRequests(event, callback) { + function sendGeneric (callback) { + maker.makeRequest(event, 'ns-event', callback); + } + + function sendByLevel (callback) { + maker.makeRequest (event, 'ns-' + event.level, callback); + } + + function sendByLevelAndName (callback) { + maker.makeRequest(event, 'ns' + ((event.level && '-' + event.level) || '') + '-' + event.name, callback); + } + + //since maker events only filter on name, we are sending multiple events and different levels of granularity + async.series([sendGeneric, sendByLevel, sendByLevelAndName], callback); + }; + if (key) { return maker(); } else { diff --git a/lib/notifications.js b/lib/notifications.js index 4ac61fe82a5..6645343ceeb 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -46,6 +46,10 @@ function init (env, ctx) { } }; + notifications.levels.toLowerCase = function toLowerCase(level) { + return notifications.levels.toString(level).toLowerCase(); + }; + function getAlarm (level) { var alarm = alarms[level]; if (!alarm) { diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 58c53a4e454..2df97ff1cae 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -61,14 +61,14 @@ function init() { var min = _.min([first, last, avg]); var rangeLabel = ''; - var eventName = 'ns-' + sbx.notifications.levels.toString(result.level).toLowerCase(); + var eventName = ''; if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; - eventName += '-high'; + eventName = 'high'; if (!result.pushoverSound) result.pushoverSound = 'climb'; } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; - eventName += '-low'; + eventName = 'low'; if (!result.pushoverSound) result.pushoverSound = 'falling'; } else { rangeLabel = 'Check BG'; @@ -83,7 +83,7 @@ function init() { title += ' w/raw'; } - var lines = ['BG Now: ' + sbx.scaleBg(sbx.data.lastSGV())]; + var lines = ['BG Now: ' + sbx.displayBg(sbx.data.lastSGV())]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index c2c464ca766..9d3210a4598 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -77,9 +77,8 @@ function init() { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var eventName = 'ns-bwp-' + levelLabel.toLowerCase(); - var lines = ['BG Now: ' + results.lastSGV]; + var lines = ['BG Now: ' + results.displaySGV]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { @@ -110,7 +109,7 @@ function init() { level: level , title: levelLabel + ', Check BG, time to bolus?' , message: message - , eventName: eventName + , eventName: 'bwp' , pushoverSound: sound , plugin: bwp , debug: results @@ -164,6 +163,7 @@ function init() { var sgv = sbx.scaleBg(sbx.data.lastSGV()); results.lastSGV = sgv; + results.displaySGV = sbx.displayBg(sbx.data.lastSGV()); if (!hasRequiredInfo(sbx)) { return results; diff --git a/lib/plugins/errorcodes.js b/lib/plugins/errorcodes.js index 2b0d3a1ee5e..8af645e9232 100644 --- a/lib/plugins/errorcodes.js +++ b/lib/plugins/errorcodes.js @@ -17,12 +17,11 @@ function init() { var now = Date.now(); var lastSGV = _.last(sbx.data.sgvs); - if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 40) { + if (lastSGV && now - lastSGV.x < TIME_10_MINS_MS && lastSGV.y < 39) { var errorDisplay; var pushoverSound = null; var notifyLevel = sbx.notifications.levels.LOW; - var eventName = 'ns-cgm-errorcode'; switch (parseInt(lastSGV.y)) { case 0: //None @@ -53,13 +52,11 @@ function init() { errorDisplay = '?AD'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; - eventName += '-hourglass'; break; case 10: //POWER_DEVIATION errorDisplay = '???'; pushoverSound = 'alien'; notifyLevel = sbx.notifications.levels.URGENT; - eventName += '-???'; break; case 12: //BAD_RF errorDisplay = '?RF'; @@ -75,7 +72,6 @@ function init() { level: notifyLevel , title: 'CGM Error Code' , message: errorDisplay - , eventName: eventName , plugin: errorcodes , pushoverSound: pushoverSound , debug: { diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index ccbc993cea1..38e5fb4542f 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -15,6 +15,7 @@ function init() { simplealarms.checkNotifications = function checkNotifications(sbx) { var lastSGV = sbx.scaleBg(sbx.data.lastSGV()) + , displaySGV = sbx.displayBg(sbx.data.lastSGV()) , lastSGVEntry = _.last(sbx.data.sgvs) , trigger = false , level = 0 @@ -30,32 +31,32 @@ function init() { level = 2; title = 'Urgent HIGH'; pushoverSound = 'persistent'; - eventName = 'ns-urgent-high'; + eventName = 'high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_high))); } else if (lastSGV > sbx.scaleBg(sbx.thresholds.bg_target_top)) { trigger = true; level = 1; title = 'High warning'; pushoverSound = 'climb'; - eventName = 'ns-warning-high'; + eventName = 'high'; console.info(title + ': ' + (lastSGV + ' > ' + sbx.scaleBg(sbx.thresholds.bg_target_top))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_low)) { trigger = true; level = 2; title = 'Urgent LOW'; pushoverSound = 'persistent'; - eventName = 'ns-urgent-low'; + eventName = 'low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_low))); } else if (lastSGV < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { trigger = true; level = 1; title = 'Low warning'; pushoverSound = 'falling'; - eventName = 'ns-warning-low'; + eventName = 'low'; console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var message = 'BG Now: ' + lastSGV; + var message = 'BG Now: ' + displaySGV; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6a3a485887a..456137dd6a0 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -130,7 +130,7 @@ function init(env, ctx) { function sendMakerEvent (notify) { var snoozeLink = ''; - if (notify.level < ctx.notifications.levels.WARN) { + if (notify.level >= ctx.notifications.levels.WARN) { //add the key to the cache before sending, but with a short TTL var now = Date.now(); var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); @@ -141,12 +141,11 @@ function init(env, ctx) { } } var event = { - name: notify.eventName || 'ns-' + notify.plugin.name - , values: [ - notify.title - , notify.message - , snoozeLink - ] + name: notify.eventName || notify.plugin.name + , level: ctx.notifications.levels.toLowerCase(notify.level) + , value1: notify.title + , value2: notify.message && '\n' + notify.message + , value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { diff --git a/lib/sandbox.js b/lib/sandbox.js index edd1577cd75..141794addbe 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -11,6 +11,7 @@ function init ( ) { function reset () { sbx.properties = []; sbx.scaleBg = scaleBg; + sbx.displayBg = displayBg; sbx.roundInsulinForDisplayFormat = roundInsulinForDisplayFormat; sbx.roundBGToDisplayFormat = roundBGToDisplayFormat; } @@ -119,6 +120,16 @@ function init ( ) { } }; + function displayBg (bg) { + if (Number(bg) == 39) { + return 'LOW'; + } else if (Number(bg) == 401) { + return 'HIGH'; + } else { + return scaleBg(bg); + } + } + function scaleBg (bg) { if (sbx.units == 'mmol' && bg) { return Number(units.mgdlToMMOL(bg)); diff --git a/tests/maker.test.js b/tests/maker.test.js index 1b0605f8b13..f4088e902a4 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,10 +4,13 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, callback) { callback && callback()}; + maker.makeRequest = function noOpMakeRequest (event, eventName, callback) { callback && callback()}; it('turn values to a query', function (done) { - maker.valuesToQuery(['This is a title', 'This is the message']).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); + maker.valuesToQuery({ + value1: 'This is a title' + , value2: 'This is the message' + }).should.equal('?value1=This%20is%20a%20title&value2=This%20is%20the%20message'); done(); }); @@ -19,7 +22,7 @@ describe('maker', function ( ) { }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedToTestSingleDone (event, callback) { callback(); done(); }; + maker.makeRequest = function mockedToTestSingleDone (event, eventName, callback) { callback(); done(); }; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); From eb542d1a6de81c73fa70c77974f05f9068da53e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:43:56 -0700 Subject: [PATCH 234/661] fix snoozelink formatting; added more properties to simplealarms --- lib/plugins/ar2.js | 1 - lib/plugins/simplealarms.js | 28 +++++++++++++++++++++++++--- lib/pushnotify.js | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 2df97ff1cae..dae234d15ce 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -75,7 +75,6 @@ function init() { } var title = sbx.notifications.levels.toString(result.level) + ', ' + rangeLabel; - title += ' BG'; if (lastSGVEntry.y > sbx.thresholds.bg_target_bottom && lastSGVEntry.y < sbx.thresholds.bg_target_top) { title += ' predicted'; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 38e5fb4542f..7ce39c72d79 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -56,13 +56,35 @@ function init() { console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var message = 'BG Now: ' + displaySGV; + var lines = ['BG Now: ' + displaySGV]; var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { - message += ' ' + delta; + lines[0] += ' ' + delta; } - message += ' ' + sbx.unitsLabel; + lines[0] += ' ' + sbx.unitsLabel; + + var rawbgProp = sbx.properties.rawbg; + if (rawbgProp) { + lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + } + + var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; + if (bwp && bwp > 0) { + lines.push(['BWP:', bwp, 'U'].join(' ')); + } + + var iob = sbx.properties.iob && sbx.properties.iob.display; + if (iob) { + lines.push(['IOB:', iob, 'U'].join(' ')); + } + + var cob = sbx.properties.cob && sbx.properties.cob.display; + if (cob) { + lines.push(['COB:', cob, 'g'].join(' ')); + } + + var message = lines.join('\n'); if (trigger) { sbx.notifications.requestNotify({ diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 456137dd6a0..734321b839d 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -135,7 +135,7 @@ function init(env, ctx) { var now = Date.now(); var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); if (sig) { - snoozeLink = 'Snooze for 30 minutes: ' + + snoozeLink = '\nSnooze(30m): ' + env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; } From f173f316d09d893622c3e61e56fab2a272f503e9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 00:45:52 -0700 Subject: [PATCH 235/661] and fix the ar2 test to expect simplified formatting --- tests/ar2.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ar2.test.js b/tests/ar2.test.js index 57073edefea..f560e5937b4 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -35,7 +35,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, HIGH BG predicted'); + highest.title.should.equal('Warning, HIGH predicted'); highest.message.should.startWith('BG Now: 170 +20 mg/dl'); done(); @@ -49,7 +49,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.URGENT); - highest.title.should.equal('Urgent, HIGH BG'); + highest.title.should.equal('Urgent, HIGH'); done(); }); @@ -62,7 +62,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG'); + highest.title.should.equal('Warning, LOW'); done(); }); @@ -75,7 +75,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG predicted'); + highest.title.should.equal('Warning, LOW predicted'); done(); }); @@ -88,7 +88,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.URGENT); - highest.title.should.equal('Urgent, LOW BG predicted'); + highest.title.should.equal('Urgent, LOW predicted'); done(); }); @@ -108,7 +108,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, LOW BG predicted w/raw'); + highest.title.should.equal('Warning, LOW predicted w/raw'); done(); }); @@ -122,7 +122,7 @@ describe('ar2', function ( ) { ar2.checkNotifications(sbx.withExtendedSettings(ar2)); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); - highest.title.should.equal('Warning, HIGH BG predicted w/raw'); + highest.title.should.equal('Warning, HIGH predicted w/raw'); done(); }); From f8b86de8268f568d09074f1c5df9846eb56b58de Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 29 Jun 2015 10:10:04 +0200 Subject: [PATCH 236/661] Updated the permissions, still running as non-root --- Dockerfile | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 847f2391b2d..328e21dddbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,19 +8,23 @@ RUN apt-get update && apt-get install -y python-software-properties python g++ m # Upgrade RUN apt-get upgrade -y +# We need to change user for security. # https://github.com/jspm/jspm-cli/issues/865 -ENV user node - -RUN groupadd --system $user && useradd --system --create-home --gid $user $user - -COPY . /home/$user/ -WORKDIR /home/$user - -# We don't wat to run in root. -RUN chown $user --recursive . -USER $user - -RUN npm install . +# http://stackoverflow.com/questions/24308760/running-app-inside-docker-as-non-root-user + +RUN useradd -ms /bin/bash node +# copy the nice dotfiles that dockerfile/ubuntu gives us: +RUN cd && cp -R .bashrc .profile /home/node + +ADD . /home/node/app +RUN chown -R node:node /home/node + +USER node +ENV HOME /home/node + +WORKDIR /home/node/app + +RUN npm install # Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. EXPOSE 1337 From aea42ea8a133e4c4b077c2da0931172832cdefe2 Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Mon, 29 Jun 2015 15:54:49 +0200 Subject: [PATCH 237/661] ok --- docker-build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-build.sh b/docker-build.sh index 05ed0164c72..8e5675e49f3 100644 --- a/docker-build.sh +++ b/docker-build.sh @@ -1,4 +1,4 @@ #!/bin/sh -sudo docker build -t local/nightscout . -sudo docker run -t local/nightscout \ No newline at end of file +sudo docker build -t nightscout . +sudo docker run local/nightscout \ No newline at end of file From 21f1edcd7c72601fe0b23d818343de2ac66d6c5a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 12:36:48 -0700 Subject: [PATCH 238/661] snooze links seem to be getting called, maybe by maker or twitter, need to do that a different way --- lib/pushnotify.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 734321b839d..19001388cba 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -131,21 +131,21 @@ function init(env, ctx) { function sendMakerEvent (notify) { var snoozeLink = ''; if (notify.level >= ctx.notifications.levels.WARN) { - //add the key to the cache before sending, but with a short TTL - var now = Date.now(); - var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); - if (sig) { - snoozeLink = '\nSnooze(30m): ' + - env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + - TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; - } +// var now = Date.now(); +// var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); +// if (sig) { +// snoozeLink = '\nSnooze(30m): ' + +// env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + +// TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; +// } } var event = { name: notify.eventName || notify.plugin.name , level: ctx.notifications.levels.toLowerCase(notify.level) , value1: notify.title , value2: notify.message && '\n' + notify.message - , value3: snoozeLink + //snooze links seem to be getting called + //, value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { From 7ba4e6113ce41ea32f9e71675d8b2e99faa207b7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 16:08:54 -0700 Subject: [PATCH 239/661] rip out notifications snooze api; too dangerous without full auth and new interaction in place --- lib/api/notifications-api.js | 22 ++++--------- lib/notifications.js | 27 --------------- lib/pushnotify.js | 12 ------- static/result/index.html | 64 ------------------------------------ 4 files changed, 7 insertions(+), 118 deletions(-) delete mode 100644 static/result/index.html diff --git a/lib/api/notifications-api.js b/lib/api/notifications-api.js index 933396db187..21f158d007e 100644 --- a/lib/api/notifications-api.js +++ b/lib/api/notifications-api.js @@ -1,25 +1,17 @@ 'use strict'; function configure (app, wares, ctx) { - var express = require('express'), - notifications = express.Router( ) + var express = require('express') + , notifications = express.Router( ) ; - notifications.get('/notifications/snooze', function (req, res) { - console.info('GOT web notification snooze', req.query); - var result = ctx.notifications.secureAck( - req.query.level - , req.query.lengthMills - , req.query.t - , req.query.sig - ); - res.redirect(302, '/result/?ok=' + result); - }); - notifications.post('/notifications/pushovercallback', function (req, res) { console.info('GOT Pushover callback', req.body); - var result = ctx.pushnotify.pushoverAck(req.body); - res.redirect(302, '/result/?ok=' + result); + if (ctx.pushnotify.pushoverAck(req.body)) { + res.sendStatus(200); + } else { + res.sendStatus(500); + } }); return notifications; diff --git a/lib/notifications.js b/lib/notifications.js index 6645343ceeb..66ab3d63721 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -187,33 +187,6 @@ function init (env, ctx) { }; - notifications.secureAck = function secureAck (level, lenghtMills, t, sig) { - var expected = notifications.sign(level, lenghtMills, t); - - if (expected && expected == sig) { - notifications.ack(level, lenghtMills, true); - return true - } else { - console.info(expected, ' != ', sig); - return false; - } - }; - - notifications.sign = function sign (level, lenghtMills, t) { - - if (env.api_secret) { - var shasum = crypto.createHash('sha1'); - shasum.update(env.api_secret); - shasum.update(level.toString()); - shasum.update(lenghtMills.toString()); - shasum.update(t.toString()); - return shasum.digest('hex'); - } else { - return false; - } - - }; - return notifications(); } diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 19001388cba..fd502cc2fe2 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -129,23 +129,11 @@ function init(env, ctx) { } function sendMakerEvent (notify) { - var snoozeLink = ''; - if (notify.level >= ctx.notifications.levels.WARN) { -// var now = Date.now(); -// var sig = ctx.notifications.sign(1, TIME_30_MINS_MS, Date.now()); -// if (sig) { -// snoozeLink = '\nSnooze(30m): ' + -// env.baseUrl + '/api/v1/notifications/snooze?level=1&lengthMills=' + -// TIME_30_MINS_MS + '&t=' + now + '&sig=' + sig; -// } - } var event = { name: notify.eventName || notify.plugin.name , level: ctx.notifications.levels.toLowerCase(notify.level) , value1: notify.title , value2: notify.message && '\n' + notify.message - //snooze links seem to be getting called - //, value3: snoozeLink }; ctx.maker.sendEvent(event, function makerCallback (err) { if (err) { diff --git a/static/result/index.html b/static/result/index.html deleted file mode 100644 index b0f5969aca0..00000000000 --- a/static/result/index.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Notifications - - - - - - - - - - -

    Success

    - -

    - You can close this page this page now. -

    - - - - - - - - - - From ad8a5519e4ccd36ddf356cec7a31b15b5e0d38e5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 17:35:52 -0700 Subject: [PATCH 240/661] fix some issues reported by codacy --- lib/maker.js | 19 +++++++++---------- lib/notifications.js | 2 +- lib/pushnotify.js | 8 ++++---- lib/sandbox.js | 4 ++-- tests/maker.test.js | 6 +++++- tests/sandbox.test.js | 14 ++++++++++++++ 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 44734ba37ef..33b8ecc125f 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var async = require('async'); var request = require('request'); @@ -23,25 +22,25 @@ function init (env) { if (err) { lastAllClear = 0; callback(err); - } else { - callback && callback(null, {sent: true}); + } else if (callback) { + callback(null, {sent: true}); } }); - } else { - callback && callback(null, {sent: false}); + } else if (callback) { + callback(null, {sent: false}); } }; maker.sendEvent = function sendEvent (event, callback) { if (!event || !event.name) { - callback('No event name found'); + if (callback) { callback('No event name found'); } } else { maker.makeRequests(event, function sendCallback (err, response) { if (err) { - callback(err); + if (callback) { callback(err); } } else { lastAllClear = 0; - callback && callback(null, response); + if (callback) { callback(null, response); } } }); } @@ -73,10 +72,10 @@ function init (env) { request .get(url) .on('response', function (response) { - callback(null, response); + if (callback) { callback(null, response); } }) .on('error', function (err) { - callback(err); + if (callback) { callback(err); } }); }; diff --git a/lib/notifications.js b/lib/notifications.js index 66ab3d63721..af61bf5d6b8 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var crypto = require('crypto'); var THIRTY_MINUTES = 30 * 60 * 1000; @@ -58,6 +57,7 @@ function init (env, ctx) { } return alarm; } + //should only be used when auto acking the alarms after going back in range or when an error corrects //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes //since this wasn't ack'd by a user action diff --git a/lib/pushnotify.js b/lib/pushnotify.js index fd502cc2fe2..1b76c475f07 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -24,8 +24,8 @@ function init(env, ctx) { pushnotify.emitNotification = function emitNotification (notify) { if (notify.clear) { console.info('got a notify clear'); - if (ctx.pushover) cancelPushoverNotifications(); - if (ctx.maker) sendMakerAllClear(); + if (ctx.pushover) { cancelPushoverNotifications(); } + if (ctx.maker) { sendMakerAllClear(); } return; } @@ -49,8 +49,8 @@ function init(env, ctx) { recentlySent.set(key, notify, 30); - if (ctx.pushover) sendPushoverNotifications(notify); - if (ctx.maker) sendMakerEvent(notify); + if (ctx.pushover) { sendPushoverNotifications(notify); } + if (ctx.maker) { sendMakerEvent(notify); } }; diff --git a/lib/sandbox.js b/lib/sandbox.js index 141794addbe..9e2ac851c39 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -121,9 +121,9 @@ function init ( ) { }; function displayBg (bg) { - if (Number(bg) == 39) { + if (Number(bg) === 39) { return 'LOW'; - } else if (Number(bg) == 401) { + } else if (Number(bg) === 401) { return 'HIGH'; } else { return scaleBg(bg); diff --git a/tests/maker.test.js b/tests/maker.test.js index f4088e902a4..1417a5274e5 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -22,7 +22,11 @@ describe('maker', function ( ) { }); it('send a allclear, but only once', function (done) { - maker.makeRequest = function mockedToTestSingleDone (event, eventName, callback) { callback(); done(); }; + function mockedToTestSingleDone (event, eventName, callback) { + callback(); done(); + } + + maker.makeRequest = mockedToTestSingleDone; maker.sendAllClear(function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js index 60ee9b91e93..58326a17c94 100644 --- a/tests/sandbox.test.js +++ b/tests/sandbox.test.js @@ -46,4 +46,18 @@ describe('sandbox', function ( ) { done(); }); + it('display 39 as LOW and 401 as HIGH', function () { + var env = require('../env')(); + var ctx = {}; + ctx.data = require('../lib/data')(env, ctx); + ctx.notifications = require('../lib/notifications')(env, ctx); + + var sbx = sandbox.serverInit(env, ctx); + + sbx.displayBg(39).should.equal('LOW'); + sbx.displayBg('39').should.equal('LOW'); + sbx.displayBg(401).should.equal('HIGH'); + sbx.displayBg('401').should.equal('HIGH'); + }); + }); From 6093f9ee12d0f46401d79aacb628b072711a8830 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 20:05:18 -0700 Subject: [PATCH 241/661] fixes and improvements to the all clear notification --- lib/maker.js | 13 +++++++++++-- lib/notifications.js | 10 +++++++--- lib/pushnotify.js | 8 +++----- lib/websocket.js | 2 +- server.js | 12 ++++++++++++ tests/maker.test.js | 10 +++++++--- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/lib/maker.js b/lib/maker.js index 33b8ecc125f..34e6ee6bef4 100644 --- a/lib/maker.js +++ b/lib/maker.js @@ -15,10 +15,18 @@ function init (env) { var lastAllClear = 0; - maker.sendAllClear = function sendAllClear (callback) { + maker.sendAllClear = function sendAllClear (notify, callback) { if (Date.now() - lastAllClear > TIME_30_MINS_MS) { lastAllClear = Date.now(); - maker.makeRequest({}, 'allclear', function allClearCallback (err) { + + //can be used to prevent maker/twitter deduping (add to IFTTT tweet text) + var shortTimestamp = Math.round(Date.now() / 1000 / 60); + + maker.makeRequest({ + value1: (notify && notify.title) || 'All Clear' + , value2: notify && notify.message && '\n' + notify.message + , value3: '\n' + shortTimestamp + }, 'ns-allclear', function allClearCallback (err) { if (err) { lastAllClear = 0; callback(err); @@ -72,6 +80,7 @@ function init (env) { request .get(url) .on('response', function (response) { + console.info('sent maker request: ', url); if (callback) { callback(null, response); } }) .on('error', function (err) { diff --git a/lib/notifications.js b/lib/notifications.js index af61bf5d6b8..ab0e675f06b 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -59,7 +59,7 @@ function init (env, ctx) { } //should only be used when auto acking the alarms after going back in range or when an error corrects - //setting the silence time to 1ms so the alarm will be retriggered as soon as the condition changes + //setting the silence time to 1ms so the alarm will be re-triggered as soon as the condition changes //since this wasn't ack'd by a user action function autoAckAlarms() { @@ -75,7 +75,7 @@ function init (env, ctx) { } if (sendClear) { - ctx.bus.emit('notification', {clear: true}); + ctx.bus.emit('notification', {clear: true, title: 'All Clear', message: 'Auto ack\'d alarm(s)'}); console.info('emitted notification clear'); } } @@ -182,7 +182,11 @@ function init (env, ctx) { } if (sendClear) { - ctx.bus.emit('notification', {clear: true}); + ctx.bus.emit('notification', { + clear: true + , title: 'All Clear' + , message: notifications.levels.toString(level) + ' was ack\'d' + }); } }; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 1b76c475f07..142467dab00 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -23,10 +23,8 @@ function init(env, ctx) { pushnotify.emitNotification = function emitNotification (notify) { if (notify.clear) { - console.info('got a notify clear'); if (ctx.pushover) { cancelPushoverNotifications(); } - if (ctx.maker) { sendMakerAllClear(); } - + if (ctx.maker) { sendMakerAllClear(notify); } return; } @@ -118,8 +116,8 @@ function init(env, ctx) { }); } - function sendMakerAllClear ( ) { - ctx.maker.sendAllClear(function makerCallback (err, result) { + function sendMakerAllClear (notify) { + ctx.maker.sendAllClear(notify, function makerCallback (err, result) { if (err) { console.error('unable to send maker allclear', err); } else if (result && result.sent) { diff --git a/lib/websocket.js b/lib/websocket.js index ef6b0c83896..0c1e116b4a6 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -40,7 +40,7 @@ function init (env, ctx, server) { io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { var level = alarmType == 'urgent_alarm' ? 2 : 1; - ctx.notifications.ack(level, silenceTime); + ctx.notifications.ack(level, silenceTime, true); }); socket.on('disconnect', function () { io.emit('clients', --watchers); diff --git a/server.js b/server.js index 2621cf10b81..aa7bfe0d486 100644 --- a/server.js +++ b/server.js @@ -66,4 +66,16 @@ require('./lib/bootevent')(env).boot(function booted (ctx) { ctx.bus.on('notification', function(info) { websocket.emitNotification(info); }); + + //after startup if there are no alarms send all clear + setTimeout(function sendStartupAllClear () { + var alarm = ctx.notifications.findHighestAlarm(); + if (!alarm) { + ctx.bus.emit('notification', { + clear: true + , title: 'All Clear' + , message: 'Server started without alarms' + }); + } + }, 20000); }); \ No newline at end of file diff --git a/tests/maker.test.js b/tests/maker.test.js index 1417a5274e5..2d94c33da4d 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -4,7 +4,11 @@ describe('maker', function ( ) { var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt - maker.makeRequest = function noOpMakeRequest (event, eventName, callback) { callback && callback()}; + function noOpMakeRequest (event, eventName, callback) { + if (callback) { callback(); } + } + + maker.makeRequest = noOpMakeRequest; it('turn values to a query', function (done) { maker.valuesToQuery({ @@ -27,13 +31,13 @@ describe('maker', function ( ) { } maker.makeRequest = mockedToTestSingleDone; - maker.sendAllClear(function sendCallback (err, result) { + maker.sendAllClear({}, function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(true); }); //send again, if done is called again test will fail - maker.sendAllClear(function sendCallback (err, result) { + maker.sendAllClear({}, function sendCallback (err, result) { should.not.exist(err); result.sent.should.equal(false); }); From 578805bfb6f118d38fd26d888968695d3f6faa5b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 20:16:41 -0700 Subject: [PATCH 242/661] more fixes for issues reported by codacy --- lib/notifications.js | 4 ++-- lib/pushnotify.js | 4 ++-- lib/sandbox.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index ab0e675f06b..0e951cc496f 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -154,7 +154,7 @@ function init (env, ctx) { var snoozedBy = notifications.snoozedBy(highestAlarm); if (snoozedBy) { console.log('snoozing: ', highestAlarm, ' with: ', snoozedBy); - notifications.ack(snoozedBy.level, snoozedBy.lengthMills) + notifications.ack(snoozedBy.level, snoozedBy.lengthMills); } emitNotification(highestAlarm); @@ -164,7 +164,7 @@ function init (env, ctx) { notifications.findInfos().forEach(function eachInfo (info) { emitNotification(info); - }) + }); }; notifications.ack = function ack (level, time, sendClear) { diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 142467dab00..32bcf4d2354 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -3,7 +3,7 @@ var _ = require('lodash'); var crypto = require('crypto'); var units = require('./units')(); -var NodeCache = require( "node-cache" ); +var NodeCache = require('node-cache'); function init(env, ctx) { @@ -58,7 +58,7 @@ function init(env, ctx) { var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { - ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true) + ctx.notifications.ack(notify.level, TIME_30_MINS_MS, true); } return !!notify; }; diff --git a/lib/sandbox.js b/lib/sandbox.js index 9e2ac851c39..c62586a8440 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -162,7 +162,7 @@ function init ( ) { function unitsLabel ( ) { if (sbx.units == 'mmol') return 'mmol/L'; - return "mg/dl"; + return 'mg/dl'; } function roundBGToDisplayFormat (bg) { From 3548081cf0efa9c99b651387eefe0389c724fd37 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:23:39 -0700 Subject: [PATCH 243/661] Update readme to include information IFTTT Maker --- README.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d4d99a4f49..872d6a96a84 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BG_LOW` (`55`) - must be set using mg/dl units; the low BG outside the target range that is considered urgent * `ALARM_TYPES` (`simple` if any `BG_`* ENV's are set, otherwise `predict`) - currently 2 alarm types are supported, and can be used independently or combined. The `simple` alarm type only compares the current BG to `BG_` thresholds above, the `predict` alarm type uses highly tuned formula that forecasts where the BG is going based on it's trend. `predict` **DOES NOT** currently use any of the `BG_`* ENV's * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. #### Core @@ -159,6 +157,9 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). + + Also see [Pushover](#pushover) and [IFTTT Maker](#ifttt-maker). + #### 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. @@ -252,7 +253,41 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? - Pushover is configured using the `PUSHOVER_API_TOKEN`, `PUSHOVER_USER_KEY`, `BASE_URL`, and `API_SECRET` environment variables. For acknowledgment callbacks to work `BASE_URL` and `API_SECRET` must be set and `BASE_URL` must be publicly accessible. For testing/devlopment try [localtunnel](http://localtunnel.me/). + Pushover is configured using the following Environment Variables: + + * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. + * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. + * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. + + For testing/devlopment try [localtunnel](http://localtunnel.me/). + +### IFTTT Maker + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md) + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. + * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. ## Setting environment variables Easy to emulate on the commandline: From 95d4c0c342e61f7e3f1c47dbf0e18d42d9259235 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:34:51 -0700 Subject: [PATCH 244/661] moved maker and pushover to plugins; initial maker setup docs --- lib/bootevent.js | 6 +++-- lib/plugins/maker-setup.md | 43 +++++++++++++++++++++++++++++++++++ lib/{ => plugins}/maker.js | 0 lib/{ => plugins}/pushover.js | 0 tests/maker.test.js | 2 +- 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 lib/plugins/maker-setup.md rename lib/{ => plugins}/maker.js (100%) rename lib/{ => plugins}/pushover.js (100%) diff --git a/lib/bootevent.js b/lib/bootevent.js index 1865077230f..c842f9d5466 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -17,9 +17,11 @@ function boot (env) { // api and json object variables /////////////////////////////////////////////////// ctx.plugins = require('./plugins')().registerServerDefaults().init(env); - ctx.pushover = require('./pushover')(env); - ctx.maker = require('./maker')(env); + + ctx.pushover = require('./plugins/pushover')(env); + ctx.maker = require('./plugins/maker')(env); ctx.pushnotify = require('./pushnotify')(env, ctx); + ctx.entries = require('./entries')(env, ctx); ctx.treatments = require('./treatments')(env, ctx); ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx); diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md new file mode 100644 index 00000000000..1aa8b9ba731 --- /dev/null +++ b/lib/plugins/maker-setup.md @@ -0,0 +1,43 @@ +Nightscout/IFTTT Maker +====================================== + +## Overview + + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. + * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. + * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. + * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. + +## Configuration + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + +## Create a recipe + + Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) + + 1. Choose a Trigger Channel + 2. Choose a Trigger + 3. Complete Trigger Fields + 4. That + 5. Choose an Action + 6. Complete Action Fields + 7. Create and Connect + diff --git a/lib/maker.js b/lib/plugins/maker.js similarity index 100% rename from lib/maker.js rename to lib/plugins/maker.js diff --git a/lib/pushover.js b/lib/plugins/pushover.js similarity index 100% rename from lib/pushover.js rename to lib/plugins/pushover.js diff --git a/tests/maker.test.js b/tests/maker.test.js index 2d94c33da4d..6b00ab5ccb4 100644 --- a/tests/maker.test.js +++ b/tests/maker.test.js @@ -1,7 +1,7 @@ var should = require('should'); describe('maker', function ( ) { - var maker = require('../lib/maker')({extendedSettings: {maker: {key: '12345'}}}); + var maker = require('../lib/plugins/maker')({extendedSettings: {maker: {key: '12345'}}}); //prevent any calls to iftt function noOpMakeRequest (event, eventName, callback) { From fd8ad2f03eb343f20c2e685c7822da7bc2125ede Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 29 Jun 2015 23:51:49 -0700 Subject: [PATCH 245/661] try adding images to maker doc --- lib/plugins/maker-setup.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 1aa8b9ba731..7ca03e5a38b 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -32,12 +32,30 @@ Nightscout/IFTTT Maker ## Create a recipe Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) - + ![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) + 1. Choose a Trigger Channel + ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) + 2. Choose a Trigger + ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) + 3. Complete Trigger Fields + ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) + 4. That + ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) + 5. Choose an Action + ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) + 6. Complete Action Fields + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) + ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) + 7. Create and Connect + ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) + + + From 889a16f1f1194851e075f6e3b4f2541a37738dde Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:06:19 -0700 Subject: [PATCH 246/661] add toc's to docs, some clean/organization --- CONTRIBUTING.md | 16 +++++ README.md | 139 +++++++++++++++++++++---------------- Release.md | 15 ---- lib/plugins/maker-setup.md | 65 +++++++++++------ 4 files changed, 140 insertions(+), 95 deletions(-) delete mode 100644 Release.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1dd0ba4d427..6169a7cbc4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,19 @@ + + +**Table of Contents** + +- [Contributing to cgm-remote-monitor](#contributing-to-cgm-remote-monitor) + - [Design](#design) + - [Develop on `dev`](#develop-on-dev) + - [Style Guide](#style-guide) + - [Create a prototype](#create-a-prototype) + - [Submit a pull request](#submit-a-pull-request) + - [Comments and issues](#comments-and-issues) + - [Co-ordination](#co-ordination) + - [Other Dev Tips](#other-dev-tips) + + + # Contributing to cgm-remote-monitor diff --git a/README.md b/README.md index 872d6a96a84..32701b7b0a2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ + + +**Table of Contents** + +- [Nightscout Web Monitor (a.k.a. cgm-remote-monitor)](#nightscout-web-monitor-aka-cgm-remote-monitor) +- [[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why.](##wearenotwaitinghttpstwittercomhashtagwearenotwaitingsrchash&verticaldefault&fimages-and-thishttpsvimeocom109767890-is-why) +- [Install](#install) +- [Usage](#usage) + - [Updating my version?](#updating-my-version) + - [What is my mongo string?](#what-is-my-mongo-string) + - [Configure my uploader to match](#configure-my-uploader-to-match) + - [Environment](#environment) + - [Required](#required) + - [Features/Labs](#featureslabs) + - [Core](#core) + - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) + - [Plugins](#plugins) + - [Extended Settings](#extended-settings) + - [Pushover](#pushover) + - [IFTTT Maker](#ifttt-maker) + - [Treatment Profile](#treatment-profile) + - [Setting environment variables](#setting-environment-variables) + - [Vagrant install](#vagrant-install) + - [More questions?](#more-questions) + - [License](#license) + + + Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== @@ -19,7 +47,7 @@ and blood glucose values are predicted 0.5 hours ahead using an autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. -#[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. +# [#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why. Community maintained fork of the [original cgm-remote-monitor][original]. @@ -39,8 +67,7 @@ Community maintained fork of the [heroku-url]: https://heroku.com/deploy [original]: https://github.com/rnpenguin/cgm-remote-monitor -Install ---------------- +# Install Requirements: @@ -52,8 +79,7 @@ Clone this repo then install dependencies into the root of the project: $ npm install ``` -Usage ---------------- +#Usage The data being uploaded from the server to the client is from a MongoDB server such as [mongolab][mongodb]. In order to access the @@ -68,31 +94,32 @@ ready, just host your web app on your service of choice. [mongostring]: http://nightscout.github.io/pages/mongostring/ [update-fork]: http://nightscout.github.io/pages/update-fork/ -### Updating my version? +## Updating my version? The easiest way to update your version of cgm-remote-monitor to our latest recommended version is to use the [update my fork tool][update-fork]. It even gives out stars if you are up to date. -### What is my mongo string? +## What is my mongo string? Try the [what is my mongo string tool][mongostring] to get a good idea of your mongo string. You can copy and paste the text in the gray box into your `MONGO_CONNECTION` environment variable. -### Configure my uploader to match +## Configure my uploader to match Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. -### Environment +## Environment `VARIABLE` (default) - description -#### Required +### Required * `MONGO_CONNECTION` - Your mongo uri, for example: `mongodb://sally:sallypass@ds099999.mongolab.com:99999/nightscout` + * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. -#### Features/Labs +### Features/Labs * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal @@ -104,9 +131,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BASE_URL` - Used for building links to your sites api, ie pushover callbacks, usually the URL of your Nightscout site you may want https instead of http -#### Core +### Core - * `DISPLAY_UNITS` (`mg/dl`) - Choices: `mg/dl` and `mmol`. Setting to `mmol` puts the entire server into `mmol` mode by default, no further settings needed. * `MONGO_COLLECTION` (`entries`) - The collection used to store SGV, MBG, and CAL records from your CGM device * `MONGO_TREATMENTS_COLLECTION` (`treatments`) -The collection used to store treatments entered in the Care Portal, see the `ENABLE` env var above * `MONGO_DEVICESTATUS_COLLECTION`(`devicestatus`) - The collection used to store device status information such as uploader battery @@ -116,7 +142,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `SSL_CA` - Path to your ssl ca file, so that ssl(https) can be enabled directly in node.js -#### Predefined values for your browser settings (optional) +### Predefined values for your browser settings (optional) * `TIME_FORMAT` (`12`)- possible values `12` or `24` * `NIGHT_MODE` (`off`) - possible values `on` or `off` * `SHOW_RAWBG` (`never`) - possible values `always`, `never` or `noise` @@ -166,6 +192,46 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins only have access to their own extended settings, all the extended settings of client plugins will be sent to the browser. +#### Pushover + In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. + + To get started install the Pushover application on your iOS or Android device and create an account. + + Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. + + You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? + + Pushover is configured using the following Environment Variables: + + * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. + * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. + * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. + * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. + * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. + + For testing/devlopment try [localtunnel](http://localtunnel.me/). + +#### IFTTT Maker + In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). + + With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. + + 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) + 2. Find your secret key on the [maker page](https://ifttt.com/maker) + 3. Configure Nightscout by setting these environment variables: + * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. + * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` + 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md#create-a-recipe) + + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. + * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. + * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. + * see the [full list of events](lib/plugins/maker-setup.md#events) + + ### Treatment Profile Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): @@ -244,51 +310,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). -### Pushover - In addition to the normal web based alarms, there is also support for [Pushover](https://pushover.net/) based alarms and notifications. - - To get started install the Pushover application on your iOS or Android device and create an account. - - Using that account login to [Pushover](https://pushover.net/), in the top left you’ll see your User Key, you’ll need this plus an application API Token/Key to complete this setup. - - You’ll need to [Create a Pushover Application](https://pushover.net/apps/build). You only need to set the Application name, you can ignore all the other settings, but setting an Icon is a nice touch. Maybe you'd like to use [this one](https://raw.githubusercontent.com/nightscout/cgm-remote-monitor/master/static/images/large.png)? - - Pushover is configured using the following Environment Variables: - - * `ENABLE` - `pushover` should be added to the list of plugin, for example: `ENABLE="pushover"`. - * `PUSHOVER_API_TOKEN` - Used to enable pushover notifications, this token is specific to the application you create from in [Pushover](https://pushover.net/), ***[additional pushover information](#pushover)*** below. - * `PUSHOVER_USER_KEY` - Your Pushover user key, can be found in the top left of the [Pushover](https://pushover.net/) site, this can also be a pushover delivery group key to send to a group rather than just a single user. - * `BASE_URL` - Used for pushover callbacks, usually the URL of your Nightscout site, use https when possible. - * `API_SECRET` - Used for signing the pushover callback request for acknowledgments. - - For testing/devlopment try [localtunnel](http://localtunnel.me/). - -### IFTTT Maker - In addition to the normal web based alarms, and pushover, there is also integration for [IFTTT Maker](https://ifttt.com/maker). - - With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. - - 1. Setup IFTTT account: [login](https://ifttt.com/login) or [create an account](https://ifttt.com/join) - 2. Find your secret key on the [maker page](https://ifttt.com/maker) - 3. Configure Nightscout by setting these environment variables: - * `ENABLE` - `maker` should be added to the list of plugin, for example: `ENABLE="maker"`. - * `MAKER_KEY` - Set this to your secret key that you located in step 2, for example: `MAKER_KEY="abcMyExampleabc123defjt1DeNSiftttmak-XQb69p"` - 4. [Create a recipe](https://ifttt.com/myrecipes/personal/new) or see [more detailed instructions](lib/plugins/maker-setup.md) - - Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: - * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. - * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. - * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-urgent` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event`. - * `ns-warning-high` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-high` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-warning-low` - Alarms at the warning level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-low` - Alarms at the urgent level with cause this event to also be triggered. It will be sent in addition to `ns-event` and `ns-urgent`. - * `ns-info-treatmentnotify` - When a treatment is entered into the care portal this event is triggered. It will be sent in addition to `ns-event` and `ns-info`. - * `ns-warning-bwp` - When the BWP plugin generates a warning alarm. It will be sent in addition to `ns-event` and `ns-warning`. - * `ns-urgent-bwp` - When the BWP plugin generates an urgent alarm. It will be sent in addition to `ns-event` and `ns-urget`. - ## Setting environment variables Easy to emulate on the commandline: diff --git a/Release.md b/Release.md deleted file mode 100644 index 8e9ba512d0d..00000000000 --- a/Release.md +++ /dev/null @@ -1,15 +0,0 @@ - -v0.4.1 / 2014-09-12 -================== - - * quick hack to prevent mbg records from crashing pebble - * add script to prep release branch - * tweak toolbar and button size/placement - * Merge pull request #128 from nightscout/wip/mbg - * Merge pull request #166 from nightscout/wip/id-rev - * Merge pull request #165 from nightscout/hotfix/pebble-sgv-string - * convert sgv to string - * add ability to easily id git rev-parse HEAD - * Merge branch 'release/0.4.0' into dev - * hack: only consider 'grey' sgv records - * Added searching for MBG data from mongo query. diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 7ca03e5a38b..4bca6fa7202 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -1,3 +1,23 @@ + + +**Table of Contents** + +- [Nightscout/IFTTT Maker](#nightscoutifttt-maker) + - [Overview](#overview) + - [Events](#events) + - [Configuration](#configuration) + - [Create a recipe](#create-a-recipe) + - [Start [creating a recipe](https://ifttt.com/myrecipes/personal/new)](#start-creating-a-recipehttpsiftttcommyrecipespersonalnew) + - [1. Choose a Trigger Channel](#1-choose-a-trigger-channel) + - [2. Choose a Trigger](#2-choose-a-trigger) + - [3. Complete Trigger Fields](#3-complete-trigger-fields) + - [4. That](#4-that) + - [5. Choose an Action](#5-choose-an-action) + - [6. Complete Action Fields](#6-complete-action-fields) + - [7. Create and Connect](#7-create-and-connect) + + + Nightscout/IFTTT Maker ====================================== @@ -7,7 +27,10 @@ Nightscout/IFTTT Maker With Maker you are able to integrate with all the other [IFTTT Channels](https://ifttt.com/channels). For example you can send a tweet when there is an alarm, change the color of hue light, send an email, send and sms, and so much more. +## Events + Plugins can create custom events, but all events sent to maker will be prefixed with `ns-`. The core events are: + * `ns-event` - This event is sent to the maker service for all alarms and notifications. This is good catch all event for general logging. * `ns-allclear` - This event is sent to the maker service when an alarm has been ack'd or when the server starts up without triggering any alarms. For example, you could use this event to turn a light to green. * `ns-info` - Plugins that generate notifications at the info level will cause this event to also be triggered. It will be sent in addition to `ns-event`. @@ -31,30 +54,30 @@ Nightscout/IFTTT Maker ## Create a recipe - Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) - ![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) +### Start [creating a recipe](https://ifttt.com/myrecipes/personal/new) +![screen shot 2015-06-29 at 10 58 48 pm](https://cloud.githubusercontent.com/assets/751143/8425240/bab51986-1eb8-11e5-88fb-5aed311896be.png) + +### 1. Choose a Trigger Channel + ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) + +### 2. Choose a Trigger + ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) - 1. Choose a Trigger Channel - ![screen shot 2015-06-29 at 10 59 01 pm](https://cloud.githubusercontent.com/assets/751143/8425243/c007ace6-1eb8-11e5-96d1-b13f9c3d071f.png) +### 3. Complete Trigger Fields + ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) + +### 4. That + ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) - 2. Choose a Trigger - ![screen shot 2015-06-29 at 10 59 18 pm](https://cloud.githubusercontent.com/assets/751143/8425246/c77c5a4e-1eb8-11e5-9084-32ae40518ee0.png) +### 5. Choose an Action + ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) - 3. Complete Trigger Fields - ![screen shot 2015-06-29 at 10 59 33 pm](https://cloud.githubusercontent.com/assets/751143/8425249/ced7b450-1eb8-11e5-95a3-730f6b9b2925.png) - - 4. That - ![screen shot 2015-06-29 at 10 59 46 pm](https://cloud.githubusercontent.com/assets/751143/8425251/d46e1dc8-1eb8-11e5-91be-8dc731e308b2.png) - - 5. Choose an Action - ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) - - 6. Complete Action Fields - ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) - ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) - - 7. Create and Connect - ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) +### 6. Complete Action Fields + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) + ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) + +### 7. Create and Connect + ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) From 6b06a5788342272565cde91f1e8ca598e553977b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:10:04 -0700 Subject: [PATCH 247/661] move toc down in readme --- README.md | 54 ++++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 32701b7b0a2..f97537aab77 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,3 @@ - - -**Table of Contents** - -- [Nightscout Web Monitor (a.k.a. cgm-remote-monitor)](#nightscout-web-monitor-aka-cgm-remote-monitor) -- [[#WeAreNotWaiting](https://twitter.com/hashtag/wearenotwaiting?src=hash&vertical=default&f=images) and [this](https://vimeo.com/109767890) is why.](##wearenotwaitinghttpstwittercomhashtagwearenotwaitingsrchash&verticaldefault&fimages-and-thishttpsvimeocom109767890-is-why) -- [Install](#install) -- [Usage](#usage) - - [Updating my version?](#updating-my-version) - - [What is my mongo string?](#what-is-my-mongo-string) - - [Configure my uploader to match](#configure-my-uploader-to-match) - - [Environment](#environment) - - [Required](#required) - - [Features/Labs](#featureslabs) - - [Core](#core) - - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - - [Plugins](#plugins) - - [Extended Settings](#extended-settings) - - [Pushover](#pushover) - - [IFTTT Maker](#ifttt-maker) - - [Treatment Profile](#treatment-profile) - - [Setting environment variables](#setting-environment-variables) - - [Vagrant install](#vagrant-install) - - [More questions?](#more-questions) - - [License](#license) - - - Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== @@ -67,6 +39,32 @@ Community maintained fork of the [heroku-url]: https://heroku.com/deploy [original]: https://github.com/rnpenguin/cgm-remote-monitor + + +**Table of Contents** + +- [Install](#install) +- [Usage](#usage) + - [Updating my version?](#updating-my-version) + - [What is my mongo string?](#what-is-my-mongo-string) + - [Configure my uploader to match](#configure-my-uploader-to-match) + - [Environment](#environment) + - [Required](#required) + - [Features/Labs](#featureslabs) + - [Core](#core) + - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) + - [Plugins](#plugins) + - [Extended Settings](#extended-settings) + - [Pushover](#pushover) + - [IFTTT Maker](#ifttt-maker) + - [Treatment Profile](#treatment-profile) + - [Setting environment variables](#setting-environment-variables) + - [Vagrant install](#vagrant-install) + - [More questions?](#more-questions) + - [License](#license) + + + # Install Requirements: From 08529c4175c2ae53f10699a8ff0e42d54d11b1fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:12:23 -0700 Subject: [PATCH 248/661] added logo to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f97537aab77..0cc3c1d0551 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) ====================================== +![nightscout horizontal](https://cloud.githubusercontent.com/assets/751143/8425633/93c94dc0-1ebc-11e5-99e7-71a8f464caac.png) + [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] From 4c7846f6e9aa28bbdf5bac16229b589283c33ae3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:36:50 -0700 Subject: [PATCH 249/661] more doc updates --- README.md | 2 +- lib/plugins/maker-setup.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0cc3c1d0551..c037be74481 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. Plugins are used extend the way information is displayed, how notifications are sent, alarms are triggered, and more. - The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding the to the `ENABLE` environment variable. + The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding to the `ENABLE` environment variable. **Built-in/Example Plugins:** diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 4bca6fa7202..261599abaff 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -78,7 +78,8 @@ Nightscout/IFTTT Maker ### 7. Create and Connect ![screen shot 2015-06-29 at 11 02 43 pm](https://cloud.githubusercontent.com/assets/751143/8425277/fe52f618-1eb8-11e5-8d7f-e0b34eebe29a.png) - - + +### Result + ![cinpiqkumaa33u7](https://cloud.githubusercontent.com/assets/751143/8425925/e7d08d2c-1ebf-11e5-853c-cdc5381c4186.png) From 93ee9b7ecf9e36ae7691b0d1f68c0d932b97a6bf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:37:46 -0700 Subject: [PATCH 250/661] update toc --- lib/plugins/maker-setup.md | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 261599abaff..1d01df7b75b 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -15,6 +15,7 @@ - [5. Choose an Action](#5-choose-an-action) - [6. Complete Action Fields](#6-complete-action-fields) - [7. Create and Connect](#7-create-and-connect) + - [Result](#result) From 52c8bc617e217e2aeed18ce12c275ef21d3b8477 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 00:40:06 -0700 Subject: [PATCH 251/661] added example tweet test to doc --- lib/plugins/maker-setup.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/maker-setup.md b/lib/plugins/maker-setup.md index 1d01df7b75b..a7d89084376 100644 --- a/lib/plugins/maker-setup.md +++ b/lib/plugins/maker-setup.md @@ -74,6 +74,8 @@ Nightscout/IFTTT Maker ![screen shot 2015-06-29 at 11 00 12 pm](https://cloud.githubusercontent.com/assets/751143/8425254/de634844-1eb8-11e5-8f09-cd43c41ccf3f.png) ### 6. Complete Action Fields + **Example:** `Nightscout: {{Value1}} {{Value2}} {{Value3}}` + ![screen shot 2015-06-29 at 11 02 14 pm](https://cloud.githubusercontent.com/assets/751143/8425267/f2da6dd4-1eb8-11e5-8e4d-cad2590d111f.png) ![screen shot 2015-06-29 at 11 02 21 pm](https://cloud.githubusercontent.com/assets/751143/8425272/f83ceb62-1eb8-11e5-8ea2-afd4dcbd391f.png) From 6f55a86d1104c9bd357f1183be174f7e3195894d Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Tue, 30 Jun 2015 18:03:34 +0200 Subject: [PATCH 252/661] Fixed the issues as described by Codacy --- tests/storage.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/storage.test.js b/tests/storage.test.js index a4a5d8152a5..641b710d920 100644 --- a/tests/storage.test.js +++ b/tests/storage.test.js @@ -1,9 +1,7 @@ 'use strict'; -var request = require('supertest'); var should = require('should'); var assert = require('assert'); -var load = require('./fixtures/load'); describe('STORAGE', function () { var env = require('../env')(); @@ -14,7 +12,7 @@ describe('STORAGE', function () { }); it('The storage class should be OK.', function (done) { - require('../lib/storage').should.be.ok; + should(require('../lib/storage')).be.ok; done(); }); @@ -25,7 +23,7 @@ describe('STORAGE', function () { store(env, function (err2, db2) { should.not.exist(err2); - assert(db1.db, db2.db, 'Check if the handlers are the same.') + assert(db1.db, db2.db, 'Check if the handlers are the same.'); done(); }); From 853a052d57933e7b6b5a7060f5064e0e640f5cd6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 18:11:51 -0700 Subject: [PATCH 253/661] adjust example profile, to prevent copy and pastes from seeing a high BWP --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c037be74481..c05ecb40f0f 100644 --- a/README.md +++ b/README.md @@ -233,36 +233,38 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. A simple example (change it to fit you): + Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. + + Example Profile (change it to fit you): ```json { - "dia": 4, - "carbs_hr": 30, - "carbratio": 7.5, - "sens": 35, - "basal": 1.00 - "target_low": 95, + "dia": 3, + "carbs_hr": 20, + "carbratio": 30, + "sens": 100, + "basal": 0.125, + "target_low": 100, "target_high": 120 } ``` - Profiles can also use time periods for any field, for example: + Profile can also use time periods for any field, for example: ```json { "carbratio": [ { "time": "00:00", - "value": 16 + "value": 30 }, { "time": "06:00", - "value": 15 + "value": 25 }, { "time": "14:00", - "value": 16 + "value": 28 } ], "basal": [ @@ -280,7 +282,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. }, { "time": "08:00", - "value": 0.1 + "value": 0.100 }, { "time": "14:00", @@ -288,11 +290,11 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. }, { "time": "20:00", - "value": 0.3 + "value": 0.175 }, { "time": "22:00", - "value": 0.225 + "value": 0.200 } ] } From e68fce5e55ca10881be20a9a446e46b5646bee9c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 18:39:58 -0700 Subject: [PATCH 254/661] minor formatting, while testing --- lib/storage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index ac8f745c40c..77c2fa33a30 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -4,11 +4,11 @@ var mongodb = require('mongodb'); var connection = null; -function init(env, cb, forceNewConnection) { +function init (env, cb, forceNewConnection) { var MongoClient = mongodb.MongoClient; var mongo = {}; - function maybe_connect(cb) { + function maybe_connect (cb) { if (connection != null && !forceNewConnection) { console.log('Reusing MongoDB connection handler'); @@ -43,24 +43,24 @@ function init(env, cb, forceNewConnection) { } } - mongo.collection = function get_collection(name) { + mongo.collection = function get_collection (name) { return connection.collection(name); }; - mongo.with_collection = function with_collection(name) { + mongo.with_collection = function with_collection (name) { return function use_collection(fn) { fn(null, connection.collection(name)); }; }; - mongo.limit = function limit(opts) { + mongo.limit = function limit (opts) { if (opts && opts.count) { return this.limit(parseInt(opts.count)); } return this; }; - mongo.ensureIndexes = function (collection, fields) { + mongo.ensureIndexes = function ensureIndexes (collection, fields) { fields.forEach(function (field) { console.info('ensuring index for: ' + field); collection.ensureIndex(field, function (err) { From 264597ba83cbb2bc6cfd5144779a1259fd11247b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 19:37:18 -0700 Subject: [PATCH 255/661] send coverage to codacy --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index c7fa740dcbb..def09f31ccc 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ MOCHA=./node_modules/mocha/bin/_mocha # Pinned from dependency list. ISTANBUL=./node_modules/.bin/istanbul ANALYZED=./coverage/lcov.info +export CODACY_REPO_TOKEN=a033cfbe4e184d6f925bab97a21ed2d0 all: test @@ -36,6 +37,9 @@ report: test -f ${ANALYZED} && \ (npm install codecov.io && cat ${ANALYZED} | \ ./node_modules/codecov.io/bin/codecov.io.js) || echo "NO COVERAGE" + test -f ${ANALYZED} && \ + (npm install codacy-coverage && cat ${ANALYZED} | \ + YOURPACKAGE_COVERAGE=1 ./node_modules/codacy-coverage/bin/codacy-coverage.js) || echo "NO COVERAGE" test: ${MONGO_SETTINGS} ${MOCHA} -R tap ${TESTS} From 21c2679672d82088e50d213344ab39d1f1d3d01d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 20:07:05 -0700 Subject: [PATCH 256/661] new key to try getting things going again --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index def09f31ccc..2b60d26001b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ MOCHA=./node_modules/mocha/bin/_mocha # Pinned from dependency list. ISTANBUL=./node_modules/.bin/istanbul ANALYZED=./coverage/lcov.info -export CODACY_REPO_TOKEN=a033cfbe4e184d6f925bab97a21ed2d0 +export CODACY_REPO_TOKEN=e29ae5cf671f4f918912d9864316207c all: test From fd585720b8693cdec31f41b38fcc4f23157cd535 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 30 Jun 2015 23:34:59 -0700 Subject: [PATCH 257/661] fix lots of little issues reported by codacy --- app.js | 12 +- env.js | 20 +-- lib/api/devicestatus/index.js | 73 +++++----- lib/api/index.js | 4 +- lib/api/status.js | 6 +- lib/api/treatments/index.js | 69 ++++----- lib/bootevent.js | 2 +- lib/bus.js | 2 +- lib/data.js | 47 +++--- lib/devicestatus.js | 65 ++++----- lib/entries.js | 15 +- lib/middleware/send-json-status.js | 17 ++- lib/mqtt.js | 220 ++++++++++++++--------------- lib/notifications.js | 2 +- lib/pebble.js | 18 +-- lib/plugins/ar2.js | 4 +- lib/plugins/basalprofile.js | 4 +- lib/plugins/boluswizardpreview.js | 10 +- lib/plugins/cannulaage.js | 2 +- lib/plugins/cob.js | 8 +- lib/plugins/iob.js | 6 +- lib/plugins/pluginbase.js | 2 +- lib/plugins/treatmentnotify.js | 4 +- lib/profilefunctions.js | 24 ++-- lib/pushnotify.js | 5 +- lib/sandbox.js | 8 +- lib/storage.js | 3 +- lib/treatments.js | 8 +- lib/units.js | 2 + lib/utils.js | 28 ++-- lib/websocket.js | 5 +- static/js/client.js | 70 ++++----- static/js/ui-utils.js | 8 +- testing/convert-treatments.js | 28 ++-- testing/make_high_data.js | 18 +-- testing/populate.js | 35 +++-- testing/populate_rest.js | 55 ++++---- testing/util.js | 107 +++++++------- tests/api.entries.test.js | 8 +- tests/api.status.test.js | 5 +- tests/cannulaage.test.js | 4 +- tests/cob.test.js | 34 ++--- tests/data.test.js | 7 +- tests/delta.test.js | 2 +- tests/iob.test.js | 12 +- tests/mqtt.test.js | 8 +- tests/pebble.test.js | 2 +- tests/profile.test.js | 98 +++++++------ tests/pushnotify.test.js | 4 +- tests/rawbg.test.js | 2 +- tests/security.test.js | 1 - tests/units.test.js | 4 +- tests/upbat.test.js | 2 +- tests/utils.test.js | 4 +- 54 files changed, 621 insertions(+), 592 deletions(-) diff --git a/app.js b/app.js index c15b5e1a6df..d6c63bea4ba 100644 --- a/app.js +++ b/app.js @@ -12,13 +12,11 @@ function create (env, ctx) { app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. - app.use(compression({filter: shouldCompress})); - - function shouldCompress(req, res) { - //TODO: return false here if we find a condition where we don't want to compress - // fallback to standard filter function - return compression.filter(req, res); - } + app.use(compression({filter: function shouldCompress(req, res) { + //TODO: return false here if we find a condition where we don't want to compress + // fallback to standard filter function + return compression.filter(req, res); + }})); //if (env.api_secret) { // console.log("API_SECRET", env.api_secret); diff --git a/env.js b/env.js index f879572221f..9cc0220d6c6 100644 --- a/env.js +++ b/env.js @@ -12,9 +12,9 @@ function config ( ) { * First inspect a bunch of environment variables: * * PORT - serve http on this port * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri - * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with "sgv" documents + * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with `sgv` documents * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization - * * NIGHTSCOUT_STATIC_FILES - the "base directory" to use for serving + * * NIGHTSCOUT_STATIC_FILES - the `base directory` to use for serving * static files over http. Default value is the included `static` * directory. */ @@ -23,10 +23,10 @@ function config ( ) { if (readENV('APPSETTING_ScmType') == readENV('ScmType') && readENV('ScmType') == 'GitHub') { env.head = require('./scm-commit-id.json'); - console.log("SCM COMMIT ID", env.head); + console.log('SCM COMMIT ID', env.head); } else { git.short(function record_git_head (head) { - console.log("GIT HEAD", head); + console.log('GIT HEAD', head); env.head = head || readENV('SCM_COMMIT_ID') || readENV('COMMIT_HASH', ''); }); } @@ -54,7 +54,7 @@ function config ( ) { env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); - env.enable = readENV('ENABLE', ""); + env.enable = readENV('ENABLE', ''); env.defaults = { // currently supported keys must defined be here 'units': 'mg/dL' @@ -127,7 +127,7 @@ function config ( ) { // if a passphrase was provided, get the hex digest to mint a single token if (useSecret) { if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { - var msg = ["API_SECRET should be at least", consts.MIN_PASSPHRASE_LENGTH, "characters"]; + var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; var err = new Error(msg.join(' ')); // console.error(err); throw err; @@ -170,9 +170,9 @@ function config ( ) { console.warn('BG_HIGH is now ' + env.thresholds.bg_high); } - //if any of the BG_* thresholds are set, default to "simple" otherwise default to "predict" to preserve current behavior + //if any of the BG_* thresholds are set, default to `simple` otherwise default to `predict` to preserve current behavior var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? "simple" : "predict"); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); //TODO: maybe get rid of ALARM_TYPES and only use enable? if (env.alarm_types.indexOf('simple') > -1) { @@ -223,8 +223,8 @@ function readENV(varName, defaultValue) { || process.env[varName] || process.env[varName.toLowerCase()]; - if (typeof value === 'string' && value.toLowerCase() == 'on') value = true; - if (typeof value === 'string' && value.toLowerCase() == 'off') value = false; + if (typeof value === 'string' && value.toLowerCase() == 'on') { value = true; } + if (typeof value === 'string' && value.toLowerCase() == 'off') { value = false; } return value != null ? value : defaultValue; } diff --git a/lib/api/devicestatus/index.js b/lib/api/devicestatus/index.js index 05980ffbe36..49e1ac36d8b 100644 --- a/lib/api/devicestatus/index.js +++ b/lib/api/devicestatus/index.js @@ -3,48 +3,49 @@ var consts = require('../../constants'); function configure (app, wares, ctx) { - var express = require('express'), - api = express.Router( ); - - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - // List settings available - api.get('/devicestatus/', function(req, res) { - var q = req.query; - if (!q.count) { - q.count = 10; - } - ctx.devicestatus.list(q, function (err, results) { - return res.json(results); - }); + var express = require('express'), + api = express.Router( ); + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw( )); + // json body types get handled as parsed json + api.use(wares.bodyParser.json( )); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ extended: true })); + + // List settings available + api.get('/devicestatus/', function(req, res) { + var q = req.query; + if (!q.count) { + q.count = 10; + } + ctx.devicestatus.list(q, function (err, results) { + return res.json(results); }); + }); - function config_authed (app, api, wares, ctx) { + function config_authed (app, api, wares, ctx) { - api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { - var obj = req.body; - ctx.devicestatus.create(obj, function (err, created) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else - res.json(created); - }); - }); + api.post('/devicestatus/', /*TODO: auth disabled for quick UI testing... wares.verifyAuthorization, */ function(req, res) { + var obj = req.body; + ctx.devicestatus.create(obj, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + } else { + res.json(created); + } + }); + }); - } + } - if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { - config_authed(app, api, wares, ctx); - } + if (app.enabled('api') || true /*TODO: auth disabled for quick UI testing...*/) { + config_authed(app, api, wares, ctx); + } - return api; + return api; } module.exports = configure; diff --git a/lib/api/index.js b/lib/api/index.js index 6e28946db46..c2552069b72 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -19,7 +19,7 @@ function create (env, ctx) { // Only allow access to the API if API_SECRET is set on the server. app.disable('api'); if (env.api_secret) { - console.log("API_SECRET", env.api_secret); + console.log('API_SECRET', env.api_secret); app.enable('api'); } @@ -28,7 +28,7 @@ function create (env, ctx) { app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; env.enable.toLowerCase().split(' ').forEach(function (value) { var enable = value.trim(); - console.info("enabling feature:", enable); + console.info('enabling feature:', enable); app.enable(enable); }); } diff --git a/lib/api/status.js b/lib/api/status.js index eca642ce0ab..8b1de9ed21d 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -9,7 +9,7 @@ function configure (app, wares) { 'json', 'svg', 'csv', 'txt', 'png', 'html', 'js' ])); // Status badge/text/json - api.get('/status', function (req, res, next) { + api.get('/status', function (req, res) { var info = { status: 'ok' , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && app.enabled('careportal') @@ -34,13 +34,13 @@ function configure (app, wares) { res.redirect(302, badge + '.svg'); }, js: function ( ) { - var head = "this.serverSettings ="; + var head = 'this.serverSettings ='; var body = JSON.stringify(info); var tail = ';'; res.send([head, body, tail].join(' ')); }, text: function ( ) { - res.send("STATUS OK"); + res.send('STATUS OK'); }, json: function ( ) { res.json(info); diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index eb093d2b4cb..e1e3a1c29b2 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -3,44 +3,45 @@ var consts = require('../../constants'); function configure (app, wares, ctx) { - var express = require('express'), - api = express.Router( ); - - // invoke common middleware - api.use(wares.sendJSONStatus); - // text body types get handled as raw buffer stream - api.use(wares.bodyParser.raw( )); - // json body types get handled as parsed json - api.use(wares.bodyParser.json( )); - // also support url-encoded content-type - api.use(wares.bodyParser.urlencoded({ extended: true })); - - // List treatments available - api.get('/treatments/', function(req, res) { - ctx.treatments.list({find: req.params}, function (err, results) { - return res.json(results); - }); + var express = require('express'), + api = express.Router( ); + + // invoke common middleware + api.use(wares.sendJSONStatus); + // text body types get handled as raw buffer stream + api.use(wares.bodyParser.raw( )); + // json body types get handled as parsed json + api.use(wares.bodyParser.json( )); + // also support url-encoded content-type + api.use(wares.bodyParser.urlencoded({ extended: true })); + + // List treatments available + api.get('/treatments/', function(req, res) { + ctx.treatments.list({find: req.params}, function (err, results) { + return res.json(results); + }); + }); + + function config_authed (app, api, wares, ctx) { + + api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { + var treatment = req.body; + ctx.treatments.create(treatment, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + } else { + res.json(created); + } + }); }); - function config_authed (app, api, wares, ctx) { - - api.post('/treatments/', /*TODO: auth disabled for now, need to get login figured out... wares.verifyAuthorization, */ function(req, res) { - var treatment = req.body; - ctx.treatments.create(treatment, function (err, created) { - if (err) - res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); - else - res.json(created); - }); - }); - - } + } - if (app.enabled('api') && app.enabled('careportal')) { - config_authed(app, api, wares, ctx); - } + if (app.enabled('api') && app.enabled('careportal')) { + config_authed(app, api, wares, ctx); + } - return api; + return api; } module.exports = configure; diff --git a/lib/bootevent.js b/lib/bootevent.js index c842f9d5466..634710f7641 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -35,7 +35,7 @@ function boot (env) { } function ensureIndexes (ctx, next) { - console.info("Ensuring indexes"); + console.info('Ensuring indexes'); ctx.store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); ctx.store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); ctx.store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); diff --git a/lib/bus.js b/lib/bus.js index 687f31cb6ee..107fe5d036f 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -25,7 +25,7 @@ function init (env, ctx) { } function ender ( ) { - if (id) cancelInterval(id); + if (id) { cancelInterval(id); } stream.emit('end'); } diff --git a/lib/data.js b/lib/data.js index cc7025a4d25..dedcfe4805f 100644 --- a/lib/data.js +++ b/lib/data.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var async = require('async'); -var utils = require('./utils')(); var ObjectID = require('mongodb').ObjectID; function uniq(a) { @@ -71,7 +70,7 @@ function init(env, ctx) { async.parallel({ entries: function (callback) { - var q = {find: {"date": {"$gte": earliest_data}}}; + var q = {find: {date: {$gte: earliest_data}}}; ctx.entries.list(q, function (err, results) { if (!err && results) { var mbgs = []; @@ -99,7 +98,7 @@ function init(env, ctx) { }) }, cal: function (callback) { //FIXME: date $gte????? - var cq = {count: 1, find: {"type": "cal"}}; + var cq = {count: 1, find: {type: 'cal'}}; ctx.entries.list(cq, function (err, results) { if (!err && results) { var cals = []; @@ -115,7 +114,7 @@ function init(env, ctx) { callback(); }); }, treatments: function (callback) { - var tq = {find: {"created_at": {"$gte": new Date(treatment_earliest_data).toISOString()}}}; + var tq = {find: {created_at: {$gte: new Date(treatment_earliest_data).toISOString()}}}; ctx.treatments.list(tq, function (err, results) { if (!err && results) { var treatments = results.map(function (treatment) { @@ -140,7 +139,7 @@ function init(env, ctx) { if (!err && results) { // There should be only one document in the profile collection with a DIA. If there are multiple, use the last one. var profiles = []; - results.forEach(function (element, index, array) { + results.forEach(function (element) { if (element) { if (element.dia) { profiles[0] = element; @@ -173,7 +172,7 @@ function init(env, ctx) { var changesFound = false; // if there's no updates done so far, just return the full set - if (!oldData.sgvs) return newData; + if (!oldData.sgvs) { return newData; } function nsArrayDiff(oldArray, newArray) { var seen = {}; @@ -203,23 +202,25 @@ function init(env, ctx) { var compressibleArrays = ['sgvs', 'treatments', 'mbgs', 'cals']; for (var array in compressibleArrays) { - var a = compressibleArrays[array]; - if (newData.hasOwnProperty(a)) { + if (compressibleArrays.hasOwnProperty(array)) { + var a = compressibleArrays[array]; + if (newData.hasOwnProperty(a)) { + + // if previous data doesn't have the property (first time delta?), just assign data over + if (!oldData.hasOwnProperty(a)) { + delta[a] = newData[a]; + changesFound = true; + continue; + } - // if previous data doesn't have the property (first time delta?), just assign data over - if (!oldData.hasOwnProperty(a)) { - delta[a] = newData[a]; - changesFound = true; - continue; - } - - // Calculate delta and assign delta over if changes were found - var deltaData = nsArrayDiff(oldData[a], newData[a]); - if (deltaData.length > 0) { - console.log('delta changes found on', a); - changesFound = true; - sort(deltaData); - delta[a] = deltaData; + // Calculate delta and assign delta over if changes were found + var deltaData = nsArrayDiff(oldData[a], newData[a]); + if (deltaData.length > 0) { + console.log('delta changes found on', a); + changesFound = true; + sort(deltaData); + delta[a] = deltaData; + } } } } @@ -238,7 +239,7 @@ function init(env, ctx) { } } - if (changesFound) return delta; + if (changesFound) { return delta; } return newData; }; diff --git a/lib/devicestatus.js b/lib/devicestatus.js index ace6a6e3b32..1357138c1b7 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -2,39 +2,40 @@ function storage (collection, ctx) { - function create(obj, fn) { - if (! obj.hasOwnProperty("created_at")){ - obj.created_at = (new Date()).toISOString(); - } - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - } - - function create_date_included(obj, fn) { - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - - } - - function last(fn) { - return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { - if (entries && entries.length > 0) - fn(err, entries[0]); - else - fn(err, null); - }); - } - - function list(opts, fn) { - var q = opts && opts.find ? opts.find : { }; - return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); - } - - function api() { - return ctx.store.db.collection(collection); + function create(obj, fn) { + if (! obj.hasOwnProperty('created_at')){ + obj.created_at = (new Date()).toISOString(); } + api().insert(obj, function (err, doc) { + fn(null, doc); + }); + } + + function create_date_included(obj, fn) { + api().insert(obj, function (err, doc) { + fn(null, doc); + }); + + } + + function last(fn) { + return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { + if (entries && entries.length > 0) { + fn(err, entries[0]); + } else { + fn(err, null); + } + }); + } + + function list(opts, fn) { + var q = opts && opts.find ? opts.find : { }; + return ctx.store.limit.call(api().find(q).sort({created_at: -1}), opts).toArray(fn); + } + + function api() { + return ctx.store.db.collection(collection); + } api.list = list; diff --git a/lib/entries.js b/lib/entries.js index 07e336b91b8..8eba532180d 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -2,7 +2,6 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); -var units = require('./units')(); var ObjectID = require('mongodb').ObjectID; /**********\ @@ -86,7 +85,7 @@ function storage(env, ctx) { // receives entire list at end of stream function done (err, result) { // report any errors - if (err) return fn(err, result); + if (err) { return fn(err, result); } // batch insert a list of records create(result, fn); } @@ -127,15 +126,17 @@ function storage(env, ctx) { function getEntry(id, fn) { with_collection(function(err, collection) { - if (err) + if (err) { fn(err); - else - collection.findOne({"_id": ObjectID(id)}, function (err, entry) { - if (err) + } else { + collection.findOne({_id: ObjectID(id)}, function (err, entry) { + if (err) { fn(err); - else + } else { fn(null, entry); + } }); + } }); } diff --git a/lib/middleware/send-json-status.js b/lib/middleware/send-json-status.js index 974fdc70af0..fa1e7183d09 100644 --- a/lib/middleware/send-json-status.js +++ b/lib/middleware/send-json-status.js @@ -2,17 +2,16 @@ // Craft a JSON friendly status (or error) message. function sendJSONStatus(res, status, title, description, warning) { - var json = { - status: status, - message: title, - description: description - }; + var json = { + status: status, + message: title, + description: description + }; - // Add optional warning message. - if (warning) - json.warning = warning; + // Add optional warning message. + if (warning) { json.warning = warning; } - res.status(status).json(json); + res.status(status).json(json); } function configure ( ) { diff --git a/lib/mqtt.js b/lib/mqtt.js index daa4a86be6b..9ac47f9f6bb 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -7,34 +7,34 @@ var direction = require('sgvdata/lib/utils').direction; var mqtt = require('mqtt'); var moment = require('moment'); -function process(client) { - var stream = es.through( - function _write(data) { - this.push(data); - } - ); - return stream; +function process ( ) { + var stream = es.through( + function _write(data) { + this.push(data); + } + ); + return stream; } -function every(storage) { - function iter(item, next) { - storage.create(item, next); - } +function every (storage) { + function iter(item, next) { + storage.create(item, next); + } - return es.map(iter); + return es.map(iter); } -function downloader() { - var opts = { - model: decoders.models.G4Download - , json: function (o) { - return o; - } - , payload: function (o) { - return o; - } - }; - return decoders(opts); +function downloader () { + var opts = { + model: decoders.models.G4Download + , json: function (o) { + return o; + } + , payload: function (o) { + return o; + } + }; + return decoders(opts); } function toSGV (proto, vars) { @@ -71,9 +71,9 @@ function toTimestamp (proto, receiver_time, download_time) { var record_offset = receiver_time - proto.sys_timestamp_sec; var record_time = download_time.clone( ).subtract(record_offset, 'second'); var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format( ) + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format( ) }; return obj; } @@ -121,7 +121,7 @@ function sgvSensorMerge(packet) { } else { console.info('mismatch or missing, sgv: ', sgv, ' sensor: ', sensor); //timestamps aren't close enough so add both - if (sgv) merged.push(sgv); + if (sgv) { merged.push(sgv); } //but the sensor will become and sgv now if (sensor) { sensor.type = 'sgv'; @@ -159,99 +159,99 @@ function iter_mqtt_record_stream (packet, prop, sync) { var stream = es.readArray(list || [ ]); var receiver_time = packet.receiver_system_time_sec; var download_time = moment(packet.download_timestamp); - function map(item, next) { - var timestamped = toTimestamp(item, receiver_time, download_time.clone( )); - var r = sync(item, timestamped); - if (!('type' in r)) { - r.type = prop; - } - console.log("ITEM", item, "TO", prop, r); - next(null, r); + function map(item, next) { + var timestamped = toTimestamp(item, receiver_time, download_time.clone( )); + var r = sync(item, timestamped); + if (!('type' in r)) { + r.type = prop; } - return stream.pipe(es.map(map)); + console.log('ITEM', item, 'TO', prop, r); + next(null, r); + } + return stream.pipe(es.map(map)); } function configure(env, ctx) { - var uri = env['MQTT_MONITOR']; - var opts = { - encoding: 'binary', - clean: false, - clientId: env.mqtt_client_id - }; - var client = mqtt.connect(uri, opts); - var downloads = downloader(); - client.subscribe('sgvs'); - client.subscribe('published'); - client.subscribe('/downloads/protobuf', {qos: 2}, granted); - client.subscribe('/uploader', granted); - client.subscribe('/entries/sgv', granted); - function granted() { - console.log('granted', arguments); - } + var uri = env['MQTT_MONITOR']; + var opts = { + encoding: 'binary', + clean: false, + clientId: env.mqtt_client_id + }; + var client = mqtt.connect(uri, opts); + var downloads = downloader(); + client.subscribe('sgvs'); + client.subscribe('published'); + client.subscribe('/downloads/protobuf', {qos: 2}, granted); + client.subscribe('/uploader', granted); + client.subscribe('/entries/sgv', granted); + function granted() { + console.log('granted', arguments); + } - client.on('message', function (topic, msg) { - console.log('topic', topic); - console.log(topic, 'on message', 'msg', msg.length); - switch (topic) { - case '/uploader': - console.log({type: topic, msg: msg.toString()}); - break; - case '/downloads/protobuf': - var b = new Buffer(msg, 'binary'); - console.log("BINARY", b.length, b.toString('hex')); - try { - var packet = downloads.parse(b); - if (!packet.type) { - packet.type = topic; - } - } catch (e) { - console.log("DID NOT PARSE", e); - break; - } - console.log('DOWNLOAD msg', msg.length, packet); - console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); - console.log("WRITE TO MONGO"); - var download_timestamp = moment(packet.download_timestamp); - if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING MERGED SGV TO MONGO", err, result); - })); + client.on('message', function (topic, msg) { + console.log('topic', topic); + console.log(topic, 'on message', 'msg', msg.length); + switch (topic) { + case '/uploader': + console.log({type: topic, msg: msg.toString()}); + break; + case '/downloads/protobuf': + var b = new Buffer(msg, 'binary'); + console.log('BINARY', b.length, b.toString('hex')); + try { + var packet = downloads.parse(b); + if (!packet.type) { + packet.type = topic; + } + } catch (e) { + console.log('DID NOT PARSE', e); + break; + } + console.log('DOWNLOAD msg', msg.length, packet); + console.log('download SGV', packet.sgv[0]); + console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); + console.log('WRITE TO MONGO'); + var download_timestamp = moment(packet.download_timestamp); + if (packet.download_status === 0) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING MERGED SGV TO MONGO', err, result); + })); - iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING Cal TO MONGO", err, result.length); - })); - iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log("DONE WRITING Meter TO MONGO", err, result.length); - })); - } - packet.type = "download"; - ctx.devicestatus.create({ - uploaderBattery: packet.uploader_battery, - created_at: download_timestamp.toISOString() - }, function empty(err, result) { - console.log("DONE WRITING TO MONGO devicestatus ", result, err); - }); + iter_mqtt_record_stream(packet, 'cal', toCal) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Cal TO MONGO', err, result.length); + })); + iter_mqtt_record_stream(packet, 'meter', toMeter) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Meter TO MONGO', err, result.length); + })); + } + packet.type = 'download'; + ctx.devicestatus.create({ + uploaderBattery: packet.uploader_battery, + created_at: download_timestamp.toISOString() + }, function empty(err, result) { + console.log('DONE WRITING TO MONGO devicestatus ', result, err); + }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log("Download written to mongo: ", packet) - }); + ctx.entries.create([ packet ], function empty(err, res) { + console.log('Download written to mongo: ', packet) + }); - // ctx.entries.write(packet); - break; - default: - console.log(topic, 'on message', 'msg', msg); - // ctx.entries.write(msg); - break; - } - }); - client.entries = process(client); - client.every = every; - return client; + // ctx.entries.write(packet); + break; + default: + console.log(topic, 'on message', 'msg', msg); + // ctx.entries.write(msg); + break; + } + }); + client.entries = process(client); + client.every = every; + return client; } //expose for tests that don't need to connect diff --git a/lib/notifications.js b/lib/notifications.js index 0e951cc496f..18e141c621e 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -114,7 +114,7 @@ function init (env, ctx) { }; notifications.snoozedBy = function snoozedBy (notify) { - if (_.isEmpty(requests.snoozes)) return false; + if (_.isEmpty(requests.snoozes)) { return false; } var byLevel = _.filter(requests.snoozes, function checkSnooze (snooze) { return snooze.level >= notify.level; diff --git a/lib/pebble.js b/lib/pebble.js index b13280a9a52..6dce18ad4a3 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -13,10 +13,10 @@ var DIRECTIONS = { , 'RATE OUT OF RANGE': 9 }; -var iob = require("./plugins/iob")(); +var iob = require('./plugins/iob')(); var async = require('async'); var units = require('./units')(); -var profileObject = require("./profilefunctions")(); +var profileObject = require('./profilefunctions')(); function directionToTrend (direction) { var trend = 8; @@ -47,7 +47,7 @@ function pebble (req, res) { //for compatibility we're keeping battery and iob here, but they would be better somewhere else if (sgvData.length > 0) { - sgvData[0].battery = uploaderBattery ? "" + uploaderBattery : undefined; + sgvData[0].battery = uploaderBattery ? '' + uploaderBattery : undefined; if (req.iob) { sgvData[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; } @@ -67,7 +67,7 @@ function pebble (req, res) { if (!err && value) { uploaderBattery = value.uploaderBattery; } else { - console.error("req.devicestatus.tail", err); + console.error('req.devicestatus.tail', err); } callback(); }); @@ -89,7 +89,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble profile error", arguments); + console.error('pebble profile error', arguments); } callback(); }); @@ -109,7 +109,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble cal error", arguments); + console.error('pebble cal error', arguments); } callback(); }); @@ -118,7 +118,7 @@ function pebble (req, res) { } } , entries: function(callback) { - var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; + var q = { count: req.count + 1, find: {sgv: { $exists: true }} }; req.ctx.entries.list(q, function(err, results) { if (!err && results) { @@ -151,7 +151,7 @@ function pebble (req, res) { } }); } else { - console.error("pebble entries error", arguments); + console.error('pebble entries error', arguments); } callback(); }); @@ -162,7 +162,7 @@ function pebble (req, res) { function loadTreatments(req, earliest_data, fn) { if (req.iob) { - var q = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; + var q = { find: {created_at: {$gte: new Date(earliest_data).toISOString()}} }; req.ctx.treatments.list(q, fn); } else { fn(null, []); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dae234d15ce..3ee91c1aed4 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -65,11 +65,11 @@ function init() { if (max > sbx.scaleBg(sbx.thresholds.bg_target_top)) { rangeLabel = 'HIGH'; eventName = 'high'; - if (!result.pushoverSound) result.pushoverSound = 'climb'; + if (!result.pushoverSound) { result.pushoverSound = 'climb'; } } else if (min < sbx.scaleBg(sbx.thresholds.bg_target_bottom)) { rangeLabel = 'LOW'; eventName = 'low'; - if (!result.pushoverSound) result.pushoverSound = 'falling'; + if (!result.pushoverSound) { result.pushoverSound = 'falling'; } } else { rangeLabel = 'Check BG'; } diff --git a/lib/plugins/basalprofile.js b/lib/plugins/basalprofile.js index 2d0851becbd..9919b153d52 100644 --- a/lib/plugins/basalprofile.js +++ b/lib/plugins/basalprofile.js @@ -1,7 +1,5 @@ 'use strict'; -var _ = require('lodash'); - function init() { function basal() { @@ -13,7 +11,7 @@ function init() { function hasRequiredInfo (sbx) { - if (!sbx.data.profile) return false; + if (!sbx.data.profile) { return false; } if (!sbx.data.profile.hasData()) { console.warn('For the Basal plugin to function you need a treatment profile'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9d3210a4598..20cd97a4929 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -2,9 +2,7 @@ var _ = require('lodash'); - var TEN_MINS = 10 * 60 * 1000; -var FIFTEEN_MINS = 15 * 60 * 1000; function init() { @@ -17,7 +15,7 @@ function init() { function hasRequiredInfo (sbx) { - if (!sbx.data.profile) return false; + if (!sbx.data.profile) { return false; } if (!sbx.data.profile.hasData()) { console.warn('For the BolusWizardPreview plugin to function you need a treatment profile'); @@ -57,9 +55,9 @@ function init() { bwp.checkNotifications = function checkNotifications (sbx) { var results = sbx.properties.bwp; - if (results == undefined) return; + if (results == undefined) { return; } - if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) return; + if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) { return; } var snoozeBWP = Number(sbx.extendedSettings.snooze) || 0.10; var warnBWP = Number(sbx.extendedSettings.warn) || 0.50; @@ -121,7 +119,7 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { var results = sbx.properties.bwp; - if (results == undefined) return; + if (results == undefined) { return; } // display text var info = [ diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 50182984ccd..58e6eab291e 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -38,7 +38,7 @@ function init() { }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') info.push({label: 'Notes:', value: message}); + if (message != '') { info.push({label: 'Notes:', value: message}); } sbx.pluginBase.updatePillText(cage, { value: age + 'h' diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 8969d2e1246..599f9d60cad 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -35,7 +35,11 @@ function init() { var liverSensRatio = 1; var totalCOB = 0; var lastCarbs = null; - if (!treatments) return {}; + + if (!treatments) { + return {}; + } + if (typeof time === 'undefined') { time = new Date(); } @@ -147,7 +151,7 @@ function init() { var prop = sbx.properties.cob; - if (prop == undefined || prop.cob == undefined) return; + if (prop == undefined || prop.cob == undefined) { return; } var displayCob = Math.round(prop.cob * 10) / 10; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 6c571cf3fe5..5c099bcf848 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -24,7 +24,7 @@ function init() { var totalIOB = 0 , totalActivity = 0; - if (!treatments) return {}; + if (!treatments) { return {}; } if (time === undefined) { time = new Date(); @@ -38,8 +38,8 @@ function init() { if (tIOB.iobContrib > 0) { lastBolus = treatment; } - if (tIOB && tIOB.iobContrib) totalIOB += tIOB.iobContrib; - if (tIOB && tIOB.activityContrib) totalActivity += tIOB.activityContrib; + if (tIOB && tIOB.iobContrib) { totalIOB += tIOB.iobContrib; } + if (tIOB && tIOB.activityContrib) { totalActivity += tIOB.activityContrib; } } }); diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 18800551b8a..3f4984ced6d 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -21,7 +21,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { container = minorPills } - var pillName = "span.pill." + plugin.name; + var pillName = 'span.pill.' + plugin.name; var pill = container.find(pillName); var classes = 'pill ' + plugin.name; diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index c68346b152a..aa6ad0694dc 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -33,8 +33,8 @@ function init() { autoSnoozeAlarms(sbx); //and add some info notifications //the notification providers (push, websockets, etc) are responsible to not sending the same notifications repeatedly - if (mbgCurrent) requestMBGNotify(lastMBG, sbx); - if (treatmentCurrent) requestTreatmentNotify(lastTreatment, sbx); + if (mbgCurrent) { requestMBGNotify(lastMBG, sbx); } + if (treatmentCurrent) { requestTreatmentNotify(lastTreatment, sbx); } } }; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index 8167bb309e3..aa1e1ab31ec 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -10,18 +10,17 @@ function init(profileData) { } profile.loadData = function loadData(profileData) { - profile.data = _.cloneDeep(profileData); - profile.preprocessProfileOnLoad(profile.data[0]); - } + profile.data = _.cloneDeep(profileData); + profile.preprocessProfileOnLoad(profile.data[0]); + }; profile.timeStringToSeconds = function timeStringToSeconds(time) { - var split = time.split(":"); + var split = time.split(':'); return parseInt(split[0])*3600 + parseInt(split[1])*60; - } + }; // preprocess the timestamps to seconds for a couple orders of magnitude faster operation - profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) - { + profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { for (var key in container) { var value = container[key]; @@ -31,17 +30,16 @@ function init(profileData) { if (value.time) { var sec = profile.timeStringToSeconds(value.time); - if (!isNaN(sec)) value.timeAsSeconds = sec; + if (!isNaN(sec)) { value.timeAsSeconds = sec; } } } - } + }; - if (profileData) profile.loadData(profileData); + if (profileData) { profile.loadData(profileData); } - profile.getValueByTime = function getValueByTime(time, valueContainer) - { - if (!time) time = new Date(); + profile.getValueByTime = function getValueByTime (time, valueContainer) { + if (!time) { time = new Date(); } // If the container is an Array, assume it's a valid timestamped value container diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 32bcf4d2354..6546dc592e1 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var crypto = require('crypto'); -var units = require('./units')(); var NodeCache = require('node-cache'); function init(env, ctx) { @@ -53,7 +52,7 @@ function init(env, ctx) { }; pushnotify.pushoverAck = function pushoverAck (response) { - if (!response.receipt) return false; + if (!response.receipt) { return false; } var notify = receipts.get(response.receipt); console.info('push ack, response: ', response, ', notify: ', notify); @@ -67,7 +66,7 @@ function init(env, ctx) { var receiptKeys = receipts.keys(); _.forEach(receiptKeys, function eachKey (receipt) { - ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err, response) { + ctx.pushover.cancelWithReceipt(receipt, function cancelCallback (err) { if (err) { console.error('error canceling receipt, err: ', err); } else { diff --git a/lib/sandbox.js b/lib/sandbox.js index c62586a8440..0aa57eb2a08 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -2,7 +2,6 @@ var _ = require('lodash'); var units = require('./units')(); -var utils = require('./utils'); var profile = require('./profilefunctions')(); function init ( ) { @@ -140,7 +139,9 @@ function init ( ) { function roundInsulinForDisplayFormat (insulin) { - if (insulin == 0) return '0'; + if (insulin == 0) { + return '0'; + } if (sbx.properties.roundingStyle == 'medtronic') { var denominator = 0.1; @@ -161,8 +162,7 @@ function init ( ) { } function unitsLabel ( ) { - if (sbx.units == 'mmol') return 'mmol/L'; - return 'mg/dl'; + return sbx.units == 'mmol' ? 'mmol/L' : 'mg/dl'; } function roundBGToDisplayFormat (bg) { diff --git a/lib/storage.js b/lib/storage.js index 77c2fa33a30..30bfd60b0b1 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -37,8 +37,9 @@ function init (env, cb, forceNewConnection) { mongo.db = connection; // If there is a valid callback, then invoke the function to perform the callback - if (cb && cb.call) + if (cb && cb.call) { cb(err, mongo); + } }); } } diff --git a/lib/treatments.js b/lib/treatments.js index 98821fd858a..ff9c3c989d0 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -23,10 +23,10 @@ function storage (env, ctx) { // clean data delete obj.eventTime; - if (!obj.carbs) delete obj.carbs; - if (!obj.insulin) delete obj.insulin; - if (!obj.notes) delete obj.notes; - if (!obj.preBolus || obj.preBolus == 0) delete obj.preBolus; + if (!obj.carbs) { delete obj.carbs; } + if (!obj.insulin) { delete obj.insulin; } + if (!obj.notes) { delete obj.notes; } + if (!obj.preBolus || obj.preBolus == 0) { delete obj.preBolus; } if (!obj.glucose) { delete obj.glucose; delete obj.glucoseType; diff --git a/lib/units.js b/lib/units.js index 031685f2280..0a86f9db06f 100644 --- a/lib/units.js +++ b/lib/units.js @@ -1,3 +1,5 @@ +'use strict'; + function mgdlToMMOL(mgdl) { return (Math.round((mgdl / 18) * 10) / 10).toFixed(1); } diff --git a/lib/utils.js b/lib/utils.js index 35761d80042..328c492b71b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -25,15 +25,25 @@ function init() { , offset = time == -1 ? -1 : (now - time) / 1000 , parts = {}; - if (offset < MINUTE_IN_SECS * -5) parts = { value: 'in the future' }; - else if (offset == -1) parts = { label: 'time ago' }; - else if (offset <= MINUTE_IN_SECS * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE_IN_SECS * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; - else if (offset < (HOUR_IN_SECS * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR_IN_SECS * 24)) parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; - else if (offset < DAY_IN_SECS) parts = { value: 1, label: 'day ago' }; - else if (offset <= (DAY_IN_SECS * 7)) parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; - else parts = { value: 'long ago' }; + if (offset < MINUTE_IN_SECS * -5) { + parts = { value: 'in the future' }; + } else if (offset == -1) { + parts = { label: 'time ago' }; + } else if (offset <= MINUTE_IN_SECS * 2) { + parts = { value: 1, label: 'min ago' }; + } else if (offset < (MINUTE_IN_SECS * 60)) { + parts = { value: Math.round(Math.abs(offset / MINUTE_IN_SECS)), label: 'mins ago' }; + } else if (offset < (HOUR_IN_SECS * 2)) { + parts = { value: 1, label: 'hr ago' }; + } else if (offset < (HOUR_IN_SECS * 24)) { + parts = { value: Math.round(Math.abs(offset / HOUR_IN_SECS)), label: 'hrs ago' }; + } else if (offset < DAY_IN_SECS) { + parts = { value: 1, label: 'day ago' }; + } else if (offset <= (DAY_IN_SECS * 7)) { + parts = { value: Math.round(Math.abs(offset / DAY_IN_SECS)), label: 'day ago' }; + } else { + parts = { value: 'long ago' }; + } if (offset > DAY_IN_SECS * 7) { parts.status = 'warn'; diff --git a/lib/websocket.js b/lib/websocket.js index 0c1e116b4a6..887dae94a5a 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,8 +1,5 @@ 'use strict'; -var _ = require('lodash'); -var utils = require('./utils')(); - function init (env, ctx, server) { function websocket ( ) { @@ -56,7 +53,7 @@ function init (env, ctx, server) { var delta = ctx.data.calculateDelta(lastData); if (delta.delta) { console.log('lastData full size', JSON.stringify(lastData).length,'bytes'); - if (delta.sgvs) console.log('patientData update size', JSON.stringify(delta).length,'bytes'); + if (delta.sgvs) { console.log('patientData update size', JSON.stringify(delta).length,'bytes'); } emitData(delta); } else { console.log('delta calculation indicates no new data is present'); } } diff --git a/static/js/client.js b/static/js/client.js index 42503eb9479..1ecffededfc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -275,7 +275,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function inRetroMode() { - if (!brush) return false; + if (!brush) { + return false; + } var time = brush.extent()[1].getTime(); @@ -470,8 +472,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var dotRadius = function(type) { var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') radius *= 2; - else if (type == 'rawbg') radius = Math.min(2, radius - 1); + if (type == 'mbg') { + radius *= 2; + } else if (type == 'rawbg') { + radius = Math.min(2, radius - 1); + } return radius / focusRangeAdjustment; }; @@ -493,7 +498,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { if (d.type == 'mbg') return 2; else return 0; }) + .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) .attr('stroke', function (d) { return (isDexcom(d.device) ? 'white' : '#0099ff'); }) @@ -512,33 +517,34 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // if new circle then just display prepareFocusCircles(focusCircles.enter().append('circle')) .on('mouseover', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - - var bgType = (d.type == 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawbgValue = 0 - , noiseLabel = ''; - - if (d.type == 'sgv') { - if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { - rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + if (d.type === 'sgv' || d.type === 'mbg') { + var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawbgValue = 0 + , noiseLabel = ''; + + if (d.type === 'sgv') { + if (rawbg.showRawBGs(d.y, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + } + noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); } - noiseLabel = rawbg.noiseCodeToDisplay(d.y, d.noise); - } - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + - (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(d.date)) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + d.sgv + + (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(d.date)) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + } }) .on('mouseout', function (d) { - if (d.type != 'sgv' && d.type != 'mbg') return; - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); + if (d.type === 'sgv' || d.type === 'mbg') { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + } }); focusCircles.exit() @@ -963,9 +969,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) {if (d.type == 'mbg') return 2; else return 0; }) - .attr('stroke', function (d) { return 'white'; }) - .attr('r', function(d) { if (d.type == 'mbg') return 4; else return 2;}); + .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke', function ( ) { return 'white'; }) + .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); if (badData.length > 0) { console.warn("Bad Data: isNaN(sgv)", badData); @@ -1138,10 +1144,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function drawTreatment(treatment, scale, showValues) { - if (!treatment.carbs && !treatment.insulin) return; + if (!treatment.carbs && !treatment.insulin) { return; } // don't render the treatment if it's not visible - if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) return; + if (Math.abs(xScale(treatment.created_at.getTime())) > window.innerWidth) { return; } var CR = treatment.CR || 20; var carbs = treatment.carbs || CR; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 5667070df51..f33182d432e 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -155,7 +155,7 @@ function closeDrawer(id, callback) { $("html, body").animate({ scrollTop: 0 }); $(id).animate({right: '-300px'}, 300, function () { $(id).css('display', 'none'); - if (callback) callback(); + if (callback) { callback(); } }); } @@ -173,7 +173,7 @@ function toggleDrawer(id, openCallback, closeCallback) { closeOpenDraw(function () { openDraw = id; $(id).css('display', 'block').animate({right: '0'}, 300, function () { - if (callback) callback(); + if (callback) { callback(); } }); }); @@ -205,8 +205,8 @@ function currentTime() { var hours = now.getHours(); var minutes = now.getMinutes(); - if (hours<10) hours = '0' + hours; - if (minutes<10) minutes = '0' + minutes; + if (hours < 10) { hours = '0' + hours; } + if (minutes < 10) { minutes = '0' + minutes; } return ''+ hours + ':' + minutes; } diff --git a/testing/convert-treatments.js b/testing/convert-treatments.js index 1ef65786be9..3c3cbf17f6e 100644 --- a/testing/convert-treatments.js +++ b/testing/convert-treatments.js @@ -1,16 +1,16 @@ db.treatments.find().forEach( - function (elem) { - db.treatments.update( - { - _id: elem._id - }, - { - $set: { - glucose: elem.glucoseValue, - insulin: elem.insulinGiven, - carbs: elem.carbsGiven - } - } - ); - } + function (elem) { + db.treatments.update( + { + _id: elem._id + }, + { + $set: { + glucose: elem.glucoseValue, + insulin: elem.insulinGiven, + carbs: elem.carbsGiven + } + } + ); + } ); diff --git a/testing/make_high_data.js b/testing/make_high_data.js index 8cbf1917859..80f48d434e3 100644 --- a/testing/make_high_data.js +++ b/testing/make_high_data.js @@ -1,6 +1,6 @@ var fs = require('fs'); -var data = "" +var data = '' var END_TIME = Date.now(); var FIVE_MINS_IN_MS = 300000; var TIME_PERIOD_HRS = 24; @@ -10,17 +10,17 @@ var currentBG = START_BG; var currentTime = END_TIME - (TIME_PERIOD_HRS * DATA_PER_HR * FIVE_MINS_IN_MS); for(var i = 0; i < TIME_PERIOD_HRS * DATA_PER_HR; i++) { - currentBG += Math.ceil(Math.cos(i)*5+.2); - currentTime += FIVE_MINS_IN_MS; - data += "1," + currentBG + ",,,,,,,,," + new Date(currentTime).toString() + ",,,,\n"; + currentBG += Math.ceil(Math.cos(i)*5+.2); + currentTime += FIVE_MINS_IN_MS; + data += '1,' + currentBG + ',,,,,,,,,' + new Date(currentTime).toString() + ',,,,\n'; } -fs.writeFile("../Dexcom.csv", data); +fs.writeFile('../Dexcom.csv', data); function makedata() { - currentBG -= 1; - currentTime += FIVE_MINS_IN_MS; - data += "1," + currentBG + ",,,,,,,,," + new Date(currentTime).toString() + ",,,,\n"; - fs.writeFile("../Dexcom.csv", data); + currentBG -= 1; + currentTime += FIVE_MINS_IN_MS; + data += '1,' + currentBG + ',,,,,,,,,' + new Date(currentTime).toString() + ',,,,\n'; + fs.writeFile('../Dexcom.csv', data); } setInterval(makedata, 1000 * 10) \ No newline at end of file diff --git a/testing/populate.js b/testing/populate.js index 67d133d7599..61bbadf0342 100644 --- a/testing/populate.js +++ b/testing/populate.js @@ -1,7 +1,6 @@ 'use strict'; var mongodb = require('mongodb'); -var software = require('./../package.json'); var env = require('./../env')(); var util = require('./helpers/util'); @@ -9,26 +8,26 @@ var util = require('./helpers/util'); main(); function main() { - var MongoClient = mongodb.MongoClient; - MongoClient.connect(env.mongo, function connected(err, db) { + var MongoClient = mongodb.MongoClient; + MongoClient.connect(env.mongo, function connected(err, db) { - console.log("Connecting to mongo..."); - if (err) { - console.log("Error occurred: ", err); - throw err; - } - populate_collection(db); - }); + console.log('Connecting to mongo...'); + if (err) { + console.log('Error occurred: ', err); + throw err; + } + populate_collection(db); + }); } function populate_collection(db) { - var cgm_collection = db.collection(env.mongo_collection); - var new_cgm_record = util.get_cgm_record(); + var cgm_collection = db.collection(env.mongo_collection); + var new_cgm_record = util.get_cgm_record(); - cgm_collection.insert(new_cgm_record, function (err, created) { - if (err) { - throw err; - } - process.exit(0); - }); + cgm_collection.insert(new_cgm_record, function (err) { + if (err) { + throw err; + } + process.exit(0); + }); } diff --git a/testing/populate_rest.js b/testing/populate_rest.js index fc8e614d7c9..703e3ea124a 100644 --- a/testing/populate_rest.js +++ b/testing/populate_rest.js @@ -1,6 +1,5 @@ 'use strict'; -var software = require('./../package.json'); var env = require('./../env')(); var http = require('http'); var util = require('./util'); @@ -8,34 +7,34 @@ var util = require('./util'); main(); function main() { - send_entry_rest(); + send_entry_rest(); } function send_entry_rest() { - var new_cgm_record = util.get_cgm_record(); - var new_cgm_record_string = JSON.stringify(new_cgm_record); - - var options = { - host: 'localhost', - port: env.PORT, - path: '/api/v1/entries/', - method: 'POST', - headers: { - 'api-secret' : env.api_secret, - 'Content-Type': 'application/json', - 'Content-Length': new_cgm_record_string.length - } - }; - - var req = http.request(options, function(res) { - console.log("Ok: ", res.statusCode); - }); - - req.on('error', function(e) { - console.error('error'); - console.error(e); - }); - - req.write(new_cgm_record_string); - req.end(); + var new_cgm_record = util.get_cgm_record(); + var new_cgm_record_string = JSON.stringify(new_cgm_record); + + var options = { + host: 'localhost', + port: env.PORT, + path: '/api/v1/entries/', + method: 'POST', + headers: { + 'api-secret' : env.api_secret, + 'Content-Type': 'application/json', + 'Content-Length': new_cgm_record_string.length + } + }; + + var req = http.request(options, function(res) { + console.log("Ok: ", res.statusCode); + }); + + req.on('error', function(e) { + console.error('error'); + console.error(e); + }); + + req.write(new_cgm_record_string); + req.end(); } \ No newline at end of file diff --git a/testing/util.js b/testing/util.js index b434fc627a3..25348e1566a 100644 --- a/testing/util.js +++ b/testing/util.js @@ -1,63 +1,72 @@ 'use strict'; exports.get_cgm_record = function() { - var dateobj = new Date(); - var datemil = dateobj.getTime(); - var datesec = datemil / 1000; - var datestr = getDateString(dateobj); - - // We put the time in a range from -1 to +1 for every thiry minute period - var range = (datesec % 1800) / 900 - 1.0; - - // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) - var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; - var dir = range > 0.0 ? "FortyFiveDown" : "FortyFiveUp"; - - console.log('Writing Record: '); - console.log('sgv = ' + sgv); - console.log('date = ' + datemil); - console.log('dir = ' + dir); - console.log('str = ' + datestr); - - return { - 'device': 'dexcom', - 'date': datemil, - 'sgv': sgv, - 'direction': dir, - 'dateString': datestr - }; -} + var dateobj = new Date(); + var datemil = dateobj.getTime(); + var datesec = datemil / 1000; + var datestr = getDateString(dateobj); + // We put the time in a range from -1 to +1 for every thiry minute period + var range = (datesec % 1800) / 900 - 1.0; + + // The we push through a COS function and scale between 40 and 400 (so it is like a bg level) + var sgv = Math.floor(360 * (Math.cos(10.0 * range / 3.14) / 2 + 0.5)) + 40; + var dir = range > 0.0 ? 'FortyFiveDown' : 'FortyFiveUp'; + + console.log('Writing Record: '); + console.log('sgv = ' + sgv); + console.log('date = ' + datemil); + console.log('dir = ' + dir); + console.log('str = ' + datestr); + + return { + 'device': 'dexcom', + 'date': datemil, + 'sgv': sgv, + 'direction': dir, + 'dateString': datestr + }; +}; + +//TODO: use moment function getDateString(d) { - // How I wish js had strftime. This would be one line of code! + // How I wish js had strftime. This would be one line of code! + + var month = d.getMonth(); + var day = d.getDay(); + var year = d.getFullYear(); + + if (month < 10) { month = '0' + month; } + if (day < 10) { day = '0' + day; } - var month = d.getMonth(); - var day = d.getDay(); - var year = d.getFullYear(); + var hour = d.getHours(); + var min = d.getMinutes(); + var sec = d.getSeconds(); - if (month < 10) month = '0' + month; - if (day < 10) day = '0' + day; + var ampm = 'PM'; + if (hour < 12) { + ampm = 'AM'; + } - var hour = d.getHours(); - var min = d.getMinutes(); - var sec = d.getSeconds(); + if (hour == 0) { + hour = 12; + } + if (hour > 12) { + hour = hour - 12; + } - var ampm = 'PM'; - if (hour < 12) { - ampm = "AM"; - } + if (hour < 10) { + hour = '0' + hour; + } - if (hour == 0) { - hour = 12; - } - if (hour > 12) { - hour = hour - 12; - } + if (min < 10) { + min = '0' + min; + } - if (hour < 10) hour = '0' + hour; - if (min < 10) min = '0' + min; - if (sec < 10) sec = '0' + sec; + if (sec < 10) { + sec = '0' + sec; + } - return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; + return month + '/' + day + '/' + year + ' ' + hour + ':' + min + ':' + sec + ' ' + ampm; } \ No newline at end of file diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 39a7c435bb8..779c32823db 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -1,6 +1,8 @@ +'use strict'; + var request = require('supertest'); -var should = require('should'); var load = require('./fixtures/load'); +require('should'); describe('Entries REST api', function ( ) { var entries = require('../lib/api/entries/'); @@ -23,10 +25,6 @@ describe('Entries REST api', function ( ) { this.archive( ).remove({ }, done); }); - it('should be a module', function ( ) { - entries.should.be.ok; - }); - // keep this test pinned at or near the top in order to validate all // entries successfully uploaded. if res.body.length is short of the // expected value, it may indicate a regression in the create diff --git a/tests/api.status.test.js b/tests/api.status.test.js index 435e6d82246..cda4caecd4c 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -1,12 +1,13 @@ +'use strict'; var request = require('supertest'); -var should = require('should'); +require('should'); describe('Status REST api', function ( ) { var api = require('../lib/api/'); before(function (done) { var env = require('../env')( ); - env.enable = "careportal rawbg"; + env.enable = 'careportal rawbg'; env.api_secret = 'this is my long pass phrase'; this.wares = require('../lib/middleware/')(env); this.app = require('express')( ); diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 2140b645391..cd4b70798bb 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('cage', function ( ) { var cage = require('../lib/plugins/cannulaage')(); diff --git a/tests/cob.test.js b/tests/cob.test.js index c34237d0823..6e383d57b0c 100644 --- a/tests/cob.test.js +++ b/tests/cob.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('COB', function ( ) { var cob = require('../lib/plugins/cob')(); @@ -17,18 +17,18 @@ describe('COB', function ( ) { var treatments = [ { - "carbs": "100", - "created_at": new Date("2015-05-29T02:03:48.827Z") + 'carbs': '100', + 'created_at': new Date('2015-05-29T02:03:48.827Z') }, { - "carbs": "10", - "created_at": new Date("2015-05-29T03:45:10.670Z") + 'carbs': '10', + 'created_at': new Date('2015-05-29T03:45:10.670Z') } ]; - var after100 = cob.cobTotal(treatments, profile, new Date("2015-05-29T02:03:49.827Z")); - var before10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:10.670Z")); - var after10 = cob.cobTotal(treatments, profile, new Date("2015-05-29T03:45:11.670Z")); + var after100 = cob.cobTotal(treatments, profile, new Date('2015-05-29T02:03:49.827Z')); + var before10 = cob.cobTotal(treatments, profile, new Date('2015-05-29T03:45:10.670Z')); + var after10 = cob.cobTotal(treatments, profile, new Date('2015-05-29T03:45:11.670Z')); after100.cob.should.equal(100); Math.round(before10.cob).should.equal(59); @@ -39,16 +39,16 @@ describe('COB', function ( ) { var treatments = [ { - "carbs": "8", - "created_at": new Date("2015-05-29T04:40:40.174Z") + 'carbs': '8', + 'created_at': new Date('2015-05-29T04:40:40.174Z') } ]; - var rightAfterCorrection = new Date("2015-05-29T04:41:40.174Z"); - var later1 = new Date("2015-05-29T05:04:40.174Z"); - var later2 = new Date("2015-05-29T05:20:00.174Z"); - var later3 = new Date("2015-05-29T05:50:00.174Z"); - var later4 = new Date("2015-05-29T06:50:00.174Z"); + var rightAfterCorrection = new Date('2015-05-29T04:41:40.174Z'); + var later1 = new Date('2015-05-29T05:04:40.174Z'); + var later2 = new Date('2015-05-29T05:20:00.174Z'); + var later3 = new Date('2015-05-29T05:50:00.174Z'); + var later4 = new Date('2015-05-29T06:50:00.174Z'); var result1 = cob.cobTotal(treatments, profile, rightAfterCorrection); var result2 = cob.cobTotal(treatments, profile, later1); @@ -70,8 +70,8 @@ describe('COB', function ( ) { var data = { treatments: [{ - carbs: "8" - , "created_at": Date.now() - 60000 //1m ago + carbs: '8' + , 'created_at': Date.now() - 60000 //1m ago }] , profile: profile }; diff --git a/tests/data.test.js b/tests/data.test.js index 27370f1fc93..d30cc637785 100644 --- a/tests/data.test.js +++ b/tests/data.test.js @@ -1,11 +1,12 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('Data', function ( ) { var env = require('../env')(); var ctx = {}; - data = require('../lib/data')(env, ctx); -// console.log(data); + var data = require('../lib/data')(env, ctx); it('should return original data if there are no changes', function() { data.sgvs = [{sgv: 100, x:100},{sgv: 100, x:99}]; diff --git a/tests/delta.test.js b/tests/delta.test.js index 788811ad01f..5d4e0790fd9 100644 --- a/tests/delta.test.js +++ b/tests/delta.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Delta', function ( ) { var delta = require('../lib/plugins/delta')(); diff --git a/tests/iob.test.js b/tests/iob.test.js index 466fe87923c..3aef85ead0e 100644 --- a/tests/iob.test.js +++ b/tests/iob.test.js @@ -1,6 +1,6 @@ -var should = require('should'); +'use strict'; -var FIVE_MINS = 10 * 60 * 1000; +require('should'); describe('IOB', function ( ) { var iob = require('../lib/plugins/iob')(); @@ -11,7 +11,7 @@ describe('IOB', function ( ) { var time = new Date() , treatments = [ { created_at: time - 1, - insulin: "1.00" + insulin: '1.00' } ]; @@ -41,7 +41,7 @@ describe('IOB', function ( ) { var treatments = [{ created_at: (new Date()) - 1, - insulin: "1.00" + insulin: '1.00' }]; var rightAfterBolus = iob.calcTotal(treatments); @@ -56,7 +56,7 @@ describe('IOB', function ( ) { var treatments = [{ created_at: time, - insulin: "5.00" + insulin: '5.00' }]; var whenApproaching0 = iob.calcTotal(treatments, undefined, new Date(time + (3 * 60 * 60 * 1000) - (90 * 1000))); @@ -71,7 +71,7 @@ describe('IOB', function ( ) { var time = new Date() , treatments = [ { created_at: time - 1, - insulin: "1.00" + insulin: '1.00' } ]; diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 76d4c3c6911..81e1ed6ea79 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); var FIVE_MINS = 5 * 60 * 1000; @@ -12,8 +14,8 @@ describe('mqtt', function ( ) { ; it('setup env correctly', function (done) { - process.env.MONGO="mongodb://localhost/test_db"; - process.env.MONGO_COLLECTION="test_sgvs"; + process.env.MONGO='mongodb://localhost/test_db'; + process.env.MONGO_COLLECTION='test_sgvs'; process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; var env = require('../env')(); env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 369d75ea7c7..d2a1cdefa6e 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -166,7 +166,7 @@ describe('Pebble Endpoint with Raw', function ( ) { var pebbleRaw = require('../lib/pebble'); before(function (done) { var envRaw = require('../env')( ); - envRaw.enable = "rawbg"; + envRaw.enable = 'rawbg'; this.appRaw = require('express')( ); this.appRaw.enable('api'); this.appRaw.use('/pebble', pebbleRaw(envRaw, ctx)); diff --git a/tests/profile.test.js b/tests/profile.test.js index b49bd5cae33..ae487ef279f 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -2,8 +2,6 @@ var should = require('should'); describe('Profile', function ( ) { - var env = require('../env')(); - var profile_empty = require('../lib/profilefunctions')(); it('should say it does not have data before it has data', function() { @@ -17,8 +15,8 @@ describe('Profile', function ( ) { }); var profileDataPartial = { - "dia": 3, - "carbs_hr": 30, + 'dia': 3 + , 'carbs_hr': 30 }; var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); @@ -29,12 +27,12 @@ describe('Profile', function ( ) { }); var profileData = { - "dia": 3, - "carbs_hr": 30, - "carbratio": 7, - "sens": 35, - "target_low": 95, - "target_high": 120 + 'dia': 3 + , 'carbs_hr': 30 + , 'carbratio': 7 + , 'sens': 35 + , 'target_low': 95 + , 'target_high': 120 }; var profile = require('../lib/profilefunctions')([profileData]); @@ -80,12 +78,12 @@ describe('Profile', function ( ) { it('should know how to reload data and still know what the low target is with old style profiles', function() { var profileData2 = { - "dia": 3, - "carbs_hr": 30, - "carbratio": 7, - "sens": 35, - "target_low": 50, - "target_high": 120 + 'dia': 3, + 'carbs_hr': 30, + 'carbratio': 7, + 'sens': 35, + 'target_low': 50, + 'target_high': 120 }; profile.loadData([profileData2]); @@ -95,69 +93,69 @@ describe('Profile', function ( ) { var complexProfileData = { - "sens": [ + 'sens': [ { - "time": "00:00", - "value": 10 + 'time': '00:00', + 'value': 10 }, { - "time": "02:00", - "value": 10 + 'time': '02:00', + 'value': 10 }, { - "time": "07:00", - "value": 9 + 'time': '07:00', + 'value': 9 } ], - "dia": 3, - "carbratio": [ + 'dia': 3, + 'carbratio': [ { - "time": "00:00", - "value": 16 + 'time': '00:00', + 'value': 16 }, { - "time": "06:00", - "value": 15 + 'time': '06:00', + 'value': 15 }, { - "time": "14:00", - "value": 16 + 'time': '14:00', + 'value': 16 } ], - "carbs_hr": 30, - "startDate": "2015-06-21", - "basal": [ + 'carbs_hr': 30, + 'startDate': '2015-06-21', + 'basal': [ { - "time": "00:00", - "value": 0.175 + 'time': '00:00', + 'value': 0.175 }, { - "time": "02:30", - "value": 0.125 + 'time': '02:30', + 'value': 0.125 }, { - "time": "05:00", - "value": 0.075 + 'time': '05:00', + 'value': 0.075 }, { - "time": "08:00", - "value": 0.1 + 'time': '08:00', + 'value': 0.1 }, { - "time": "14:00", - "value": 0.125 + 'time': '14:00', + 'value': 0.125 }, { - "time": "20:00", - "value": 0.3 + 'time': '20:00', + 'value': 0.3 }, { - "time": "22:00", - "value": 0.225 + 'time': '22:00', + 'value': 0.225 } ], - "target_low": 4.5, - "target_high": 8 + 'target_low': 4.5, + 'target_high': 8 }; var complexProfile = require('../lib/profilefunctions')([complexProfileData]); diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js index 0f81a5a5755..bbafa27651d 100644 --- a/tests/pushnotify.test.js +++ b/tests/pushnotify.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('pushnotify', function ( ) { diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index 7695c01c076..e4aec0e9c16 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Raw BG', function ( ) { var rawbg = require('../lib/plugins/rawbg')(); diff --git a/tests/security.test.js b/tests/security.test.js index d5a5b193763..d718d5ed5cf 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -85,7 +85,6 @@ describe('API_SECRET', function ( ) { }); it('should not work short', function ( ) { - var known = 'c1d117818a97e847bdf286aa02d9dc8e8f7148f5'; delete process.env.API_SECRET; process.env.API_SECRET = 'tooshort'; var env; diff --git a/tests/units.test.js b/tests/units.test.js index 3f51382e817..3f5793d327c 100644 --- a/tests/units.test.js +++ b/tests/units.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('units', function ( ) { var units = require('../lib/units')(); diff --git a/tests/upbat.test.js b/tests/upbat.test.js index 9b71d965c21..0b49a3d790c 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -1,6 +1,6 @@ 'use strict'; -var should = require('should'); +require('should'); describe('Uploader Battery', function ( ) { var data = {uploaderBattery: 20}; diff --git a/tests/utils.test.js b/tests/utils.test.js index 0b79d107ade..55b0eaad4c1 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,4 +1,6 @@ -var should = require('should'); +'use strict'; + +require('should'); describe('utils', function ( ) { var utils = require('../lib/utils')(); From 1cfdc03dbf63f4779d3446b2029782358e6f0545 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:03:23 -0700 Subject: [PATCH 258/661] added some missing sbx.scaleBg's --- lib/plugins/ar2.js | 2 +- lib/plugins/rawbg.js | 7 +++---- lib/plugins/simplealarms.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index dae234d15ce..69209a8b748 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -92,7 +92,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index cbd3410bc0a..7be7e78b61c 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -35,7 +35,7 @@ function init() { var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value - , value: prop.value + , value: sbx.scaleBg(prop.value) , label: prop.noiseLabel } : { hide: true @@ -48,7 +48,6 @@ function init() { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 - , sgv = sgv.y , scale = parseFloat(cal.scale) || 0 , intercept = parseFloat(cal.intercept) || 0 , slope = parseFloat(cal.slope) || 0; @@ -56,10 +55,10 @@ function init() { if (slope == 0 || unfiltered == 0 || scale == 0) { raw = 0; - } else if (filtered == 0 || sgv < 40) { + } else if (filtered == 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { - var ratio = scale * (filtered - intercept) / slope / sgv; + var ratio = scale * (filtered - intercept) / slope / sgv.y; raw = scale * ( unfiltered - intercept) / slope / ratio; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 7ce39c72d79..b056ac8f087 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -66,7 +66,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; From 320565b0cbd7e100edc04b2f1c1ead02c1e1ced6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:30:39 -0700 Subject: [PATCH 259/661] more easy cleanup --- bundle/bundle.source.js | 2 +- lib/api/status.js | 2 +- lib/data.js | 22 ++++++++++++---------- lib/entries.js | 2 +- lib/mqtt.js | 2 +- lib/plugins/ar2.js | 2 +- lib/plugins/boluswizardpreview.js | 2 +- lib/plugins/cob.js | 4 ++-- lib/plugins/iob.js | 2 +- lib/plugins/pluginbase.js | 2 +- lib/profilefunctions.js | 23 +++++++++++------------ lib/treatments.js | 4 +++- lib/units.js | 2 +- static/index.html | 10 ---------- static/js/client.js | 23 ++++++++++++++--------- static/js/experiments.js | 28 ---------------------------- static/js/ui-utils.js | 8 ++++---- testing/make_high_data.js | 4 ++-- testing/populate_rest.js | 2 +- tests/api.status.test.js | 4 ++-- tests/boluswizardpreview.test.js | 6 +++--- tests/delta.test.js | 2 +- tests/pebble.test.js | 2 +- tests/plugins.test.js | 2 +- tests/pushnotify.test.js | 4 ++-- tests/rawbg.test.js | 2 +- tests/security.test.js | 4 ++-- tests/units.test.js | 4 ++-- tests/upbat.test.js | 2 +- tests/utils.test.js | 2 +- 30 files changed, 75 insertions(+), 105 deletions(-) delete mode 100644 static/js/experiments.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 81693b6e81a..ae04f5f72bf 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -11,7 +11,7 @@ sandbox: require('../lib/sandbox')() }; - console.info("Nightscout bundle ready", window.Nightscout); + console.info('Nightscout bundle ready', window.Nightscout); })(); diff --git a/lib/api/status.js b/lib/api/status.js index 8b1de9ed21d..862f5909513 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -25,7 +25,7 @@ function configure (app, wares) { var badge = 'http://img.shields.io/badge/Nightscout-OK-green'; return res.format({ html: function ( ) { - res.send("

    STATUS OK

    "); + res.send('

    STATUS OK

    '); }, png: function ( ) { res.redirect(302, badge + '.png'); diff --git a/lib/data.js b/lib/data.js index dedcfe4805f..d682b292b64 100644 --- a/lib/data.js +++ b/lib/data.js @@ -95,7 +95,7 @@ function init(env, ctx) { data.sgvs = sgvs; } callback(); - }) + }); }, cal: function (callback) { //FIXME: date $gte????? var cq = {count: 1, find: {type: 'cal'}}; @@ -156,7 +156,7 @@ function init(env, ctx) { data.devicestatus.uploaderBattery = result.uploaderBattery; } callback(); - }) + }); } }, done); @@ -164,7 +164,7 @@ function init(env, ctx) { data.calculateDelta = function calculateDelta(lastData) { return data.calculateDeltaBetweenDatasets(lastData,data); - } + }; data.calculateDeltaBetweenDatasets = function calculateDeltaBetweenDatasets(oldData,newData) { @@ -178,7 +178,7 @@ function init(env, ctx) { var seen = {}; var l = oldArray.length; for (var i = 0; i < l; i++) { - seen[oldArray[i].x] = true + seen[oldArray[i].x] = true; } var result = []; l = newArray.length; @@ -229,12 +229,14 @@ function init(env, ctx) { var skippableObjects = ['profiles', 'devicestatus']; for (var object in skippableObjects) { - var o = skippableObjects[object]; - if (newData.hasOwnProperty(o)) { - if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { - console.log('delta changes found on', o); - changesFound = true; - delta[o] = newData[o]; + if (skippableObjects.hasOwnProperty(object)) { + var o = skippableObjects[object]; + if (newData.hasOwnProperty(o)) { + if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { + console.log('delta changes found on', o); + changesFound = true; + delta[o] = newData[o]; + } } } } diff --git a/lib/entries.js b/lib/entries.js index 8eba532180d..c9a52a00f03 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -43,7 +43,7 @@ function storage(env, ctx) { // determine sort options function sort ( ) { - return {"date": -1}; + return {date: -1}; // return this.sort({"date": -1}); } diff --git a/lib/mqtt.js b/lib/mqtt.js index 9ac47f9f6bb..6d3414348cd 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -237,7 +237,7 @@ function configure(env, ctx) { }); ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet) + console.log('Download written to mongo: ', packet); }); diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 3ee91c1aed4..3cc910554cc 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -31,7 +31,7 @@ function init() { var result = {}; if (lastSGVEntry && Date.now() - lastSGVEntry.x < TEN_MINUTES) { - result = ar2.forcastAndCheck(sbx.data.sgvs) + result = ar2.forcastAndCheck(sbx.data.sgvs); } var usingRaw = false; diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 20cd97a4929..819b9aaf5b5 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -70,7 +70,7 @@ function init() { level: sbx.notifications.levels.URGENT , lengthMills: snoozeLength , debug: results - }) + }); } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 599f9d60cad..1eee45074b9 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -103,7 +103,7 @@ function init() { return { netCarbImpact: netCarbImpact, totalImpact: totalImpact - } + }; }; cob.cobCalc = function cobCalc(treatment, profile, lastDecayedBy, time) { @@ -159,7 +159,7 @@ function init() { if (prop.lastCarbs) { var when = moment(new Date(prop.lastCarbs.created_at)).format('lll'); var amount = prop.lastCarbs.carbs + 'g'; - info = [{label: 'Last Carbs', value: amount + ' @ ' + when }] + info = [{label: 'Last Carbs', value: amount + ' @ ' + when }]; } sbx.pluginBase.updatePillText(sbx, { diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 5c099bcf848..d1b2fbe4eab 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -97,7 +97,7 @@ function init() { if (prop && prop.lastBolus) { var when = moment(new Date(prop.lastBolus.created_at)).format('lll'); var amount = sbx.roundInsulinForDisplayFormat(Number(prop.lastBolus.insulin)) + 'U'; - info = [{label: 'Last Bolus', value: amount + ' @ ' + when }] + info = [{label: 'Last Bolus', value: amount + ' @ ' + when }]; } sbx.pluginBase.updatePillText(iob, { diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 3f4984ced6d..3c58145144d 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -18,7 +18,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { } else if (plugin.pluginType == 'pill-alt') { container = bgStatus; } else { - container = minorPills + container = minorPills; } var pillName = 'span.pill.' + plugin.name; diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index aa1e1ab31ec..e6ac5ea0e9c 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -59,46 +59,45 @@ function init(profileData) { } return returnValue; - } + }; profile.getCurrentProfile = function getCurrentProfile() { - if (profile.hasData()) return profile.data[0]; - return {}; - } + return profile.hasData() ? profile.data[0] : {}; + }; profile.hasData = function hasData() { var rVal = false; if (profile.data) rVal = true; return (rVal); - } + }; profile.getDIA = function getDIA(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['dia']); - } + }; profile.getSensitivity = function getSensitivity(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['sens']); - } + }; profile.getCarbRatio = function getCarbRatio(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['carbratio']); - } + }; profile.getCarbAbsorptionRate = function getCarbAbsorptionRate(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['carbs_hr']); - } + }; profile.getLowBGTarget = function getLowBGTarget(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['target_low']); - } + }; profile.getHighBGTarget = function getHighBGTarget(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['target_high']); - } + }; profile.getBasal = function getBasal(time) { return profile.getValueByTime(time,profile.getCurrentProfile()['basal']); - } + }; return profile(); diff --git a/lib/treatments.js b/lib/treatments.js index ff9c3c989d0..ca6895f24e5 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -44,7 +44,9 @@ function storage (env, ctx) { carbs: preBolusCarbs }; - if (obj.notes) pbTreat.notes = obj.notes; + if (obj.notes) { + pbTreat.notes = obj.notes; + } api( ).insert(pbTreat, function() { //nothing to do here diff --git a/lib/units.js b/lib/units.js index 0a86f9db06f..cf99785ebd9 100644 --- a/lib/units.js +++ b/lib/units.js @@ -7,7 +7,7 @@ function mgdlToMMOL(mgdl) { function configure() { return { mgdlToMMOL: mgdlToMMOL - } + }; } module.exports = configure; \ No newline at end of file diff --git a/static/index.html b/static/index.html index 2b2d40ab14b..a44fca3ca42 100644 --- a/static/index.html +++ b/static/index.html @@ -141,15 +141,6 @@

    Nightscout

    Reset, and use defaults
    -
    - Experiments -
    - Hamburger?
    - - -
    -
    -
    About
    @@ -266,6 +257,5 @@

    Nightscout

    - diff --git a/static/js/client.js b/static/js/client.js index 1ecffededfc..ea7313ab7f6 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -415,7 +415,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var lookbackTime = (lookback + 2) * FIVE_MINS_IN_MS + 2 * ONE_MIN_IN_MS; nowData = nowData.filter(function(d) { return d.date.getTime() >= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS - lookbackTime && - d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS + d.date.getTime() <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; }); // sometimes nowData contains duplicates. uniq it. @@ -505,7 +505,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('r', function (d) { return dotRadius(d.type); }); if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + console.warn('Bad Data: isNaN(sgv)', badData); } return sel; @@ -974,7 +974,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); if (badData.length > 0) { - console.warn("Bad Data: isNaN(sgv)", badData); + console.warn('Bad Data: isNaN(sgv)', badData); } return sel; @@ -1121,7 +1121,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); if (treatment.units == 'mmol') { //BG is in mmol and display in mg/dl - treatmentGlucose = Math.round(treatment.glucose * 18) + treatmentGlucose = Math.round(treatment.glucose * 18); } else { //BG is in mg/dl and display in mmol treatmentGlucose = scaleBg(treatment.glucose); @@ -1158,7 +1158,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; R3 = R2 + 8 / scale; if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn("Bad Data: Found isNaN value in treatment", treatment); + console.warn('Bad Data: Found isNaN value in treatment', treatment); return; } @@ -1172,8 +1172,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; arc_data[0].outlineOnly = !treatment.carbs; arc_data[2].outlineOnly = !treatment.insulin; - if (treatment.carbs > 0) arc_data[1].element = Math.round(treatment.carbs) + ' g'; - if (treatment.insulin > 0) arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + if (treatment.carbs > 0) { + arc_data[1].element = Math.round(treatment.carbs) + ' g'; + } + + if (treatment.insulin > 0) { + arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + } var arc = d3.svg.arc() .innerRadius(function (d) { return 5 * d.inner; }) @@ -1205,8 +1210,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); var arcs = treatmentDots.append('path') .attr('class', 'path') - .attr('fill', function (d, i) { if (d.outlineOnly) return 'transparent'; else return d.color; }) - .attr('stroke-width', function (d) {if (d.outlineOnly) return 1; else return 0; }) + .attr('fill', function (d, i) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) .attr('stroke', function (d) { return d.color; }) .attr('id', function (d, i) { return 's' + i; }) .attr('d', arc); diff --git a/static/js/experiments.js b/static/js/experiments.js deleted file mode 100644 index d0e7b8aa100..00000000000 --- a/static/js/experiments.js +++ /dev/null @@ -1,28 +0,0 @@ -$(function() { - if (querystring.experiments) { - $(".experiments").show(); - if (!querystring.drawer) { - $("#drawerToggle").click(); - } - } else { - $(".experiments").hide(); - } - - $(".glyphToggle").on("click", function(){ - var newGlyph = $(this).find("i").attr("class"); - $("#drawerToggle").find("i").prop("class", newGlyph); - event.preventDefault(); - }); - - $(".iconToggle").on("click", function(){ - var newIcon = $(this).find("img").attr("src"); - $("#favicon").prop("href", newIcon); - event.preventDefault(); - }); - - $(".toolbarIconToggle").on("click", function(){ - var newIcon = $(this).find("img").attr("src"); - $("#toolbar").css({'background-image':'url('+newIcon+')'}); - event.preventDefault(); - }); -}); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index f33182d432e..bff843efa45 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -152,7 +152,7 @@ function isTouch() { function closeDrawer(id, callback) { openDraw = null; - $("html, body").animate({ scrollTop: 0 }); + $('html, body').animate({ scrollTop: 0 }); $(id).animate({right: '-300px'}, 300, function () { $(id).css('display', 'none'); if (callback) { callback(); } @@ -166,7 +166,7 @@ function toggleDrawer(id, openCallback, closeCallback) { if (openDraw) { closeDrawer(openDraw, callback); } else { - callback() + callback(); } } @@ -242,7 +242,7 @@ function showNotification(note, type) { } function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.' + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; $('.browserSettings').html('Settings'+msg+''); $('#save').hide(); } @@ -373,7 +373,7 @@ $('#notification').click(function(event) { $('#save').click(function(event) { function checkedPluginNames() { - var checkedPlugins = [] + var checkedPlugins = []; $('#show-plugins input:checked').each(function eachPluginCheckbox(index, checkbox) { checkedPlugins.push($(checkbox).val()); }); diff --git a/testing/make_high_data.js b/testing/make_high_data.js index 80f48d434e3..610b163d41a 100644 --- a/testing/make_high_data.js +++ b/testing/make_high_data.js @@ -1,6 +1,6 @@ var fs = require('fs'); -var data = '' +var data = ''; var END_TIME = Date.now(); var FIVE_MINS_IN_MS = 300000; var TIME_PERIOD_HRS = 24; @@ -23,4 +23,4 @@ function makedata() { fs.writeFile('../Dexcom.csv', data); } -setInterval(makedata, 1000 * 10) \ No newline at end of file +setInterval(makedata, 1000 * 10); \ No newline at end of file diff --git a/testing/populate_rest.js b/testing/populate_rest.js index 703e3ea124a..a3f75faddbe 100644 --- a/testing/populate_rest.js +++ b/testing/populate_rest.js @@ -27,7 +27,7 @@ function send_entry_rest() { }; var req = http.request(options, function(res) { - console.log("Ok: ", res.statusCode); + console.log('Ok: ', res.statusCode); }); req.on('error', function(e) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index cda4caecd4c..c37407048de 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -41,7 +41,7 @@ describe('Status REST api', function ( ) { .end(function(err, res) { res.type.should.equal('text/html'); res.statusCode.should.equal(200); - done() + done(); }); }); @@ -52,7 +52,7 @@ describe('Status REST api', function ( ) { res.type.should.equal('application/javascript'); res.statusCode.should.equal(200); res.text.should.startWith('this.serverSettings ='); - done() + done(); }); }); diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 9677c9d2c5e..8e9244dcbb5 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -24,7 +24,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); @@ -41,7 +41,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); @@ -58,7 +58,7 @@ describe('boluswizardpreview', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); sbx.offerProperty('iob', function () { - return {iob: 0} + return {iob: 0}; }); boluswizardpreview.setProperties(sbx); diff --git a/tests/delta.test.js b/tests/delta.test.js index 5d4e0790fd9..d5c838234f0 100644 --- a/tests/delta.test.js +++ b/tests/delta.test.js @@ -21,7 +21,7 @@ describe('Delta', function ( ) { var result = setter(); result.value.should.equal(5); result.display.should.equal('+5'); - done() + done(); }; delta.setProperties(sbx); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index d2a1cdefa6e..bf87f889a67 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -96,7 +96,7 @@ var ctx = { callback(null, {uploaderBattery: 100}); } } -} +}; describe('Pebble Endpoint without Raw', function ( ) { var pebble = require('../lib/pebble'); diff --git a/tests/plugins.test.js b/tests/plugins.test.js index c7fa5f11618..0b0fc20ac43 100644 --- a/tests/plugins.test.js +++ b/tests/plugins.test.js @@ -18,7 +18,7 @@ describe('Plugins', function ( ) { }); it('should find sever plugins, but not client only plugins', function (done) { - var plugins = require('../lib/plugins/')().registerServerDefaults() + var plugins = require('../lib/plugins/')().registerServerDefaults(); plugins('rawbg').name.should.equal('rawbg'); plugins('treatmentnotify').name.should.equal('treatmentnotify'); diff --git a/tests/pushnotify.test.js b/tests/pushnotify.test.js index bbafa27651d..4380dcd6210 100644 --- a/tests/pushnotify.test.js +++ b/tests/pushnotify.test.js @@ -26,7 +26,7 @@ describe('pushnotify', function ( ) { msg.priority.should.equal(2); msg.sound.should.equal('climb'); callback(null, JSON.stringify({receipt: 'abcd12345'})); - done() + done(); } }; @@ -60,7 +60,7 @@ describe('pushnotify', function ( ) { msg.priority.should.equal(0); msg.sound.should.equal('gamelan'); callback(null, JSON.stringify({})); - done() + done(); } }; diff --git a/tests/rawbg.test.js b/tests/rawbg.test.js index e4aec0e9c16..f7a626c3c3f 100644 --- a/tests/rawbg.test.js +++ b/tests/rawbg.test.js @@ -24,7 +24,7 @@ describe('Raw BG', function ( ) { var result = setter(); result.value.should.equal(113); result.noiseLabel.should.equal('Clean'); - done() + done(); }; rawbg.setProperties(sbx); diff --git a/tests/security.test.js b/tests/security.test.js index d718d5ed5cf..bf8d25fc68d 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -103,7 +103,7 @@ describe('API_SECRET', function ( ) { res.body.status.should.equal('ok'); fn( ); // console.log('err', err, 'res', res); - }) + }); } function ping_authorized_endpoint (app, fails, fn) { @@ -117,7 +117,7 @@ describe('API_SECRET', function ( ) { } fn( ); // console.log('err', err, 'res', res); - }) + }); } }); diff --git a/tests/units.test.js b/tests/units.test.js index 3f5793d327c..b6e8a9faa8f 100644 --- a/tests/units.test.js +++ b/tests/units.test.js @@ -6,11 +6,11 @@ describe('units', function ( ) { var units = require('../lib/units')(); it('should convert 99 to 5.5', function () { - units.mgdlToMMOL(99).should.equal('5.5') + units.mgdlToMMOL(99).should.equal('5.5'); }); it('should convert 180 to 10.0', function () { - units.mgdlToMMOL(180).should.equal('10.0') + units.mgdlToMMOL(180).should.equal('10.0'); }); }); diff --git a/tests/upbat.test.js b/tests/upbat.test.js index 0b49a3d790c..668e3c9cfc4 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -18,7 +18,7 @@ describe('Uploader Battery', function ( ) { result.display.should.equal('20%'); result.status.should.equal('urgent'); result.level.should.equal(25); - done() + done(); }; var upbat = require('../lib/plugins/upbat')(); diff --git a/tests/utils.test.js b/tests/utils.test.js index 55b0eaad4c1..3dab434df78 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -11,7 +11,7 @@ describe('utils', function ( ) { }; it('format numbers', function () { - utils.toFixed(5.499999999).should.equal('5.50') + utils.toFixed(5.499999999).should.equal('5.50'); }); it('show format recent times to 1 minute', function () { From 61d235953b365b8a58d162a832ce57b60668070d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:03:23 -0700 Subject: [PATCH 260/661] added some missing sbx.scaleBg's --- lib/plugins/ar2.js | 2 +- lib/plugins/rawbg.js | 7 +++---- lib/plugins/simplealarms.js | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 3cc910554cc..855dd35164b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -92,7 +92,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index cbd3410bc0a..7be7e78b61c 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -35,7 +35,7 @@ function init() { var options = prop && prop.sgv && rawbg.showRawBGs(prop.sgv.y, prop.sgv.noise, prop.cal, sbx) ? { hide: !prop || !prop.value - , value: prop.value + , value: sbx.scaleBg(prop.value) , label: prop.noiseLabel } : { hide: true @@ -48,7 +48,6 @@ function init() { var raw = 0 , unfiltered = parseInt(sgv.unfiltered) || 0 , filtered = parseInt(sgv.filtered) || 0 - , sgv = sgv.y , scale = parseFloat(cal.scale) || 0 , intercept = parseFloat(cal.intercept) || 0 , slope = parseFloat(cal.slope) || 0; @@ -56,10 +55,10 @@ function init() { if (slope == 0 || unfiltered == 0 || scale == 0) { raw = 0; - } else if (filtered == 0 || sgv < 40) { + } else if (filtered == 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { - var ratio = scale * (filtered - intercept) / slope / sgv; + var ratio = scale * (filtered - intercept) / slope / sgv.y; raw = scale * ( unfiltered - intercept) / slope / ratio; } diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index 7ce39c72d79..b056ac8f087 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -66,7 +66,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; From beeeebdf182d1b83e50f18509db26fbacb9810d9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 00:54:46 -0700 Subject: [PATCH 261/661] more cleanup --- lib/plugins/boluswizardpreview.js | 8 ++++---- lib/plugins/index.js | 18 ++++++++++++------ lib/plugins/rawbg.js | 8 ++++---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 819b9aaf5b5..8b2027241a1 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -55,7 +55,7 @@ function init() { bwp.checkNotifications = function checkNotifications (sbx) { var results = sbx.properties.bwp; - if (results == undefined) { return; } + if (results === undefined) { return; } if (results.lastSGV < sbx.data.profile.getHighBGTarget(sbx.time)) { return; } @@ -74,7 +74,7 @@ function init() { } else if (results.bolusEstimate > warnBWP) { var level = results.bolusEstimate > urgentBWP ? sbx.notifications.levels.URGENT : sbx.notifications.levels.WARN; var levelLabel = sbx.notifications.levels.toString(level); - var sound = level == sbx.notifications.levels.URGENT ? 'updown' : 'bike'; + var sound = level === sbx.notifications.levels.URGENT ? 'updown' : 'bike'; var lines = ['BG Now: ' + results.displaySGV]; @@ -119,7 +119,7 @@ function init() { bwp.updateVisualisation = function updateVisualisation (sbx) { var results = sbx.properties.bwp; - if (results == undefined) { return; } + if (results === undefined) { return; } // display text var info = [ @@ -189,7 +189,7 @@ function init() { results.bolusEstimate = delta / sens * -1; } - if (results.bolusEstimate != 0 && sbx.data.profile.getBasal()) { + if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal()) { // Basal profile exists, calculate % change var basal = sbx.data.profile.getBasal(sbx.time); diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 2f45802de78..d92c37b0b8e 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -56,7 +56,7 @@ function init() { }); }; - plugins.init = function init(envOrApp) { + plugins.init = function initPlugins (envOrApp) { enabledPlugins = []; function isEnabled(plugin) { //TODO: unify client/server env/app @@ -96,25 +96,31 @@ function init() { plugins.hasShownType = function hasShownType(pluginType, sbx) { return _.find(plugins.shownPlugins(sbx), function findWithType(plugin) { - return plugin.pluginType == pluginType; - }) != undefined; + return plugin.pluginType === pluginType; + }) !== undefined; }; plugins.setProperties = function setProperties(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.setProperties && plugin.setProperties(sbx.withExtendedSettings(plugin)); + if (plugin.setProperties) { + plugin.setProperties(sbx.withExtendedSettings(plugin)); + } }); }; plugins.checkNotifications = function checkNotifications(sbx) { plugins.eachEnabledPlugin( function eachPlugin (plugin) { - plugin.checkNotifications && plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + if (plugin.checkNotifications) { + plugin.checkNotifications(sbx.withExtendedSettings(plugin)); + } }); }; plugins.updateVisualisations = function updateVisualisations(sbx) { plugins.eachShownPlugins(sbx, function eachPlugin(plugin) { - plugin.updateVisualisation && plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + if (plugin.updateVisualisation) { + plugin.updateVisualisation(sbx.withExtendedSettings(plugin)); + } }); }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 7be7e78b61c..a3635017805 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -53,9 +53,9 @@ function init() { , slope = parseFloat(cal.slope) || 0; - if (slope == 0 || unfiltered == 0 || scale == 0) { + if (slope === 0 || unfiltered === 0 || scale === 0) { raw = 0; - } else if (filtered == 0 || sgv.y < 40) { + } else if (filtered === 0 || sgv.y < 40) { raw = scale * (unfiltered - intercept) / slope; } else { var ratio = scale * (filtered - intercept) / slope / sgv.y; @@ -72,8 +72,8 @@ function init() { rawbg.showRawBGs = function showRawBGs(sgv, noise, cal, sbx) { return cal && rawbg.isEnabled(sbx) - && (sbx.defaults.showRawbg == 'always' - || (sbx.defaults.showRawbg == 'noise' && (noise >= 2 || sgv < 40)) + && (sbx.defaults.showRawbg === 'always' + || (sbx.defaults.showRawbg === 'noise' && (noise >= 2 || sgv < 40)) ); }; From 48c2a2a3856258b0e0752a0264573c9ce8f83504 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 09:26:31 -0700 Subject: [PATCH 262/661] mqtt fixes for issues reported by codacy --- lib/mqtt.js | 67 +++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 6d3414348cd..078d93eb681 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -105,7 +105,7 @@ function sgvSensorMerge(packet) { , sgvsLength = sgvs.length , sensorsLength = sensors.length; - if (sgvsLength >= 0 && sensorsLength == 0) { + if (sgvsLength >= 0 && sensorsLength === 0) { merged = sgvs; } else { var smallerLength = Math.min(sgvsLength, sensorsLength); @@ -204,45 +204,42 @@ function configure(env, ctx) { var packet = downloads.parse(b); if (!packet.type) { packet.type = topic; - } - } catch (e) { - console.log('DID NOT PARSE', e); - break; - } - console.log('DOWNLOAD msg', msg.length, packet); - console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); - console.log('WRITE TO MONGO'); - var download_timestamp = moment(packet.download_timestamp); - if (packet.download_status === 0) { - es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING MERGED SGV TO MONGO', err, result); - })); - iter_mqtt_record_stream(packet, 'cal', toCal) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Cal TO MONGO', err, result.length); - })); - iter_mqtt_record_stream(packet, 'meter', toMeter) - .pipe(ctx.entries.persist(function empty(err, result) { - console.log('DONE WRITING Meter TO MONGO', err, result.length); + } + console.log('DOWNLOAD msg', msg.length, packet); + console.log('download SGV', packet.sgv[0]); + console.log('download_timestamp', packet.download_timestamp, new Date(Date.parse(packet.download_timestamp))); + console.log('WRITE TO MONGO'); + var download_timestamp = moment(packet.download_timestamp); + if (packet.download_status === 0) { + es.readArray(sgvSensorMerge(packet)).pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING MERGED SGV TO MONGO', err, result); })); - } - packet.type = 'download'; - ctx.devicestatus.create({ - uploaderBattery: packet.uploader_battery, - created_at: download_timestamp.toISOString() - }, function empty(err, result) { - console.log('DONE WRITING TO MONGO devicestatus ', result, err); - }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet); - }); + iter_mqtt_record_stream(packet, 'cal', toCal) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Cal TO MONGO', err, result.length); + })); + iter_mqtt_record_stream(packet, 'meter', toMeter) + .pipe(ctx.entries.persist(function empty(err, result) { + console.log('DONE WRITING Meter TO MONGO', err, result.length); + })); + } + packet.type = 'download'; + ctx.devicestatus.create({ + uploaderBattery: packet.uploader_battery, + created_at: download_timestamp.toISOString() + }, function empty(err, result) { + console.log('DONE WRITING TO MONGO devicestatus ', result, err); + }); + ctx.entries.create([ packet ], function empty(err, res) { + console.log('Download written to mongo: ', packet); + }); + } catch (e) { + console.log('DID NOT PARSE', e); + } - // ctx.entries.write(packet); - break; default: console.log(topic, 'on message', 'msg', msg); // ctx.entries.write(msg); From bd9759a489983b523c492028088d7aa7ed7baa98 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 09:34:31 -0700 Subject: [PATCH 263/661] added back break that was lost --- lib/mqtt.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mqtt.js b/lib/mqtt.js index 078d93eb681..e3beafc061d 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -240,6 +240,8 @@ function configure(env, ctx) { console.log('DID NOT PARSE', e); } + break; + default: console.log(topic, 'on message', 'msg', msg); // ctx.entries.write(msg); From 853fb60c21225b81f7bda76d8624f0b9d7bbee82 Mon Sep 17 00:00:00 2001 From: Paul Andrel Date: Wed, 1 Jul 2015 13:15:25 -0400 Subject: [PATCH 264/661] Notifications generated by BWP were missing a sbx.scaleBg() around raw values --- lib/plugins/boluswizardpreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 9d3210a4598..87109384768 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -88,7 +88,7 @@ function init() { var rawbgProp = sbx.properties.rawbg; if (rawbgProp) { - lines.push(['Raw BG:', rawbgProp.value, sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); + lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); } lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); From 87893bf6dce6aaca8bb6e788b01c3363558d7542 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 17:59:48 -0700 Subject: [PATCH 265/661] more == vs === and other issues reported by codacy --- env.js | 12 ++++++------ lib/api/entries/index.js | 6 +++--- lib/api/experiments/index.js | 6 ++---- lib/bus.js | 7 +------ lib/data.js | 2 +- lib/devicestatus.js | 12 +----------- lib/entries.js | 20 ++++++++------------ lib/mqtt.js | 8 ++++++-- lib/notifications.js | 2 +- lib/plugins/cannulaage.js | 4 ++-- lib/plugins/cob.js | 2 +- lib/plugins/pluginbase.js | 8 ++++---- lib/plugins/treatmentnotify.js | 4 ++-- lib/poller.js | 18 ------------------ lib/profile.js | 5 +---- lib/pushnotify.js | 2 +- lib/sandbox.js | 13 +++++-------- lib/treatments.js | 6 ++---- lib/utils.js | 6 +++--- lib/websocket.js | 6 +++--- testing/util.js | 2 +- tests/api.status.test.js | 6 +----- tests/notifications.test.js | 2 +- tests/pebble.test.js | 10 +--------- tests/profile.test.js | 7 ------- tests/security.test.js | 8 ++++---- 26 files changed, 61 insertions(+), 123 deletions(-) delete mode 100644 lib/poller.js diff --git a/env.js b/env.js index 9cc0220d6c6..1c212aa9709 100644 --- a/env.js +++ b/env.js @@ -21,7 +21,7 @@ function config ( ) { var software = require('./package.json'); var git = require('git-rev'); - if (readENV('APPSETTING_ScmType') == readENV('ScmType') && readENV('ScmType') == 'GitHub') { + if (readENV('APPSETTING_ScmType') === readENV('ScmType') && readENV('ScmType') === 'GitHub') { env.head = require('./scm-commit-id.json'); console.log('SCM COMMIT ID', env.head); } else { @@ -44,7 +44,7 @@ function config ( ) { //some MQTT servers only allow the client id to be 23 chars env.mqtt_client_id = mongoHash.digest('base64').substring(0, 23); console.info('Using Mongo host/db/collection to create the default MQTT client_id', hostDbCollection); - if (env.MQTT_MONITOR.indexOf('?clientId=') == -1) { + if (env.MQTT_MONITOR.indexOf('?clientId=') === -1) { console.info('Set MQTT client_id to: ', env.mqtt_client_id); } else { console.info('MQTT configured to use a custom client id, it will override the default: ', env.mqtt_client_id); @@ -94,9 +94,9 @@ function config ( ) { env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); //TODO: figure out something for some plugins to have them shown by default - if (env.defaults.showPlugins != '') { + if (env.defaults.showPlugins !== '') { env.defaults.showPlugins += ' delta upbat'; - if (env.defaults.showRawbg == 'always' || env.defaults.showRawbg == 'noise') { + if (env.defaults.showRawbg === 'always' || env.defaults.showRawbg === 'noise') { env.defaults.showPlugins += 'rawbg'; } } @@ -223,8 +223,8 @@ function readENV(varName, defaultValue) { || process.env[varName] || process.env[varName.toLowerCase()]; - if (typeof value === 'string' && value.toLowerCase() == 'on') { value = true; } - if (typeof value === 'string' && value.toLowerCase() == 'off') { value = false; } + if (typeof value === 'string' && value.toLowerCase() === 'on') { value = true; } + if (typeof value === 'string' && value.toLowerCase() === 'off') { value = false; } return value != null ? value : defaultValue; } diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 0e5e6a4c1fe..1ad6e90d499 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -29,7 +29,7 @@ function configure (app, wares, ctx) { function force_typed_data (opts) { function sync (data, next) { - if (data.type != opts.type) { + if (data.type !== opts.type) { console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); data.type = opts.type; } @@ -39,10 +39,10 @@ function configure (app, wares, ctx) { } // Middleware to format any response involving entries. - function format_entries (req, res, next) { + function format_entries (req, res) { var type_params = { type: (req.query && req.query.find && req.query.find.type - && req.query.find.type != req.params.model) + && req.query.find.type !== req.params.model) ? req.query.find.type : req.params.model }; var output = es.readArray(res.entries || [ ]); diff --git a/lib/api/experiments/index.js b/lib/api/experiments/index.js index b0dca1ed408..4ccd13ff596 100644 --- a/lib/api/experiments/index.js +++ b/lib/api/experiments/index.js @@ -1,7 +1,5 @@ 'use strict'; -var consts = require('../../constants'); - function configure (app, wares) { var express = require('express'), api = express.Router( ) @@ -9,11 +7,11 @@ function configure (app, wares) { if (app.enabled('api')) { api.use(wares.sendJSONStatus); - api.get('/:secret/test', wares.verifyAuthorization, function (req, res, next) { + api.get('/:secret/test', wares.verifyAuthorization, function (req, res) { return res.json({status: 'ok'}); }); - api.get('/test', wares.verifyAuthorization, function (req, res, next) { + api.get('/test', wares.verifyAuthorization, function (req, res) { return res.json({status: 'ok'}); }); } diff --git a/lib/bus.js b/lib/bus.js index 107fe5d036f..6ec88a5a2a2 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -1,6 +1,6 @@ var Stream = require('stream'); -function init (env, ctx) { +function init (env) { var beats = 0; var started = new Date( ); var id; @@ -24,11 +24,6 @@ function init (env, ctx) { stream.emit('tick', ictus( )); } - function ender ( ) { - if (id) { cancelInterval(id); } - stream.emit('end'); - } - stream.readable = true; stream.uptime = repeat; id = setInterval(repeat, interval); diff --git a/lib/data.js b/lib/data.js index d682b292b64..a5c04eafc81 100644 --- a/lib/data.js +++ b/lib/data.js @@ -232,7 +232,7 @@ function init(env, ctx) { if (skippableObjects.hasOwnProperty(object)) { var o = skippableObjects[object]; if (newData.hasOwnProperty(o)) { - if (JSON.stringify(newData[o]) != JSON.stringify(oldData[o])) { + if (JSON.stringify(newData[o]) !== JSON.stringify(oldData[o])) { console.log('delta changes found on', o); changesFound = true; delta[o] = newData[o]; diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 1357138c1b7..87652001279 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -11,13 +11,6 @@ function storage (collection, ctx) { }); } - function create_date_included(obj, fn) { - api().insert(obj, function (err, doc) { - fn(null, doc); - }); - - } - function last(fn) { return api().find({}).sort({created_at: -1}).limit(1).toArray(function (err, entries) { if (entries && entries.length > 0) { @@ -37,14 +30,11 @@ function storage (collection, ctx) { return ctx.store.db.collection(collection); } - api.list = list; api.create = create; api.last = last; - api.indexedFields = indexedFields; + api.indexedFields = ['created_at']; return api; } -var indexedFields = ['created_at']; - module.exports = storage; diff --git a/lib/entries.js b/lib/entries.js index c9a52a00f03..7c9ac75f08e 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -93,13 +93,12 @@ function storage(env, ctx) { return es.pipeline(map( ), es.writeArray(done)); } - function update (fn) { - // TODO: implement - } - - function remove (fn) { - // TODO: implement - } + //TODO: implement + //function update (fn) { + //} + // + //function remove (fn) { + //} // store new documents using the storage mechanism function create (docs, fn) { @@ -112,7 +111,7 @@ function storage(env, ctx) { docs.forEach(function(doc) { var query = (doc.sysTime && doc.type) ? {sysTime: doc.sysTime, type: doc.type} : doc; - collection.update(query, doc, {upsert: true}, function (err, created) { + collection.update(query, doc, {upsert: true}, function (err) { firstErr = firstErr || err; if (++totalCreated === numDocs) { //TODO: this is triggering a read from Mongo, we can do better @@ -154,13 +153,10 @@ function storage(env, ctx) { api.create = create; api.persist = persist; api.getEntry = getEntry; - api.indexedFields = indexedFields; + api.indexedFields = [ 'date', 'type', 'sgv', 'sysTime' ]; return api; } -var indexedFields = [ 'date', 'type', 'sgv', 'sysTime' ]; -storage.indexedFields = indexedFields; - // expose module storage.storage = storage; module.exports = storage; diff --git a/lib/mqtt.js b/lib/mqtt.js index e3beafc061d..50ebca5d648 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -233,8 +233,12 @@ function configure(env, ctx) { console.log('DONE WRITING TO MONGO devicestatus ', result, err); }); - ctx.entries.create([ packet ], function empty(err, res) { - console.log('Download written to mongo: ', packet); + ctx.entries.create([ packet ], function empty(err) { + if (err) { + console.log('Error writting to mongo: ', err); + } else { + console.log('Download written to mongo: ', packet); + } }); } catch (e) { console.log('DID NOT PARSE', e); diff --git a/lib/notifications.js b/lib/notifications.js index 18e141c621e..cc983e72679 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -177,7 +177,7 @@ function init (env, ctx) { alarm.silenceTime = time ? time : THIRTY_MINUTES; delete alarm.lastEmitTime; - if (level == 2) { + if (level === 2) { notifications.ack(1, time); } diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 58e6eab291e..37b0913a1c8 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -19,7 +19,7 @@ function init() { var message = ''; _.forEach(sbx.data.treatments, function eachTreatment (treatment) { - if (treatment.eventType == 'Site Change') { + if (treatment.eventType === 'Site Change') { treatmentDate = new Date(treatment.created_at); var hours = Math.round(Math.abs(sbx.time - treatmentDate) / 36e5); @@ -38,7 +38,7 @@ function init() { }); var info = [{label: 'Inserted:', value: moment(treatmentDate).format('lll')}]; - if (message != '') { info.push({label: 'Notes:', value: message}); } + if (message !== '') { info.push({label: 'Notes:', value: message}); } sbx.pluginBase.updatePillText(cage, { value: age + 'h' diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 1eee45074b9..8f8c9f5c68b 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -151,7 +151,7 @@ function init() { var prop = sbx.properties.cob; - if (prop == undefined || prop.cob == undefined) { return; } + if (prop === undefined || prop.cob === undefined) { return; } var displayCob = Math.round(prop.cob * 10) / 10; diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index 3c58145144d..ac876edc9a4 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -11,11 +11,11 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { function findOrCreatePill (plugin) { var container = null; - if (plugin.pluginType == 'pill-major') { + if (plugin.pluginType === 'pill-major') { container = majorPills; - } else if (plugin.pluginType == 'pill-status') { + } else if (plugin.pluginType === 'pill-status') { container = statusPills; - } else if (plugin.pluginType == 'pill-alt') { + } else if (plugin.pluginType === 'pill-alt') { container = bgStatus; } else { container = minorPills; @@ -26,7 +26,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { var classes = 'pill ' + plugin.name; - if (!pill || pill.length == 0) { + if (!pill || pill.length === 0) { pill = $(''); var pillLabel = $(''); var pillValue = $(''); diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index aa6ad0694dc..04d041aa3e9 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -23,11 +23,11 @@ function init() { //TODO: figure out why date is x here #CleanUpDataModel var lastMBGTime = lastMBG ? lastMBG.x : 0; var mbgAgo = (lastMBGTime && lastMBGTime <= now) ? now - lastMBGTime : -1; - var mbgCurrent = mbgAgo != -1 && mbgAgo < TIME_10_MINS_MS; + var mbgCurrent = mbgAgo !== -1 && mbgAgo < TIME_10_MINS_MS; var lastTreatmentTime = lastTreatment ? new Date(lastTreatment.created_at).getTime() : 0; var treatmentAgo = (lastTreatmentTime && lastTreatmentTime <= now) ? now - lastTreatmentTime : -1; - var treatmentCurrent = treatmentAgo != -1 && treatmentAgo < TIME_10_MINS_MS; + var treatmentCurrent = treatmentAgo !== -1 && treatmentAgo < TIME_10_MINS_MS; if (mbgCurrent || treatmentCurrent) { autoSnoozeAlarms(sbx); diff --git a/lib/poller.js b/lib/poller.js deleted file mode 100644 index fd78327a1b2..00000000000 --- a/lib/poller.js +++ /dev/null @@ -1,18 +0,0 @@ - -var es = require('event-stream'); - -function create (core) { - function heartbeat (ev) { - - } - core.inputs.on('heartbeat', heartbeat); - function make (beat) { - var poll = {heart: beat}; - return poll; - } - function writer (data) { - } - var poller = es.through(writer); -} - -module.exports = create; diff --git a/lib/profile.js b/lib/profile.js index ca97ff6d841..060845210d7 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -1,6 +1,5 @@ 'use strict'; - function storage (collection, ctx) { var ObjectID = require('mongodb').ObjectID; @@ -35,10 +34,8 @@ function storage (collection, ctx) { api.create = create; api.save = save; api.last = last; - api.indexedFields = indexedFields; + api.indexedFields = ['validfrom']; return api; } -var indexedFields = ['validfrom']; - module.exports = storage; diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 6546dc592e1..dfba17eb98c 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -90,7 +90,7 @@ function init(env, ctx) { if (notify.level >= ctx.notifications.levels.WARN) { //ADJUST RETRY TIME based on WARN or URGENT - msg.retry = notify.level == ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; + msg.retry = notify.level === ctx.notifications.levels.URGENT ? TIME_2_MINS_S : TIME_15_MINS_S; if (env.baseUrl) { msg.callback = env.baseUrl + '/api/v1/notifications/pushovercallback'; } diff --git a/lib/sandbox.js b/lib/sandbox.js index 0aa57eb2a08..21015a9ce27 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -130,7 +130,7 @@ function init ( ) { } function scaleBg (bg) { - if (sbx.units == 'mmol' && bg) { + if (sbx.units === 'mmol' && bg) { return Number(units.mgdlToMMOL(bg)); } else { return Number(bg); @@ -139,11 +139,11 @@ function init ( ) { function roundInsulinForDisplayFormat (insulin) { - if (insulin == 0) { + if (insulin === 0) { return '0'; } - if (sbx.properties.roundingStyle == 'medtronic') { + if (sbx.properties.roundingStyle === 'medtronic') { var denominator = 0.1; var digits = 1; if (insulin > 0.5 && iob < 1) { @@ -162,14 +162,11 @@ function init ( ) { } function unitsLabel ( ) { - return sbx.units == 'mmol' ? 'mmol/L' : 'mg/dl'; + return sbx.units === 'mmol' ? 'mmol/L' : 'mg/dl'; } function roundBGToDisplayFormat (bg) { - if (sbx.units == 'mmol') { - return Math.round(bg * 10) / 10; - } - return Math.round(bg); + return sbx.units === 'mmol' ? Math.round(bg * 10) / 10 : Math.round(bg); } diff --git a/lib/treatments.js b/lib/treatments.js index ca6895f24e5..7804142bfa6 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -26,7 +26,7 @@ function storage (env, ctx) { if (!obj.carbs) { delete obj.carbs; } if (!obj.insulin) { delete obj.insulin; } if (!obj.notes) { delete obj.notes; } - if (!obj.preBolus || obj.preBolus == 0) { delete obj.preBolus; } + if (!obj.preBolus || obj.preBolus === 0) { delete obj.preBolus; } if (!obj.glucose) { delete obj.glucose; delete obj.glucoseType; @@ -73,11 +73,9 @@ function storage (env, ctx) { api.list = list; api.create = create; - api.indexedFields = indexedFields; + api.indexedFields = ['created_at', 'eventType']; return api; } -var indexedFields = ['created_at', 'eventType']; - module.exports = storage; diff --git a/lib/utils.js b/lib/utils.js index 328c492b71b..86476956a32 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -15,19 +15,19 @@ function init() { return '0'; } else { var fixed = value.toFixed(2); - return fixed == '-0.00' ? '0.00' : fixed; + return fixed === '-0.00' ? '0.00' : fixed; } }; utils.timeAgo = function timeAgo(time, clientSettings) { var now = Date.now() - , offset = time == -1 ? -1 : (now - time) / 1000 + , offset = time === -1 ? -1 : (now - time) / 1000 , parts = {}; if (offset < MINUTE_IN_SECS * -5) { parts = { value: 'in the future' }; - } else if (offset == -1) { + } else if (offset === -1) { parts = { label: 'time ago' }; } else if (offset <= MINUTE_IN_SECS * 2) { parts = { value: 1, label: 'min ago' }; diff --git a/lib/websocket.js b/lib/websocket.js index 887dae94a5a..a6e5745f200 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -65,12 +65,12 @@ function init (env, ctx, server) { if (notify.clear) { io.emit('clear_alarm', true); console.info('emitted clear_alarm to all clients'); - } else if (notify.level == levels.INFO) { + } else if (notify.level === levels.INFO) { //client doesn't know how to display info notifications yet, ignoring - } else if (notify.level == levels.WARN) { + } else if (notify.level === levels.WARN) { io.emit('alarm', notify); console.info('emitted alarm to all clients'); - } else if (notify.level == levels.URGENT) { + } else if (notify.level === levels.URGENT) { io.emit('urgent_alarm', notify); console.info('emitted urgent_alarm to all clients'); } diff --git a/testing/util.js b/testing/util.js index 25348e1566a..eb433429da8 100644 --- a/testing/util.js +++ b/testing/util.js @@ -49,7 +49,7 @@ function getDateString(d) { ampm = 'AM'; } - if (hour == 0) { + if (hour === 0) { hour = 12; } if (hour > 12) { diff --git a/tests/api.status.test.js b/tests/api.status.test.js index c37407048de..8447d560b93 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -19,10 +19,6 @@ describe('Status REST api', function ( ) { }); }); - it('should be a module', function ( ) { - api.should.be.ok; - }); - it('/status.json', function (done) { request(this.app) .get('/api/status.json') @@ -62,7 +58,7 @@ describe('Status REST api', function ( ) { .end(function(err, res) { res.headers.location.should.equal('http://img.shields.io/badge/Nightscout-OK-green.png'); res.statusCode.should.equal(302); - done() + done(); }); }); diff --git a/tests/notifications.test.js b/tests/notifications.test.js index df386cbf098..1c6d55e994f 100644 --- a/tests/notifications.test.js +++ b/tests/notifications.test.js @@ -13,7 +13,7 @@ describe('notifications', function ( ) { var notifications = require('../lib/notifications')(env, ctx); - var examplePlugin = function examplePlugin () {}; + function examplePlugin () {}; var exampleInfo = { title: 'test' diff --git a/tests/pebble.test.js b/tests/pebble.test.js index bf87f889a67..dd1e18ee714 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -79,7 +79,7 @@ var ctx = { if (opts && opts.find && opts.find.sgv) { callback(null, sgvs.slice(0, count)); - } else if (opts && opts.find && opts.find.type == 'cal') { + } else if (opts && opts.find && opts.find.type === 'cal') { callback(null, cals.slice(0, count)); } } @@ -108,10 +108,6 @@ describe('Pebble Endpoint without Raw', function ( ) { done(); }); - it('should be a module', function ( ) { - pebble.should.be.ok; - }); - it('/pebble default(1) count', function (done) { request(this.app) .get('/pebble') @@ -173,10 +169,6 @@ describe('Pebble Endpoint with Raw', function ( ) { done(); }); - it('should be a module', function ( ) { - pebbleRaw.should.be.ok; - }); - it('/pebble', function (done) { request(this.appRaw) .get('/pebble?count=2') diff --git a/tests/profile.test.js b/tests/profile.test.js index ae487ef279f..7a3156d2c5d 100644 --- a/tests/profile.test.js +++ b/tests/profile.test.js @@ -14,13 +14,6 @@ describe('Profile', function ( ) { should.not.exist(dia); }); - var profileDataPartial = { - 'dia': 3 - , 'carbs_hr': 30 - }; - - var profilePartial = require('../lib/profilefunctions')([profileDataPartial]); - it('should return undefined if asking for missing keys', function() { var sens = profile_empty.getSensitivity(now); should.not.exist(sens); diff --git a/tests/security.test.js b/tests/security.test.js index bf8d25fc68d..7f692b6a31f 100644 --- a/tests/security.test.js +++ b/tests/security.test.js @@ -6,7 +6,6 @@ var load = require('./fixtures/load'); describe('API_SECRET', function ( ) { var api = require('../lib/api/'); - api.should.be.ok; var scope = this; function setup_app (env, fn) { @@ -34,7 +33,8 @@ describe('API_SECRET', function ( ) { var env = require('../env')( ); should.not.exist(env.api_secret); setup_app(env, function (ctx) { - ctx.app.enabled('api').should.be.false; + + ctx.app.enabled('api').should.equal(false); ping_status(ctx.app, again); function again ( ) { ping_authorized_endpoint(ctx.app, 404, done); @@ -51,7 +51,7 @@ describe('API_SECRET', function ( ) { env.api_secret.should.equal(known); setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); - ctx.app.enabled('api').should.be.true; + ctx.app.enabled('api').should.equal(true); // ping_status(ctx.app, done); // ping_authorized_endpoint(ctx.app, 200, done); ping_status(ctx.app, again); @@ -72,7 +72,7 @@ describe('API_SECRET', function ( ) { env.api_secret.should.equal(known); setup_app(env, function (ctx) { // console.log(this.app.enabled('api')); - ctx.app.enabled('api').should.be.true; + ctx.app.enabled('api').should.equal(true); // ping_status(ctx.app, done); // ping_authorized_endpoint(ctx.app, 200, done); ping_status(ctx.app, again); From c306a3aefb194249a717a69cbee513f935601ccd Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 19:12:33 -0700 Subject: [PATCH 266/661] maybe the last of the easy js clean up? --- lib/middleware/verify-token.js | 2 +- lib/profilefunctions.js | 10 ++-- lib/websocket.js | 2 +- static/js/client.js | 98 ++++++++++++++++++---------------- static/js/ui-utils.js | 14 ++--- 5 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/middleware/verify-token.js b/lib/middleware/verify-token.js index f7f875fa83a..714201c5ac2 100644 --- a/lib/middleware/verify-token.js +++ b/lib/middleware/verify-token.js @@ -8,7 +8,7 @@ function configure (env) { var secret = req.params.secret ? req.params.secret : req.header('api-secret'); // Return an error message if the authorization fails. - var unauthorized = (typeof api_secret === 'undefined' || secret != api_secret); + var unauthorized = (typeof api_secret === 'undefined' || secret !== api_secret); if (unauthorized) { res.sendJSONStatus(res, consts.HTTP_UNAUTHORIZED, 'Unauthorized', 'api-secret Request Header is incorrect or missing.'); } else { diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index e6ac5ea0e9c..a937b1de501 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -1,7 +1,6 @@ 'use strict'; var _ = require('lodash'); -var moment = require('moment'); function init(profileData) { @@ -50,12 +49,11 @@ function init(profileData) { var timeAsDate = new Date(time); var timeAsSecondsFromMidnight = timeAsDate.getHours()*3600 + timeAsDate.getMinutes()*60; - for (var t in valueContainer) { - var value = valueContainer[t]; + _.forEach(valueContainer, function eachValue (value) { if (timeAsSecondsFromMidnight >= value.timeAsSeconds) { returnValue = value.value; } - } + }); } return returnValue; @@ -66,9 +64,7 @@ function init(profileData) { }; profile.hasData = function hasData() { - var rVal = false; - if (profile.data) rVal = true; - return (rVal); + return profile.data ? true : false; }; profile.getDIA = function getDIA(time) { diff --git a/lib/websocket.js b/lib/websocket.js index a6e5745f200..4d1fa74ac95 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -36,7 +36,7 @@ function init (env, ctx, server) { socket.emit('dataUpdate',lastData); io.emit('clients', ++watchers); socket.on('ack', function(alarmType, silenceTime) { - var level = alarmType == 'urgent_alarm' ? 2 : 1; + var level = alarmType === 'urgent_alarm' ? 2 : 1; ctx.notifications.ack(level, silenceTime, true); }); socket.on('disconnect', function () { diff --git a/static/js/client.js b/static/js/client.js index ea7313ab7f6..b669164d117 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -39,7 +39,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , data = [] , foucusRangeMS = THREE_HOURS_MS , clientAlarms = {} - , audio = document.getElementById('audio') , alarmInProgress = false , currentAlarmType = null , alarmSound = 'alarm.mp3' @@ -80,7 +79,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) == 24; + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; } function getTimeFormat(isForScale, compact) { @@ -96,7 +95,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // lixgbg: Convert mg/dL BG value to metric mmol function scaleBg(bg) { - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { return Nightscout.units.mgdlToMMOL(bg); } else { return bg; @@ -170,8 +169,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; [':%S', function(d) { return d.getSeconds(); }], ['%I:%M', function(d) { return d.getMinutes(); }], [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() != 1; }], - ['%b %d', function(d) { return d.getDate() != 1; }], + ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], + ['%b %d', function(d) { return d.getDate() !== 1; }], ['%B', function(d) { return d.getMonth(); }], ['%Y', function() { return true; }] ]); @@ -224,7 +223,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove all 'color != 'none'' code + // remove this code and all references to `type: 'server-forecast'` var lastTime = data.length > 0 ? data[data.length - 1].date.getTime() : Date.now(); var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; for (var i = 1; i <= n; i++) { @@ -260,7 +259,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // update the opacity of the context data points to brush extent context.selectAll('circle') .data(data) - .style('opacity', function (d) { return 1; }); + .style('opacity', 1); } function brushEnded() { @@ -317,7 +316,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var brushExtent = brush.extent(); // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { + if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { // ensure that brush updating is with the time range if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { @@ -340,8 +339,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , majorPills = $('.bgStatus .majorPills') , minorPills = $('.bgStatus .minorPills') , statusPills = $('.status .statusPills') - , lastEntry = $('#lastEntry'); - + ; function updateCurrentSGV(entry) { var value, time, ago, isCurrent; @@ -350,7 +348,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; ago = timeAgo(time, browserSettings); isCurrent = ago.status === 'current'; - if (value == 9) { + if (value === 9) { currentBG.text(''); } else if (value < 39) { currentBG.html(errorCodeToDisplay(value)); @@ -370,9 +368,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - currentBG.toggleClass('icon-hourglass', value == 9); + currentBG.toggleClass('icon-hourglass', value === 9); currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value == 39 || value > 400); + currentBG.toggleClass('bg-limit', value === 39 || value > 400); $('.container').removeClass('loading'); @@ -405,7 +403,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var lookback = 2; var nowData = data.filter(function(d) { - return d.type == 'sgv'; + return d.type === 'sgv'; }); if (inRetroMode()) { @@ -468,13 +466,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // selects all our data into data and uses date function to get current max date var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - var focusRangeAdjustment = foucusRangeMS == THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); var dotRadius = function(type) { var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type == 'mbg') { + if (type === 'mbg') { radius *= 2; - } else if (type == 'rawbg') { + } else if (type === 'rawbg') { radius = Math.min(2, radius - 1); } @@ -482,7 +480,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }; function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') == 0; + return device && device.toLowerCase().indexOf('dexcom') === 0; } function prepareFocusCircles(sel) { @@ -498,7 +496,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }) - .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) .attr('stroke', function (d) { return (isDexcom(d.device) ? 'white' : '#0099ff'); }) @@ -531,7 +529,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('' + bgType + ' BG: ' + d.sgv + - (d.type == 'mbg' ? '
    Device: ' + d.device : '') + + (d.type === 'mbg' ? '
    Device: ' + d.device : '') + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + '
    Time: ' + formatTime(d.date)) @@ -647,7 +645,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function updateChart(init) { + var updateChart = _.debounce(function debouncedUpdateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -671,7 +669,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var currentBrushExtent = brush.extent(); // only redraw chart if chart size has changed - if ((prevChartWidth != chartWidth) || (prevChartHeight != chartHeight)) { + if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { prevChartWidth = chartWidth; prevChartHeight = chartHeight; @@ -969,9 +967,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('fill', function (d) { return d.color; }) .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) { return d.type == 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) .attr('stroke', function ( ) { return 'white'; }) - .attr('r', function (d) { return d.type == 'mbg' ? 4 : 2; }); + .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); if (badData.length > 0) { console.warn('Bad Data: isNaN(sgv)', badData); @@ -998,7 +996,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function sgvToColor(sgv) { var color = 'grey'; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { if (sgv > app.thresholds.bg_high) { color = 'red'; } else if (sgv > app.thresholds.bg_target_top) { @@ -1018,7 +1016,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function sgvToColoredRange(sgv) { var range = ''; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { if (sgv > app.thresholds.bg_high) { range = 'urgent'; } else if (sgv > app.thresholds.bg_target_top) { @@ -1039,18 +1037,18 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function generateAlarm(file) { alarmInProgress = true; var selector = '.audio.alarms audio.' + file; - d3.select(selector).each(function (d, i) { + d3.select(selector).each(function () { var audio = this; playAlarm(audio); $(this).addClass('playing'); }); - $('.bgButton').addClass(file == urgentAlarmSound ? 'urgent' : 'warning'); + $('.bgButton').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); $('#container').addClass('alarming'); } function playAlarm(audio) { // ?mute=true disables alarms to testers. - if (querystring.mute != 'true') { + if (querystring.mute !== 'true') { audio.play(); } else { showNotification('Alarm was muted (?mute=true)'); @@ -1089,7 +1087,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function calcBGByTime(time) { var withBGs = _.filter(data, function(d) { - return d.y > 39 && d.type == 'sgv'; + return d.y > 39 && d.type === 'sgv'; }); var beforeTreatment = _.findLast(withBGs, function (d) { @@ -1117,9 +1115,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.warn('found an invalid glucose value', treatment); } else { if (treatment.glucose && treatment.units && browserSettings.units) { - if (treatment.units != browserSettings.units) { + if (treatment.units !== browserSettings.units) { console.info('found mismatched glucose units, converting ' + treatment.units + ' into ' + browserSettings.units, treatment); - if (treatment.units == 'mmol') { + if (treatment.units === 'mmol') { //BG is in mmol and display in mg/dl treatmentGlucose = Math.round(treatment.glucose * 18); } else { @@ -1210,7 +1208,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); var arcs = treatmentDots.append('path') .attr('class', 'path') - .attr('fill', function (d, i) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) .attr('stroke', function (d) { return d.color; }) .attr('id', function (d, i) { return 's' + i; }) @@ -1249,13 +1247,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var BG_MAX = scaleBg(400); function roundByUnits(value) { - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { return value.toFixed(1); } else { return Math.round(value); } } + //TODO: clean when moving the ar2 plugin + var y; + // these are the one sigma limits for the first 13 prediction interval uncertainties (65 minutes) var CONE = [0.020, 0.041, 0.061, 0.081, 0.099, 0.116, 0.132, 0.146, 0.159, 0.171, 0.182, 0.192, 0.201]; // these are modified to make the cone much blunter @@ -1263,7 +1264,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // for testing //var CONE = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; if (actual.length < lookback+1) { - var y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; + y = [Math.log(actual[actual.length-1].sgv / BG_REF), Math.log(actual[actual.length-1].sgv / BG_REF)]; } else { var elapsedMins = (actual[actual.length-1].date - actual[actual.length-1-lookback].date) / ONE_MINUTE; // construct a '5m ago' sgv offset from current sgv by the average change over the lookback interval @@ -1281,7 +1282,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var AR = [-0.723, 1.716]; var dt = actual[lookback].date.getTime(); var predictedColor = 'blue'; - if (browserSettings.theme == 'colors') { + if (browserSettings.theme === 'colors') { predictedColor = 'cyan'; } for (var i = 0; i < CONE.length; i++) { @@ -1301,9 +1302,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }; predicted.forEach(function (d) { d.type = 'forecast'; - if (d.sgv < BG_MIN) + if (d.sgv < BG_MIN) { d.color = 'transparent'; - }) + } + }); } return predicted; } @@ -1318,7 +1320,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Dim the screen by reducing the opacity when at nighttime if (browserSettings.nightMode) { var dateTime = new Date(); - if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { $('body').css({ 'opacity': opacity.NIGHT }); } else { $('body').css({ 'opacity': opacity.DAY }); @@ -1327,7 +1329,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function updateClockDisplay() { - if (inRetroMode()) return; + if (inRetroMode()) { + return; + } now = Date.now(); var dateTime = new Date(now); $('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', ''); @@ -1343,7 +1347,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function isTimeAgoAlarmType(alarmType) { - return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo'; + return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; } function checkTimeAgoAlarm(ago) { @@ -1354,7 +1358,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentAlarmType = alarm.type; console.info('generating timeAgoAlarm', alarm.type); $('#container').addClass('alarming-timeago'); - if (level == 'warn') { + if (level === 'warn') { generateAlarm(alarmSound); } else { generateAlarm(urgentAlarmSound); @@ -1376,12 +1380,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } if ( - (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { + (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { checkTimeAgoAlarm(ago); } - if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { $('#container').removeClass('alarming-timeago'); stopAlarm(true, ONE_MIN_IN_MS); } @@ -1408,7 +1412,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .style('opacity', 0); // Tick Values - if (browserSettings.units == 'mmol') { + if (browserSettings.units === 'mmol') { tickValues = [ 2.0 , Math.round(scaleBg(app.thresholds.bg_low)) @@ -1573,7 +1577,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } else { return null; } - }).filter(function(entry) { return entry != null; }); + }).filter(function(entry) { return entry !== null; }); } var temp2 = SGVdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), direction: obj.direction, color: sgvToColor(obj.y), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index bff843efa45..a9aa1ec3b9c 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -10,7 +10,7 @@ function getBrowserSettings(storage) { var json = {}; function scaleBg(bg) { - if (json.units == 'mmol') { + if (json.units === 'mmol') { return Nightscout.units.mgdlToMMOL(bg); } else { return bg; @@ -18,7 +18,7 @@ function getBrowserSettings(storage) { } function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') == -1 ? '' : ' (' + scaleBg(threshold) + ')'; + return app.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; } try { @@ -42,7 +42,7 @@ function getBrowserSettings(storage) { // Default browser units to server units if undefined. json.units = setDefault(json.units, app.units); - if (json.units == 'mmol') { + if (json.units === 'mmol') { $('#mmol-browser').prop('checked', true); } else { $('#mgdl-browser').prop('checked', true); @@ -82,7 +82,7 @@ function getBrowserSettings(storage) { $('input#customTitle').prop('value', json.customTitle); json.theme = setDefault(json.theme, app.defaults.theme); - if (json.theme == 'colors') { + if (json.theme === 'colors') { $('#theme-colors-browser').prop('checked', true); } else { $('#theme-default-browser').prop('checked', true); @@ -90,7 +90,7 @@ function getBrowserSettings(storage) { json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); - if (json.timeFormat == '24') { + if (json.timeFormat === '24') { $('#24-browser').prop('checked', true); } else { $('#12-browser').prop('checked', true); @@ -179,7 +179,7 @@ function toggleDrawer(id, openCallback, closeCallback) { } - if (openDraw == id) { + if (openDraw === id) { closeDrawer(id, closeCallback); } else { openDrawer(id, openCallback); @@ -278,7 +278,7 @@ function treatmentSubmit(event) { window.alert(errors.join('\n')); } else { var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { + if ($('#treatment-form input[name=nowOrOther]:checked').val() !== 'now') { var value = $('#eventTimeValue').val(); var eventTimeParts = value.split(':'); data.eventTime = new Date(); From 0f5a12b827419617d047287c009746d1e75888d5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 19:46:15 -0700 Subject: [PATCH 267/661] there are always more codacy issues to fix... --- lib/profilefunctions.js | 7 ++----- static/js/client.js | 45 ++++++++++++++++++++++++++--------------- static/js/ui-utils.js | 2 +- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/lib/profilefunctions.js b/lib/profilefunctions.js index a937b1de501..5604bd500b5 100644 --- a/lib/profilefunctions.js +++ b/lib/profilefunctions.js @@ -20,9 +20,7 @@ function init(profileData) { // preprocess the timestamps to seconds for a couple orders of magnitude faster operation profile.preprocessProfileOnLoad = function preprocessProfileOnLoad(container) { - for (var key in container) { - var value = container[key]; - + _.forEach(container, function eachValue (value) { if( Object.prototype.toString.call(value) === '[object Array]' ) { profile.preprocessProfileOnLoad(value); } @@ -31,8 +29,7 @@ function init(profileData) { var sec = profile.timeStringToSeconds(value.time); if (!isNaN(sec)) { value.timeAsSeconds = sec; } } - - } + }); }; if (profileData) { profile.loadData(profileData); } diff --git a/static/js/client.js b/static/js/client.js index b669164d117..5ce392a4ded 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1206,7 +1206,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .duration(TOOLTIP_TRANS_MS) .style('opacity', 0); }); - var arcs = treatmentDots.append('path') + + treatmentDots.append('path') .attr('class', 'path') .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) @@ -1285,6 +1286,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (browserSettings.theme === 'colors') { predictedColor = 'cyan'; } + for (var i = 0; i < CONE.length; i++) { y = [y[1], AR[0] * y[0] + AR[1] * y[1]]; dt = dt + FIVE_MINUTES; @@ -1300,13 +1302,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundByUnits(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), color: predictedColor }; - predicted.forEach(function (d) { - d.type = 'forecast'; - if (d.sgv < BG_MIN) { - d.color = 'transparent'; - } - }); } + + predicted.forEach(function (d) { + d.type = 'forecast'; + if (d.sgv < BG_MIN) { + d.color = 'transparent'; + } + }); + return predicted; } @@ -1492,7 +1496,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var silenceDropdown = new Dropdown('.dropdown-menu'); $('.bgButton').click(function (e) { - if (alarmingNow()) silenceDropdown.open(e); + if (alarmingNow()) { + silenceDropdown.open(e); + } }); $('#silenceBtn').find('a').click(function (e) { @@ -1527,10 +1533,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // If there was no delta data, just return the original data - if (!receivedDataArray) return cachedDataArray; + if (!receivedDataArray) { + return cachedDataArray; + } // If this is not a delta update, replace all data - if (!isDelta) return receivedDataArray; + if (!isDelta) { + return receivedDataArray; + } // If this is delta, calculate the difference, merge and sort var diff = nsArrayDiff(cachedDataArray,receivedDataArray); @@ -1541,19 +1551,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; socket.on('dataUpdate', function receivedSGV(d) { - if (!d) return; + if (!d) { + return; + } // Calculate the diff to existing data and replace as needed SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + if (d.profiles) { profile = d.profiles[0]; Nightscout.profile.loadData(d.profiles); } - if (d.cals) cal = d.cals[d.cals.length-1]; - if (d.devicestatus) devicestatusData = d.devicestatus; + + if (d.cals) { cal = d.cals[d.cals.length-1]; } + if (d.devicestatus) { devicestatusData = d.devicestatus; } // Do some reporting on the console console.log('Total SGV data size', SGVdata.length); @@ -1590,8 +1604,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; data = data.concat(MBGdata.map(function (obj) { return { date: new Date(obj.x), y: obj.y, sgv: scaleBg(obj.y), color: 'red', type: 'mbg', device: obj.device } })); data.forEach(function (d) { - if (d.y < 39) - d.color = 'transparent'; + if (d.y < 39) { d.color = 'transparent'; } }); // OPTIMIZATION: precalculate treatment location in timeline @@ -1616,7 +1629,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // Alarms and Text handling //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket.on('connect', function () { - console.log('Client connected to server.') + console.log('Client connected to server.'); }); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index a9aa1ec3b9c..f461981e33d 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -22,7 +22,7 @@ function getBrowserSettings(storage) { } try { - var json = { + json = { 'units': storage.get('units'), 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), 'alarmHigh': storage.get('alarmHigh'), From 6c84eac2e8ab366c17385af08fbc75fcecf2684a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 23:22:08 -0700 Subject: [PATCH 268/661] Added Codacy badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c05ecb40f0f..5ff52f8c910 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Nightscout Web Monitor (a.k.a. cgm-remote-monitor) [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] [![Coverage Status][coverage-img]][coverage-url] +[![Codacy Badge][codacy-img]][codacy-url] [![Gitter chat][gitter-img]][gitter-url] [![Stories in Ready][ready-img]][waffle] [![Stories in Progress][progress-img]][waffle] @@ -32,6 +33,8 @@ Community maintained fork of the [dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor [coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/master.svg [coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=master +[codacy-img]: https://www.codacy.com/project/badge/f79327216860472dad9afda07de39d3b +[codacy-url]: https://www.codacy.com/app/Nightscout/cgm-remote-monitor [gitter-img]: https://img.shields.io/badge/Gitter-Join%20Chat%20%E2%86%92-1dce73.svg [gitter-url]: https://gitter.im/nightscout/public [ready-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=ready&title=Ready From 4649296bfd2c146f1f5243cec19eb0b8e03bd092 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 1 Jul 2015 23:55:22 -0700 Subject: [PATCH 269/661] added test to reproduce #664, and fixed the bug --- lib/plugins/ar2.js | 16 +++++++++++----- tests/ar2.test.js | 13 +++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 855dd35164b..577563bb07e 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -39,10 +39,12 @@ function init() { var cal = _.last(sbx.data.cals); if (cal) { var rawSGVs = _.map(_.takeRight(sbx.data.sgvs, 2), function eachSGV (sgv) { - return { - x: sgv.x - , y: Math.max(rawbg.calc(sgv, cal), BG_MIN) //stay above BG_MIN - }; + var rawResult = rawbg.calc(sgv, cal); + + //ignore when raw is 0, and use BG_MIN if raw is lower + var rawY = rawResult === 0 ? 0 : Math.max(rawResult, BG_MIN); + + return { x: sgv.x, y: rawY }; }); result = ar2.forcastAndCheck(rawSGVs, true); usingRaw = true; @@ -158,10 +160,14 @@ function init() { , avgLoss: 0 }; + if (lastIndex < 1) { + return result; + } + var current = sgvs[lastIndex].y; var prev = sgvs[lastIndex - 1].y; - if (lastIndex > 0 && current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { + if (current >= BG_MIN && sgvs[lastIndex - 1].y >= BG_MIN) { // predict using AR model var lastValidReadingTime = sgvs[lastIndex].x; var elapsedMins = (sgvs[lastIndex].x - sgvs[lastIndex - 1].x) / ONE_MINUTE; diff --git a/tests/ar2.test.js b/tests/ar2.test.js index f560e5937b4..28ca4e8e6f1 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -99,6 +99,19 @@ describe('ar2', function ( ) { return require('../lib/sandbox')().serverInit(envRaw, ctx); } + it('should not trigger an alarm when raw is missing or 0', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{unfiltered: 0, filtered: 0, y: 100, x: before, noise: 1}, {unfiltered: 0, filtered: 0, y: 100, x: now, noise: 1}]; + ctx.data.cals = [{scale: 1, intercept: 25717.82377004309, slope: 766.895601715918}]; + + var sbx = rawSandbox(ctx); + ar2.checkNotifications(sbx.withExtendedSettings(ar2)); + should.not.exist(ctx.notifications.findHighestAlarm()); + + done(); + }); + + it('should trigger a warning (no urgent for raw) when raw is falling really fast, but sgv is steady', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{unfiltered: 113680, filtered: 111232, y: 100, x: before, noise: 1}, {unfiltered: 43680, filtered: 111232, y: 100, x: now, noise: 1}]; From 5ea7b3e83ef1d888f07137e121268830a4d1238b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 2 Jul 2015 18:26:20 -0700 Subject: [PATCH 270/661] remove some duplication when formatting messages --- lib/plugins/ar2.js | 34 +++++---------------------- lib/plugins/boluswizardpreview.js | 30 ++---------------------- lib/plugins/cob.js | 5 ++-- lib/plugins/iob.js | 10 ++++---- lib/plugins/rawbg.js | 1 + lib/plugins/simplealarms.js | 32 +------------------------- lib/sandbox.js | 36 +++++++++++++++++++++++++++++ tests/ar2.test.js | 5 +++- tests/sandbox.test.js | 38 +++++++++++++++++++++++-------- 9 files changed, 88 insertions(+), 103 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 577563bb07e..f4e11fe643a 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -84,35 +84,13 @@ function init() { title += ' w/raw'; } - var lines = ['BG Now: ' + sbx.displayBg(sbx.data.lastSGV())]; + var lines = sbx.prepareDefaultLines(sbx.data.lastSGV()); - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - lines.push([usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ')); - - var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp && bwp > 0) { - lines.push(['BWP:', bwp, 'U'].join(' ')); - } - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } + //insert prediction after the first line + lines.splice(1 + , 0 + , [usingRaw ? 'Raw BG' : 'BG', '15m:', sbx.scaleBg(predicted[2]), sbx.unitsLabel].join(' ') + ); var message = lines.join('\n'); diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 6ece9487671..9013d62d7be 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -76,37 +76,10 @@ function init() { var levelLabel = sbx.notifications.levels.toString(level); var sound = level === sbx.notifications.levels.URGENT ? 'updown' : 'bike'; - var lines = ['BG Now: ' + results.displaySGV]; - - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - lines.push(['BWP:', results.bolusEstimateDisplay, 'U'].join(' ')); - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } - - var message = lines.join('\n'); - sbx.notifications.requestNotify({ level: level , title: levelLabel + ', Check BG, time to bolus?' - , message: message + , message: sbx.buildDefaultMessage(results.displaySGV) , eventName: 'bwp' , pushoverSound: sound , plugin: bwp @@ -205,6 +178,7 @@ function init() { results.outcomeDisplay = sbx.roundBGToDisplayFormat(results.outcome); results.displayIOB = sbx.roundInsulinForDisplayFormat(results.iob); results.effectDisplay = sbx.roundBGToDisplayFormat(results.effect); + results.displayLine = 'BWP: ' + results.bolusEstimateDisplay + 'U'; return results; }; diff --git a/lib/plugins/cob.js b/lib/plugins/cob.js index 8f8c9f5c68b..0bb076dafba 100644 --- a/lib/plugins/cob.js +++ b/lib/plugins/cob.js @@ -81,14 +81,15 @@ function init() { }); var rawCarbImpact = isDecaying * profile.getSensitivity(time) / profile.getCarbRatio(time) * profile.getCarbAbsorptionRate(time) / 60; - + var display = Math.round(totalCOB * 10) / 10; return { decayedBy: lastDecayedBy , isDecaying: isDecaying , carbs_hr: profile.getCarbAbsorptionRate(time) , rawCarbImpact: rawCarbImpact , cob: totalCOB - , display: Math.round(totalCOB * 10) / 10 + , display: display + , displayLine: 'COB: ' + display + 'g' , lastCarbs: lastCarbs }; }; diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index d1b2fbe4eab..dec67f44ab0 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -43,11 +43,13 @@ function init() { } }); + var display = utils.toFixed(totalIOB); return { - iob: totalIOB, - display: utils.toFixed(totalIOB), - activity: totalActivity, - lastBolus: lastBolus + iob: totalIOB + , display: display + , displayLine: 'IOB: ' + display + 'U' + , activity: totalActivity + , lastBolus: lastBolus }; }; diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index a3635017805..9619bf21a4a 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -24,6 +24,7 @@ function init() { result.noiseLabel = rawbg.noiseCodeToDisplay(currentSGV.y, currentSGV.noise); result.sgv = currentSGV; result.cal = currentCal; + result.displayLine = ['Raw BG:', sbx.scaleBg(result.value), sbx.unitsLabel, result.noiseLabel].join(' '); } return result; diff --git a/lib/plugins/simplealarms.js b/lib/plugins/simplealarms.js index b056ac8f087..f0edf4ecf9d 100644 --- a/lib/plugins/simplealarms.js +++ b/lib/plugins/simplealarms.js @@ -56,41 +56,11 @@ function init() { console.info(title + ': ' + (lastSGV + ' < ' + sbx.scaleBg(sbx.thresholds.bg_target_bottom))); } - var lines = ['BG Now: ' + displaySGV]; - - var delta = sbx.properties.delta && sbx.properties.delta.display; - if (delta) { - lines[0] += ' ' + delta; - } - lines[0] += ' ' + sbx.unitsLabel; - - var rawbgProp = sbx.properties.rawbg; - if (rawbgProp) { - lines.push(['Raw BG:', sbx.scaleBg(rawbgProp.value), sbx.unitsLabel, rawbgProp.noiseLabel].join(' ')); - } - - var bwp = sbx.properties.bwp && sbx.properties.bwp.bolusEstimateDisplay; - if (bwp && bwp > 0) { - lines.push(['BWP:', bwp, 'U'].join(' ')); - } - - var iob = sbx.properties.iob && sbx.properties.iob.display; - if (iob) { - lines.push(['IOB:', iob, 'U'].join(' ')); - } - - var cob = sbx.properties.cob && sbx.properties.cob.display; - if (cob) { - lines.push(['COB:', cob, 'g'].join(' ')); - } - - var message = lines.join('\n'); - if (trigger) { sbx.notifications.requestNotify({ level: level , title: title - , message: message + , message: sbx.buildDefaultMessage(displaySGV) , eventName: eventName , plugin: simplealarms , pushoverSound: pushoverSound diff --git a/lib/sandbox.js b/lib/sandbox.js index 21015a9ce27..83703370bad 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -119,6 +119,42 @@ function init ( ) { } }; + sbx.buildBGNowLine = function buildBGNowLine (displaySGV) { + var line = 'BG Now: ' + displaySGV; + var delta = sbx.properties.delta && sbx.properties.delta.display; + if (delta) { + line += ' ' + delta; + } + line += ' ' + sbx.unitsLabel; + + return line; + }; + + sbx.appendPropertyLine = function appendPropertyLine (propertyName, lines) { + lines = lines || []; + + var displayLine = sbx.properties[propertyName] && sbx.properties[propertyName].displayLine; + if (displayLine) { + lines.push(displayLine); + } + + return lines; + }; + + sbx.prepareDefaultLines = function prepareDefaultLines(displaySGV) { + var lines = [sbx.buildBGNowLine(displaySGV)]; + sbx.appendPropertyLine('rawbg', lines); + sbx.appendPropertyLine('bwp', lines); + sbx.appendPropertyLine('iob', lines); + sbx.appendPropertyLine('cob', lines); + + return lines; + }; + + sbx.buildDefaultMessage = function buildDefaultMessage(displaySGV) { + return sbx.prepareDefaultLines(displaySGV).join('\n'); + }; + function displayBg (bg) { if (Number(bg) === 39) { return 'LOW'; diff --git a/tests/ar2.test.js b/tests/ar2.test.js index 28ca4e8e6f1..20661ba0e92 100644 --- a/tests/ar2.test.js +++ b/tests/ar2.test.js @@ -32,11 +32,14 @@ describe('ar2', function ( ) { var sbx = require('../lib/sandbox')().serverInit(env, ctx); delta.setProperties(sbx); + sbx.offerProperty('iob', function setFakeIOB() { + return {displayLine: 'IOB: 1.25U'}; + }); ar2.checkNotifications(sbx); var highest = ctx.notifications.findHighestAlarm(); highest.level.should.equal(ctx.notifications.levels.WARN); highest.title.should.equal('Warning, HIGH predicted'); - highest.message.should.startWith('BG Now: 170 +20 mg/dl'); + highest.message.should.equal('BG Now: 170 +20 mg/dl\nBG 15m: 206 mg/dl\nIOB: 1.25U'); done(); }); diff --git a/tests/sandbox.test.js b/tests/sandbox.test.js index 58326a17c94..8eafaebf29d 100644 --- a/tests/sandbox.test.js +++ b/tests/sandbox.test.js @@ -29,14 +29,18 @@ describe('sandbox', function ( ) { done(); }); - it('init on server', function (done) { + function createServerSandbox() { var env = require('../env')(); var ctx = {}; ctx.data = require('../lib/data')(env, ctx); - ctx.data.sgvs = [{sgv: 100}]; ctx.notifications = require('../lib/notifications')(env, ctx); - var sbx = sandbox.serverInit(env, ctx); + return sandbox.serverInit(env, ctx); + } + + it('init on server', function (done) { + var sbx = createServerSandbox(); + sbx.data.sgvs = [{sgv: 100}]; should.exist(sbx.notifications.requestNotify); should.not.exist(sbx.notifications.process); @@ -47,12 +51,7 @@ describe('sandbox', function ( ) { }); it('display 39 as LOW and 401 as HIGH', function () { - var env = require('../env')(); - var ctx = {}; - ctx.data = require('../lib/data')(env, ctx); - ctx.notifications = require('../lib/notifications')(env, ctx); - - var sbx = sandbox.serverInit(env, ctx); + var sbx = createServerSandbox(); sbx.displayBg(39).should.equal('LOW'); sbx.displayBg('39').should.equal('LOW'); @@ -60,4 +59,25 @@ describe('sandbox', function ( ) { sbx.displayBg('401').should.equal('HIGH'); }); + it('build BG Now line using properties', function ( ) { + var sbx = createServerSandbox(); + sbx.properties = { delta: {display: '+5' } }; + + sbx.buildBGNowLine(99).should.equal('BG Now: 99 +5 mg/dl'); + + }); + + it('build default message using properties', function ( ) { + var sbx = createServerSandbox(); + sbx.properties = { + delta: {display: '+5' } + , rawbg: {displayLine: 'Raw BG: 100 mg/dl'} + , iob: {displayLine: 'IOB: 1.25U'} + , cob: {displayLine: 'COB: 15g'} + }; + + sbx.buildDefaultMessage(99).should.equal('BG Now: 99 +5 mg/dl\nRaw BG: 100 mg/dl\nIOB: 1.25U\nCOB: 15g'); + + }); + }); From 853a58c94e5dfb8825835b3692235660e1180b09 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 3 Jul 2015 01:01:57 -0700 Subject: [PATCH 271/661] converted the direction to a plugin less code in client.js and using it for messages too --- README.md | 1 + env.js | 4 +- lib/data.js | 19 +------- lib/plugins/direction.js | 72 ++++++++++++++++++++++++++++ lib/plugins/index.js | 4 +- lib/plugins/pluginbase.js | 16 ++++--- lib/plugins/rawbg.js | 2 +- lib/sandbox.js | 7 +++ static/css/main.css | 23 ++++----- static/index.html | 2 +- static/js/client.js | 28 +---------- tests/ar2.test.js | 5 +- tests/direction.test.js | 98 +++++++++++++++++++++++++++++++++++++++ tests/sandbox.test.js | 7 +-- 14 files changed, 218 insertions(+), 70 deletions(-) create mode 100644 lib/plugins/direction.js create mode 100644 tests/direction.test.js diff --git a/README.md b/README.md index 5ff52f8c910..72a1e09029c 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `BWP_SNOOZE` - (`0.10`) If BG is higher then the `target_high` and `BWP` < `BWP_SNOOZE` alarms will be snoozed for `BWP_SNOOZE_MINS`. * `cage` (Cannula Age) - Calculates the number of hours since the last `Site Change` treatment that was recorded. * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** + * `direction` (BG Direction) - Displays the trend direction. **Enabled by default.** * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended setting](#extended-settings) `AR2_USE_RAW=true` to forecast using `rawbg` values. * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. diff --git a/env.js b/env.js index 1c212aa9709..cb1b0365666 100644 --- a/env.js +++ b/env.js @@ -95,7 +95,7 @@ function config ( ) { //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins !== '') { - env.defaults.showPlugins += ' delta upbat'; + env.defaults.showPlugins += ' delta direction upbat'; if (env.defaults.showRawbg === 'always' || env.defaults.showRawbg === 'noise') { env.defaults.showPlugins += 'rawbg'; } @@ -193,7 +193,7 @@ function config ( ) { } //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta upbat errorcodes'; + env.enable += ' delta direction upbat errorcodes'; env.extendedSettings = findExtendedSettings(env.enable, process.env); diff --git a/lib/data.js b/lib/data.js index a5c04eafc81..802523ba755 100644 --- a/lib/data.js +++ b/lib/data.js @@ -27,23 +27,6 @@ function init(env, ctx) { , TWO_DAYS = 172800000; - var dir2Char = { - 'NONE': '⇼', - 'DoubleUp': '⇈', - 'SingleUp': '↑', - 'FortyFiveUp': '↗', - 'Flat': '→', - 'FortyFiveDown': '↘', - 'SingleDown': '↓', - 'DoubleDown': '⇊', - 'NOT COMPUTABLE': '-', - 'RATE OUT OF RANGE': '↮' - }; - - function directionToChar(direction) { - return dir2Char[direction] || '-'; - } - data.clone = function clone() { return _.cloneDeep(data, function (value) { //special handling of mongo ObjectID's @@ -83,7 +66,7 @@ function init(env, ctx) { }); } else if (element.sgv) { sgvs.push({ - y: element.sgv, x: element.date, device: element.device, direction: directionToChar(element.direction), filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi + y: element.sgv, x: element.date, device: element.device, direction: element.direction, filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi }); } } diff --git a/lib/plugins/direction.js b/lib/plugins/direction.js new file mode 100644 index 00000000000..0a282f47dfd --- /dev/null +++ b/lib/plugins/direction.js @@ -0,0 +1,72 @@ +'use strict'; + +var _ = require('lodash'); + +function init() { + + function direction() { + return direction; + } + + direction.label = 'BG direction'; + direction.pluginType = 'bg-status'; + + direction.setProperties = function setProperties (sbx) { + sbx.offerProperty('direction', function setDirection ( ) { + return direction.info(_.last(sbx.data.sgvs)); + }); + }; + + direction.updateVisualisation = function updateVisualisation (sbx) { + var prop = sbx.properties.direction; + + var lastSGV = _.last(sbx.data.sgvs); + if (lastSGV && lastSGV.y < 39) { + prop.value = 'CGM ERROR'; + prop.label = '✖'; + } + + sbx.pluginBase.updatePillText(direction, { + label: prop.label + , directText: true + }); + }; + + direction.info = function info(sgv) { + var result = { display: null }; + + if (!sgv) { return result; } + + result.value = sgv.direction; + result.label = directionToChar(result.value); + result.entity = charToEntity(result.label); + + return result; + }; + + var dir2Char = { + NONE: '⇼' + , DoubleUp: '⇈' + , SingleUp: '↑' + , FortyFiveUp: '↗' + , Flat: '→' + , FortyFiveDown: '↘' + , SingleDown: '↓' + , DoubleDown: '⇊' + , 'NOT COMPUTABLE': '-' + , 'RATE OUT OF RANGE': '⇕' + }; + + function charToEntity(char) { + return char && char.length && '&#' + char.charCodeAt(0) + ';'; + } + + function directionToChar(direction) { + return dir2Char[direction] || '-'; + } + + return direction(); + +} + +module.exports = init; \ No newline at end of file diff --git a/lib/plugins/index.js b/lib/plugins/index.js index d92c37b0b8e..03c1ca8eaef 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -20,6 +20,7 @@ function init() { var clientDefaultPlugins = [ require('./rawbg')() , require('./delta')() + , require('./direction')() , require('./iob')() , require('./cob')() , require('./boluswizardpreview')() @@ -31,6 +32,7 @@ function init() { var serverDefaultPlugins = [ require('./rawbg')() , require('./delta')() + , require('./direction')() , require('./ar2')() , require('./simplealarms')() , require('./errorcodes')() @@ -82,7 +84,7 @@ function init() { }; //these plugins are either always on or have custom settings - plugins.specialPlugins = 'delta upbat rawbg'; + plugins.specialPlugins = 'delta direction upbat rawbg'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { diff --git a/lib/plugins/pluginbase.js b/lib/plugins/pluginbase.js index ac876edc9a4..a06b24625c9 100644 --- a/lib/plugins/pluginbase.js +++ b/lib/plugins/pluginbase.js @@ -15,7 +15,7 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { container = majorPills; } else if (plugin.pluginType === 'pill-status') { container = statusPills; - } else if (plugin.pluginType === 'pill-alt') { + } else if (plugin.pluginType === 'bg-status') { container = bgStatus; } else { container = minorPills; @@ -59,13 +59,17 @@ function init (majorPills, minorPills, statusPills, bgStatus, tooltip) { pill.addClass(options.pillClass); - pill.find('label').attr('class', options.labelClass).text(options.label); + if (options.directText) { + pill.text(options.label); + } else { + pill.find('label').attr('class', options.labelClass).text(options.label); - pill.find('em') - .attr('class', options.valueClass) - .toggle(options.value != null) - .text(options.value) + pill.find('em') + .attr('class', options.valueClass) + .toggle(options.value != null) + .text(options.value) ; + } if (options.info) { var html = _.map(options.info, function mapInfo (i) { diff --git a/lib/plugins/rawbg.js b/lib/plugins/rawbg.js index 9619bf21a4a..233e3883216 100644 --- a/lib/plugins/rawbg.js +++ b/lib/plugins/rawbg.js @@ -9,7 +9,7 @@ function init() { } rawbg.label = 'Raw BG'; - rawbg.pluginType = 'pill-alt'; + rawbg.pluginType = 'bg-status'; rawbg.pillFlip = true; rawbg.setProperties = function setProperties (sbx) { diff --git a/lib/sandbox.js b/lib/sandbox.js index 83703370bad..071b7b8153b 100644 --- a/lib/sandbox.js +++ b/lib/sandbox.js @@ -121,10 +121,17 @@ function init ( ) { sbx.buildBGNowLine = function buildBGNowLine (displaySGV) { var line = 'BG Now: ' + displaySGV; + var delta = sbx.properties.delta && sbx.properties.delta.display; if (delta) { line += ' ' + delta; } + + var trend = sbx.properties.trend && sbx.properties.trend.label; + if (trend) { + line += ' ' + trend; + } + line += ' ' + sbx.unitsLabel; return line; diff --git a/static/css/main.css b/static/css/main.css index bb17a8fda3a..e5324398406 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -68,12 +68,13 @@ body { font-size: 90px; } -.bgStatus .currentDirection { - font-weight: normal; - text-decoration: none; - font-size: 90px; - line-height: 90px; - vertical-align: middle; +.bgStatus .pill.direction { + font-weight: normal; + text-decoration: none; + font-size: 90px; + line-height: 90px; + vertical-align: middle; + border: none; } .bgStatus .majorPills { @@ -360,7 +361,7 @@ div.tooltip { font-size: 70px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 70px; line-height: 70px; } @@ -442,7 +443,7 @@ div.tooltip { font-size: 50px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } @@ -500,7 +501,7 @@ div.tooltip { padding-left: 20px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 60px; line-height: 60px; } @@ -626,7 +627,7 @@ div.tooltip { font-size: 70px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } @@ -673,7 +674,7 @@ div.tooltip { font-size: 60px; } - .bgStatus .currentDirection { + .bgStatus .pill.direction { font-size: 50px; line-height: 50px; } diff --git a/static/index.html b/static/index.html index a44fca3ca42..38d8fb23c84 100644 --- a/static/index.html +++ b/static/index.html @@ -49,7 +49,7 @@

    Nightscout

    --- - - + -
    @@ -256,6 +257,7 @@

    Nightscout

    + diff --git a/static/js/treatment.js b/static/js/treatment.js new file mode 100644 index 00000000000..2beec340066 --- /dev/null +++ b/static/js/treatment.js @@ -0,0 +1,116 @@ +'use strict'; + +function initTreatmentDrawer() { + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); + $('#eventDateValue').val(new Date().toDateInputValue()); +} + +function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + var eventTimeDisplay = ''; + if ($('#othertime').is(':checked')) { + data.eventTime = mergeInputTime('#eventTimeValue','#eventDateValue'); + eventTimeDisplay = data.eventTime.toLocaleString(); + } + + var dataJson = JSON.stringify(data, null, ' '); + + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + ( data.glucose ? '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType : '' ) + + ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + + ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + + ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + + ( data.notes ? '\nNotes: ' + data.notes : '' ) + + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + + (eventTimeDisplay ? '\nEvent Time: ' + eventTimeDisplay: '' ) + ); + + if (ok) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } + } + + if (event) { + event.preventDefault(); + } +} + +$('#treatmentDrawerToggle').click(function(event) { + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); + event.preventDefault(); +}); + +$('#treatmentDrawer').find('button').click(treatmentSubmit); + +$('#eventTime input:radio').change(function (event){ + if ($('#othertime').is(':checked')) { + $('#eventTimeValue').focus(); + } + event.preventDefault(); +}); + +$('.eventtimeinput').focus(function (event) { + $('#othertime').prop('checked', true); + var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + $(this).attr('oldminutes',time.getMinutes()); + $(this).attr('oldhours',time.getHours()); + event.preventDefault(); +}); + +$('.eventtimeinput').change(function (event) { + $('#othertime').prop('checked', true); + var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + if ($(this).attr('oldminutes')==59 && time.getMinutes()==0) time.addHours(1); + if ($(this).attr('oldminutes')==0 && time.getMinutes()==59) time.addHours(-1); + $('#eventTimeValue').val(time.toTimeString().slice(0,5)); + $('#eventDateValue').val(time.toDateInputValue()); + $(this).attr('oldminutes',time.getMinutes()); + $(this).attr('oldhours',time.getHours()); + event.preventDefault(); +}); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 0e144b81a56..770e51c7f81 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -187,19 +187,6 @@ function toggleDrawer(id, openCallback, closeCallback) { } -function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(currentTime()); -} - function currentTime() { var now = new Date(); var hours = now.getHours(); @@ -248,79 +235,6 @@ function showLocalstorageError() { } -function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = $('#preBolus').val(); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); - } - - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() !== 'now') { - var value = $('#eventTimeValue').val(); - var eventTimeParts = value.split(':'); - data.eventTime = new Date(); - data.eventTime.setHours(eventTimeParts[0]); - data.eventTime.setMinutes(eventTimeParts[1]); - data.eventTime.setSeconds(0); - data.eventTime.setMilliseconds(0); - eventTimeDisplay = formatTime(data.eventTime); - } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType + - '\nCarbs Given: ' + data.carbs + - '\nInsulin Given: ' + data.insulin + - '\nPre Bolus: ' + data.preBolus + - '\nNotes: ' + data.notes + - '\nEntered By: ' + data.enteredBy + - '\nEvent Time: ' + eventTimeDisplay); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } - } - - if (event) { - event.preventDefault(); - } -} - - var querystring = getQueryParms(); function Dropdown(el) { @@ -349,23 +263,6 @@ $('#drawerToggle').click(function(event) { event.preventDefault(); }); -$('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); -}); - -$('#treatmentDrawer').find('button').click(treatmentSubmit); - -$('#eventTime input:radio').change(function (){ - if ($('#othertime').attr('checked')) { - $('#eventTimeValue').focus(); - } -}); - -$('#eventTimeValue').focus(function () { - $('#othertime').attr('checked', 'checked'); -}); - $('#notification').click(function(event) { closeNotification(); event.preventDefault(); @@ -444,3 +341,37 @@ $(function() { openDrawer('#drawer'); } }); + +// some helpers for input "date" +function mergeInputTime(timeid,dateid) { + var value = $(timeid).val(); + var eventTimeParts = value.split(':'); + var eventTime = new Date($(dateid).val()); + eventTime.setHours(eventTimeParts[0]); + eventTime.setMinutes(eventTimeParts[1]); + eventTime.setSeconds(0); + eventTime.setMilliseconds(0); + return eventTime; +} + +Date.prototype.toDateInputValue = function toDateInputValue() { + var local = new Date(this); + local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); + return local.toJSON().slice(0,10); +} + +Date.prototype.addMinutes = function addMinutes(h) { + this.setTime(this.getTime() + (h*60*1000)); + return this; +} + +Date.prototype.addHours = function addHours(h) { + this.setTime(this.getTime() + (h*60*60*1000)); + return this; +} + +Date.prototype.addDays = function addDays(h) { + this.setTime(this.getTime() + (h*24*60*60*1000)); + return this; +} + From 0be552c43cf27f2e0642da78a3851e0f47c7fedf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 13 Jul 2015 14:10:39 -0700 Subject: [PATCH 389/661] bind pushPoint to sbx --- lib/plugins/ar2.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 606f5010e67..778433761a2 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -113,7 +113,7 @@ function init() { //fold left, each time update the accumulator result.predicted = _.reduce( new Array(6) //only 6 points are used for calculating avgLoss - , pushPoint + , pushPoint.bind(null, sbx) , initAR2(sgvs, sbx) ).points; @@ -148,7 +148,7 @@ function init() { var coneFactor = getConeFactor(sbx); function pushConePoints(result, step) { - var next = incrementAR2(result); + var next = incrementAR2(result, sbx); //offset from points so they are at a unique time if (coneFactor > 0) { @@ -288,8 +288,8 @@ function incrementAR2 (result, sbx) { }; } -function pushPoint(result) { - var next = incrementAR2(result); +function pushPoint(sbx, result) { + var next = incrementAR2(result, sbx); next.points.push(ar2Point( next From 712e077ce9939005f9e34c5f2c8e95d79df35060 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 13 Jul 2015 23:50:24 +0200 Subject: [PATCH 390/661] changes for codacy --- static/js/treatment.js | 8 ++++++-- static/js/ui-utils.js | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 2beec340066..7e42d65e78f 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -106,8 +106,12 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); var time = mergeInputTime('#eventTimeValue','#eventDateValue'); - if ($(this).attr('oldminutes')==59 && time.getMinutes()==0) time.addHours(1); - if ($(this).attr('oldminutes')==0 && time.getMinutes()==59) time.addHours(-1); + if ($(this).attr('oldminutes')=='59' && time.getMinutes()=='0') { + time.addHours(1); + } + if ($(this).attr('oldminutes')=='0' && time.getMinutes()=='59') { + time.addHours(-1); + } $('#eventTimeValue').val(time.toTimeString().slice(0,5)); $('#eventDateValue').val(time.toDateInputValue()); $(this).attr('oldminutes',time.getMinutes()); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 770e51c7f81..3842d3f6660 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -358,20 +358,20 @@ Date.prototype.toDateInputValue = function toDateInputValue() { var local = new Date(this); local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); return local.toJSON().slice(0,10); -} +}; Date.prototype.addMinutes = function addMinutes(h) { this.setTime(this.getTime() + (h*60*1000)); return this; -} +}; Date.prototype.addHours = function addHours(h) { this.setTime(this.getTime() + (h*60*60*1000)); return this; -} +}; Date.prototype.addDays = function addDays(h) { this.setTime(this.getTime() + (h*24*60*60*1000)); return this; -} +}; From d30987966acfeede09b66f02e39194a3bf1ba70e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 13 Jul 2015 17:14:48 -0700 Subject: [PATCH 391/661] added some debug to try tracking down the intermitant retro mode when switching back to a tab after some time --- static/js/client.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 60b6accb3e6..055b4321d17 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -73,7 +73,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , clip; var container = $('.container') - , bgButton = $('.bgButton') , bgStatus = $('.bgStatus') , currentBG = $('.bgStatus .currentBG') , majorPills = $('.bgStatus .majorPills') @@ -234,7 +233,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove this code and all references to `type: 'server-forecast'` - var lastTime = data.length > 0 ? data[data.length - 1].mills : Date.now(); + var last = _.last(data); + var lastTime = last && last.mills; + if (!lastTime) { + console.error('Bad Data, last point has no mills', last); + lastTime = Date.now(); + } + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; for (var i = 1; i <= n; i++) { data.push({ From 15c8f40e8188a8a859a8f91c0ac905b10048a5f4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:42:08 -0700 Subject: [PATCH 392/661] remove false warning --- lib/api/entries/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/api/entries/index.js b/lib/api/entries/index.js index 1ad6e90d499..048a51cc0b7 100644 --- a/lib/api/entries/index.js +++ b/lib/api/entries/index.js @@ -30,7 +30,6 @@ function configure (app, wares, ctx) { function force_typed_data (opts) { function sync (data, next) { if (data.type !== opts.type) { - console.warn('BAD DATA TYPE, setting', data.type, 'to', opts.type); data.type = opts.type; } next(null, data); From 4940e27569dee5ccd2aa1d34c599d6c8794b21a9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:43:56 -0700 Subject: [PATCH 393/661] add timestamps to notification event logging --- lib/notifications.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/notifications.js b/lib/notifications.js index 151025b6758..fd472ed00cc 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -207,20 +207,25 @@ function init (env, ctx) { function logEmitEvent(notify) { var type = notify.level >= levels.WARN ? 'ALARM' : (notify.clear ? 'ALL CLEAR' : 'NOTIFICATION'); console.info([ - 'EMITTING ' + type + ':' + logTimestamp() + '\tEMITTING ' + type + ':' , ' ' + JSON.stringify(notifyToView(notify)) ].join('\n')); } function logSnoozingEvent(highestAlarm, snoozedBy) { console.info([ - 'SNOOZING ALARM:' + logTimestamp() + '\tSNOOZING ALARM:' , ' ' + JSON.stringify(notifyToView(highestAlarm)) , ' BECAUSE:' , ' ' + JSON.stringify(snoozeToView(snoozedBy)) ].join('\n')); } + //TODO: we need a common logger, but until then... + function logTimestamp ( ) { + return (new Date).toISOString(); + } + return notifications(); } From d86a27017d5a6db6046a8d9492322062d6b68cc9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:46:05 -0700 Subject: [PATCH 394/661] simplify ar2 timing init/increment --- lib/plugins/ar2.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 778433761a2..9814c9fb30f 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -113,7 +113,7 @@ function init() { //fold left, each time update the accumulator result.predicted = _.reduce( new Array(6) //only 6 points are used for calculating avgLoss - , pushPoint.bind(null, sbx) + , pushPoint , initAR2(sgvs, sbx) ).points; @@ -148,7 +148,7 @@ function init() { var coneFactor = getConeFactor(sbx); function pushConePoints(result, step) { - var next = incrementAR2(result, sbx); + var next = incrementAR2(result); //offset from points so they are at a unique time if (coneFactor > 0) { @@ -272,24 +272,24 @@ function initAR2 (sgvs, sbx) { var mgdl5MinsAgo = delta.calc(prev, current, sbx).mgdl5MinsAgo; return { - forecastTime: sbx.lastSGVMills() + forecastTime: current.mills || Date.now() , points: [] , prev: Math.log(mgdl5MinsAgo / BG_REF) , curr: Math.log(current.mgdl / BG_REF) }; } -function incrementAR2 (result, sbx) { +function incrementAR2 (result) { return { - forecastTime: result.forecastTime ? result.forecastTime + FIVE_MINUTES : sbx.lastSGVMills() + forecastTime: result.forecastTime + FIVE_MINUTES , points: result.points || [] , prev: result.curr , curr: AR[0] * result.prev + AR[1] * result.curr }; } -function pushPoint(sbx, result) { - var next = incrementAR2(result, sbx); +function pushPoint(result) { + var next = incrementAR2(result); next.points.push(ar2Point( next From 6b2e09545b49456cc1bc6c7014222ac410157b4e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 00:59:41 -0700 Subject: [PATCH 395/661] new look for the forecast points --- static/js/client.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 055b4321d17..fabdb346b33 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -443,6 +443,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); if (type === 'mbg') { radius *= 2; + } else if (type === 'forecast') { + radius = Math.min(3, radius - 1); } else if (type === 'rawbg') { radius = Math.min(2, radius - 1); } @@ -476,11 +478,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return yScale(scaled); } }) - .attr('fill', function (d) { return d.color; }) + .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : '#0099ff'); + return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); }) .attr('r', function (d) { return dotRadius(d.type); }); From 466ca382fa22776dadb1a3728e4cb3bc7cc8d069 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 01:02:19 -0700 Subject: [PATCH 396/661] scale the thresholds for now, need to be able to set them in either units --- lib/plugins/ar2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 9814c9fb30f..210ae6ff186 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -209,9 +209,9 @@ function selectEventType (prop, sbx) { var eventName = ''; if (in20mins !== undefined) { - if (in20mins > sbx.thresholds.bg_target_top) { + if (in20mins > sbx.scaleMgdl(sbx.thresholds.bg_target_top)) { eventName = 'high'; - } else if (in20mins < sbx.thresholds.bg_target_bottom) { + } else if (in20mins < sbx.scaleMgdl(sbx.thresholds.bg_target_bottom)) { eventName = 'low'; } } @@ -224,7 +224,7 @@ function buildTitle(prop, sbx) { var title = levels.toDisplay(prop.level) + ', ' + rangeLabel; var sgv = sbx.lastScaledSGV(); - if (sgv > sbx.thresholds.bg_target_bottom && sgv < sbx.thresholds.bg_target_top) { + if (sgv > sbx.scaleMgdl(sbx.thresholds.bg_target_bottom) && sgv < sbx.scaleMgdl(sbx.thresholds.bg_target_top)) { title += ' predicted'; } if (prop.usingRaw) { From a48ccffb0b9dfc15dd876c2f06712483e86c71ed Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:19:04 +0300 Subject: [PATCH 397/661] Revert older Pebble API behavior, where bgDelta was a string --- lib/pebble.js | 2 +- tests/pebble.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 5484342b6e5..c1e50f19130 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -63,7 +63,7 @@ function prepareSGVs (req, sbx) { //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + bgs[0].bgdelta = String(deltaResult && deltaResult.scaled || 0); bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); if (req.iob) { diff --git a/tests/pebble.test.js b/tests/pebble.test.js index a7568784889..82abfa46be0 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -102,7 +102,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(1); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -127,7 +127,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(1); var bg = bgs[0]; bg.sgv.should.equal('4.6'); - bg.bgdelta.should.equal(-0.1); + bg.bgdelta.should.equal('-0.1'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -151,7 +151,7 @@ describe('Pebble Endpoint', function ( ) { bgs.length.should.equal(2); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -187,7 +187,7 @@ describe('Pebble Endpoint with Raw and IOB', function ( ) { bgs.length.should.equal(2); var bg = bgs[0]; bg.sgv.should.equal('82'); - bg.bgdelta.should.equal(-2); + bg.bgdelta.should.equal('-2'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); From b1b83907263ca6dcd2a474bd29c886487984e2a9 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:22:47 +0300 Subject: [PATCH 398/661] Show basal calculation in case basal can be set to exact zero or +100% --- lib/plugins/boluswizardpreview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 75276ca760f..1ca425442f5 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -225,12 +225,12 @@ function pushTempBasalAdjustments(prop, info, sbx) { info.push({label: 'Current basal', value: sbx.data.profile.getBasal(sbx.time)}); info.push({label: 'Basal change to account for ' + prop.bolusEstimateDisplay + ':', value: ''}); - if (prop.tempBasalAdjustment.thirtymin > 0 && prop.tempBasalAdjustment.thirtymin < 200) { + if (prop.tempBasalAdjustment.thirtymin >= 0 && prop.tempBasalAdjustment.thirtymin <= 200) { info.push({label: '30m temp basal', value: '' + prop.tempBasalAdjustment.thirtymin + '% (' + sign + (prop.tempBasalAdjustment.thirtymin - 100) + '%)'}); } else { info.push({label: '30m temp basal', value: carbsOrBolusMessage}); } - if (prop.tempBasalAdjustment.onehour > 0 && prop.tempBasalAdjustment.onehour < 200) { + if (prop.tempBasalAdjustment.onehour >= 0 && prop.tempBasalAdjustment.onehour <= 200) { info.push({label: '1h temp basal', value: '' + prop.tempBasalAdjustment.onehour + '% (' + sign + (prop.tempBasalAdjustment.onehour - 100) + '%)'}); } else { info.push({label: '1h temp basal', value: carbsOrBolusMessage}); From 16c85ad4f7c3234d0a7da3d5b77c58423a6cf928 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:23:33 +0300 Subject: [PATCH 399/661] Calculate IOB for treatments that match the current timestamp --- lib/plugins/iob.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/iob.js b/lib/plugins/iob.js index 64ccc99374a..4307e0abd9c 100644 --- a/lib/plugins/iob.js +++ b/lib/plugins/iob.js @@ -33,7 +33,7 @@ function init() { var lastBolus = null; _.forEach(treatments, function eachTreatment(treatment) { - if (treatment.mills < time) { + if (treatment.mills <= time) { var tIOB = iob.calcTreatment(treatment, profile, time); if (tIOB.iobContrib > 0) { lastBolus = treatment; From d6d767afc2a37108951080464746f052b2e11c01 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 13:26:06 +0300 Subject: [PATCH 400/661] More comprehensive test for BWP, including a test in MMOL --- tests/boluswizardpreview.test.js | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index e58e5aca752..179916723a6 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -38,6 +38,121 @@ describe('boluswizardpreview', function ( ) { , target_low: 100 }; + it('should calculate IOB results correctly with 0 IOB', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = []; + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(0); + results.effectDisplay.should.equal(0); + results.outcome.should.equal(100); + results.outcomeDisplay.should.equal(100); + results.bolusEstimate.should.equal(0); + results.displayLine.should.equal('BWP: 0U'); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = [{mills: now, insulin: '1.0'}]; + + var profile = { + dia: 3 + , sens: 50 + , target_high: 100 + , target_low: 50 + }; + + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + Math.round(results.effect).should.equal(50); + results.effectDisplay.should.equal(50); + Math.round(results.outcome).should.equal(50); + results.outcomeDisplay.should.equal(50); + results.bolusEstimate.should.equal(0); + results.displayLine.should.equal('BWP: 0U'); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB resulting in going low', function (done) { + ctx.notifications.initRequests(); + ctx.data.sgvs = [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]; + ctx.data.treatments = [{mills: now, insulin: '1.0'}]; + + var profile = { + dia: 3 + , sens: 50 + , target_high: 200 + , target_low: 100 + , basal: 1 + }; + + + ctx.data.profiles = [profile]; + + var sbx = prepareSandbox(); + var results = boluswizardpreview.calc(sbx); + + Math.round(results.effect).should.equal(50); + results.effectDisplay.should.equal(50); + Math.round(results.outcome).should.equal(50); + results.outcomeDisplay.should.equal(50); + Math.round(results.bolusEstimate).should.equal(-1); + results.displayLine.should.equal('BWP: -1.00U'); + results.tempBasalAdjustment.thirtymin.should.equal(-100); + results.tempBasalAdjustment.onehour.should.equal(0); + + done(); + }); + + it('should calculate IOB results correctly with 1.0 U IOB resulting in going low in MMOL', function (done) { + + // boilerplate for client sandbox running in mmol + + var profileData = { + dia: 3 + , units: 'mmol' + , sens: 10 + , target_high: 10 + , target_low: 5.6 + , basal: 1 + }; + + var sandbox = require('../lib/sandbox')(); + var app = { }; + var pluginBase = {}; + var clientSettings = { units: 'mmol' }; + var data = {sgvs: [{mills: before, mgdl: 100}, {mills: now, mgdl: 100}]}; + data.treatments = [{mills: now, insulin: '1.0'}]; + data.profile = require('../lib/profilefunctions')([profileData]); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var iob = require('../lib/plugins/iob')(); + sbx.properties.iob = iob.calcTotal(data.treatments, data.profile, now); + + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(10); + results.outcome.should.equal(-4.4); + results.bolusEstimate.should.equal(-1); + results.displayLine.should.equal('BWP: -1.00U'); + results.tempBasalAdjustment.thirtymin.should.equal(-100); + results.tempBasalAdjustment.onehour.should.equal(0); + + done(); + }); + + + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); ctx.data.sgvs = [{mills: before, mgdl: 95}, {mills: now, mgdl: 100}]; From 9fd0ebbd43cce8231fa0acefde75a6ce36ed0496 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 14:18:47 +0300 Subject: [PATCH 401/661] One more MMOL test for BWP --- tests/boluswizardpreview.test.js | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/boluswizardpreview.test.js b/tests/boluswizardpreview.test.js index 179916723a6..e24e130595b 100644 --- a/tests/boluswizardpreview.test.js +++ b/tests/boluswizardpreview.test.js @@ -152,6 +152,42 @@ describe('boluswizardpreview', function ( ) { }); + it('should calculate IOB results correctly with 0.45 U IOB resulting in going low in MMOL', function (done) { + + // boilerplate for client sandbox running in mmol + + var profileData = { + dia: 3 + , units: 'mmol' + , sens: 9 + , target_high: 6 + , target_low: 5 + , basal: 0.125 + }; + + var sandbox = require('../lib/sandbox')(); + var app = { }; + var pluginBase = {}; + var clientSettings = { units: 'mmol' }; + var data = {sgvs: [{mills: before, mgdl: 175}, {mills: now, mgdl: 153}]}; + data.treatments = [{mills: now, insulin: '0.45'}]; + data.profile = require('../lib/profilefunctions')([profileData]); + var sbx = sandbox.clientInit(app, clientSettings, Date.now(), pluginBase, data); + var iob = require('../lib/plugins/iob')(); + sbx.properties.iob = iob.calcTotal(data.treatments, data.profile, now); + + var results = boluswizardpreview.calc(sbx); + + results.effect.should.equal(4.05); + results.outcome.should.equal(4.45); + Math.round(results.bolusEstimate*100).should.equal(-6); + results.displayLine.should.equal('BWP: -0.07U'); + results.tempBasalAdjustment.thirtymin.should.equal(2); + results.tempBasalAdjustment.onehour.should.equal(51); + + done(); + }); + it('Not trigger an alarm when in range', function (done) { ctx.notifications.initRequests(); From 326d9d379e18098d67b468fa15943698df91e1a7 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 14 Jul 2015 15:06:55 +0300 Subject: [PATCH 402/661] Show the BG target in BWP, when above or below --- lib/plugins/boluswizardpreview.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 1ca425442f5..070fbcecc9a 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -133,6 +133,8 @@ function init() { if (results.outcome > target_high) { delta = results.outcome - target_high; results.bolusEstimate = delta / sens; + results.aimTarget= target_high; + results.aimTargetString = "above high"; } var target_low = profile.getLowBGTarget(sbx.time); @@ -140,6 +142,8 @@ function init() { if (results.outcome < target_low) { delta = Math.abs(results.outcome - target_low); results.bolusEstimate = delta / sens * -1; + results.aimTarget = target_low; + results.aimTargetString = "below low"; } if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal(sbx.time)) { @@ -222,6 +226,9 @@ function pushTempBasalAdjustments(prop, info, sbx) { } info.push({label: '---------', value: ''}); + if (prop.aimTarget) { + info.push({label: 'Projection ' + prop.aimTargetString +' target', value: 'aiming at ' + prop.aimTarget + ' ' + sbx.units}); + } info.push({label: 'Current basal', value: sbx.data.profile.getBasal(sbx.time)}); info.push({label: 'Basal change to account for ' + prop.bolusEstimateDisplay + ':', value: ''}); From 84b1ab959623972b782a5007921cf4db087e9a1b Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 18:37:52 +0200 Subject: [PATCH 403/661] Data.prototype.. converted to Nightscout.utils. treatment post split to 2 functions --- lib/utils.js | 35 +++++++++++++++++++- static/js/treatment.js | 74 +++++++++++++++++++++--------------------- static/js/ui-utils.js | 33 ------------------- 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 86476956a32..7d68c3b2704 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -59,7 +59,40 @@ function init() { }; - return utils(); + // some helpers for input "date" + utils.mergeInputTime = function mergeInputTime(timeid,dateid) { + var value = $(timeid).val(); + var eventTimeParts = value.split(':'); + var eventTime = new Date($(dateid).val()); + eventTime.setHours(eventTimeParts[0]); + eventTime.setMinutes(eventTimeParts[1]); + eventTime.setSeconds(0); + eventTime.setMilliseconds(0); + return eventTime; + } + + utils.toDateInputValue = function toDateInputValue(datetime) { + var local = new Date(datetime); + local.setMinutes(datetime.getMinutes() - datetime.getTimezoneOffset()); + return local.toJSON().slice(0,10); + }; + + utils.addMinutes = function addMinutes(datetime,h) { + datetime.setTime(datetime.getTime() + (h*60*1000)); + return datetime; + }; + + utils.addHours = function addHours(datetime,h) { + datetime.setTime(datetime.getTime() + (h*60*60*1000)); + return datetime; + }; + + utils.addDays = function addDays(datetime,h) { + datetime.setTime(datetime.getTime() + (h*24*60*60*1000)); + return datetime; + }; + + return utils(); } module.exports = init; \ No newline at end of file diff --git a/static/js/treatment.js b/static/js/treatment.js index 7e42d65e78f..04cd606b558 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -11,7 +11,7 @@ function initTreatmentDrawer() { $('#enteredBy').val(browserStorage.get('enteredBy') || ''); $('#nowtime').prop('checked', true); $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); - $('#eventDateValue').val(new Date().toDateInputValue()); + $('#eventDateValue').val(Nightscout.utils.toDateInputValue(new Date())); } function treatmentSubmit(event) { @@ -43,37 +43,10 @@ function treatmentSubmit(event) { if (errors.length > 0) { window.alert(errors.join('\n')); } else { - var eventTimeDisplay = ''; if ($('#othertime').is(':checked')) { - data.eventTime = mergeInputTime('#eventTimeValue','#eventDateValue'); - eventTimeDisplay = data.eventTime.toLocaleString(); + data.eventTime = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); } - - var dataJson = JSON.stringify(data, null, ' '); - - var ok = window.confirm( - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - ( data.glucose ? '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType : '' ) + - ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + - ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + - ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + - ( data.notes ? '\nNotes: ' + data.notes : '' ) + - ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - (eventTimeDisplay ? '\nEvent Time: ' + eventTimeDisplay: '' ) - ); - - if (ok) { - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } + confirmPost(data); } if (event) { @@ -81,6 +54,33 @@ function treatmentSubmit(event) { } } +function confirmPost(data) { + var ok = window.confirm( + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType + + ( data.glucose ? '\nBlood glucose: ' + data.glucose + + '\nMethod: ' + data.glucoseType : '' ) + + ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + + ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + + ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + + ( data.notes ? '\nNotes: ' + data.notes : '' ) + + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + + (data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ) + ); + + if (ok) { + var dataJson = JSON.stringify(data, null, ' '); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } +} + $('#treatmentDrawerToggle').click(function(event) { toggleDrawer('#treatmentDrawer', initTreatmentDrawer); event.preventDefault(); @@ -97,7 +97,7 @@ $('#eventTime input:radio').change(function (event){ $('.eventtimeinput').focus(function (event) { $('#othertime').prop('checked', true); - var time = mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); @@ -105,15 +105,15 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); - var time = mergeInputTime('#eventTimeValue','#eventDateValue'); - if ($(this).attr('oldminutes')=='59' && time.getMinutes()=='0') { - time.addHours(1); + var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { + Nightscout.utils.addHours(time,1); } - if ($(this).attr('oldminutes')=='0' && time.getMinutes()=='59') { - time.addHours(-1); + if ($(this).attr('oldminutes')==='0' && time.getMinutes()===59) { + Nightscout.utils.addHours(time,-1); } $('#eventTimeValue').val(time.toTimeString().slice(0,5)); - $('#eventDateValue').val(time.toDateInputValue()); + $('#eventDateValue').val(Nightscout.utils.toDateInputValue(time)); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 3842d3f6660..98b7a1d7d14 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -342,36 +342,3 @@ $(function() { } }); -// some helpers for input "date" -function mergeInputTime(timeid,dateid) { - var value = $(timeid).val(); - var eventTimeParts = value.split(':'); - var eventTime = new Date($(dateid).val()); - eventTime.setHours(eventTimeParts[0]); - eventTime.setMinutes(eventTimeParts[1]); - eventTime.setSeconds(0); - eventTime.setMilliseconds(0); - return eventTime; -} - -Date.prototype.toDateInputValue = function toDateInputValue() { - var local = new Date(this); - local.setMinutes(this.getMinutes() - this.getTimezoneOffset()); - return local.toJSON().slice(0,10); -}; - -Date.prototype.addMinutes = function addMinutes(h) { - this.setTime(this.getTime() + (h*60*1000)); - return this; -}; - -Date.prototype.addHours = function addHours(h) { - this.setTime(this.getTime() + (h*60*60*1000)); - return this; -}; - -Date.prototype.addDays = function addDays(h) { - this.setTime(this.getTime() + (h*24*60*60*1000)); - return this; -}; - From 7e46a08456e6a7bf3c3416c6823aa828c1eb0504 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 22:00:15 +0200 Subject: [PATCH 404/661] removed unused code --- lib/utils.js | 2 +- static/js/treatment.js | 7 +++---- static/js/ui-utils.js | 21 --------------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index 7d68c3b2704..f50fd89655a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -69,7 +69,7 @@ function init() { eventTime.setSeconds(0); eventTime.setMilliseconds(0); return eventTime; - } + }; utils.toDateInputValue = function toDateInputValue(datetime) { var local = new Date(datetime); diff --git a/static/js/treatment.js b/static/js/treatment.js index 04cd606b558..35e85fa30e1 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -55,7 +55,7 @@ function treatmentSubmit(event) { } function confirmPost(data) { - var ok = window.confirm( + var confirmtext = 'Please verify that the data entered is correct: ' + '\nEvent type: ' + data.eventType + ( data.glucose ? '\nBlood glucose: ' + data.glucose + @@ -65,10 +65,9 @@ function confirmPost(data) { ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + ( data.notes ? '\nNotes: ' + data.notes : '' ) + ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - (data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ) - ); + ( data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ); - if (ok) { + if (window.confirm(confirmtext)) { var dataJson = JSON.stringify(data, null, ' '); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/v1/treatments/', true); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 98b7a1d7d14..be4cab8b2a6 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -187,27 +187,6 @@ function toggleDrawer(id, openCallback, closeCallback) { } -function currentTime() { - var now = new Date(); - var hours = now.getHours(); - var minutes = now.getMinutes(); - - if (hours < 10) { hours = '0' + hours; } - if (minutes < 10) { minutes = '0' + minutes; } - - return ''+ hours + ':' + minutes; -} - -function formatTime(date) { - var hours = date.getHours(); - var minutes = date.getMinutes(); - var ampm = hours >= 12 ? 'pm' : 'am'; - hours = hours % 12; - hours = hours ? hours : 12; // the hour '0' should be '12' - minutes = minutes < 10 ? '0' + minutes : minutes; - return hours + ':' + minutes + ' ' + ampm; -} - function closeNotification() { var notify = $('#notification'); notify.hide(); From 623ff90e1f434fdf4e1433d1bd319db7baa72643 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 14 Jul 2015 22:11:08 +0200 Subject: [PATCH 405/661] removed jquery from utils.js. lowered code complexity --- lib/utils.js | 7 +++---- static/js/treatment.js | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/utils.js b/lib/utils.js index f50fd89655a..c9b061cf5d3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -60,10 +60,9 @@ function init() { }; // some helpers for input "date" - utils.mergeInputTime = function mergeInputTime(timeid,dateid) { - var value = $(timeid).val(); - var eventTimeParts = value.split(':'); - var eventTime = new Date($(dateid).val()); + utils.mergeInputTime = function mergeInputTime(timestring,datestring) { + var eventTimeParts = timestring.split(':'); + var eventTime = new Date(datestring); eventTime.setHours(eventTimeParts[0]); eventTime.setMinutes(eventTimeParts[1]); eventTime.setSeconds(0); diff --git a/static/js/treatment.js b/static/js/treatment.js index 35e85fa30e1..a9a9e29ebc5 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -44,7 +44,7 @@ function treatmentSubmit(event) { window.alert(errors.join('\n')); } else { if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); } confirmPost(data); } @@ -56,16 +56,15 @@ function treatmentSubmit(event) { function confirmPost(data) { var confirmtext = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType + - ( data.glucose ? '\nBlood glucose: ' + data.glucose + - '\nMethod: ' + data.glucoseType : '' ) + - ( data.carbs ? '\nCarbs Given: ' + data.carbs : '' ) + - ( data.insulin ? '\nInsulin Given: ' + data.insulin : '' ) + - ( data.preBolus ? '\nPre Bolus: ' + data.preBolus : '' ) + - ( data.notes ? '\nNotes: ' + data.notes : '' ) + - ( data.enteredBy ? '\nEntered By: ' + data.enteredBy : '') + - ( data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): '' ); + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType; + confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; + confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): ''; if (window.confirm(confirmtext)) { var dataJson = JSON.stringify(data, null, ' '); @@ -96,7 +95,7 @@ $('#eventTime input:radio').change(function (event){ $('.eventtimeinput').focus(function (event) { $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); $(this).attr('oldminutes',time.getMinutes()); $(this).attr('oldhours',time.getHours()); event.preventDefault(); @@ -104,7 +103,7 @@ $('.eventtimeinput').focus(function (event) { $('.eventtimeinput').change(function (event) { $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime('#eventTimeValue','#eventDateValue'); + var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { Nightscout.utils.addHours(time,1); } From cbb5c45961e1995fe96df52d72f22bff759b2c40 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 17:33:10 -0700 Subject: [PATCH 406/661] Dockerfile needs to be in a different repo --- .dockerignore | 17 ----------------- Dockerfile | 32 -------------------------------- 2 files changed, 49 deletions(-) delete mode 100644 .dockerignore delete mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 9bd34f41610..00000000000 --- a/.dockerignore +++ /dev/null @@ -1,17 +0,0 @@ -bower_components/ -node_modules/ - -.idea/ -*.iml - -*.env -static/bower_components/ -.*.sw? -.DS_Store - -.vagrant -/iisnode - -# istanbul output -coverage/ -npm-debug.log diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b9cf711dac0..00000000000 --- a/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM node:latest - -MAINTAINER Nightscout https://github.com/nightscout/ - -# Installing the required packages. -RUN apt-get update && apt-get install -y python-software-properties python g++ make git - -# Upgrade -RUN apt-get upgrade -y - -# We need to change user for security and for proper execution of all the NPM stages -# https://github.com/jspm/jspm-cli/issues/865 -# http://stackoverflow.com/questions/24308760/running-app-inside-docker-as-non-root-user - -RUN useradd --system -ms /bin/bash node - -RUN cd && cp -R .bashrc .profile /home/node - -ADD . /home/node/app -RUN chown -R node:node /home/node - -USER node - -ENV HOME /home/node -WORKDIR /home/node/app - -# Invoke NPM -RUN npm install - -# Expose the default port, although this does not matter at it will be exposed as an arbitrary port by the Docker network driver. -EXPOSE 1337 -CMD ["node", "server.js"] \ No newline at end of file From be3a2e7fc92cba4cf0a0bcb665db22fc8a81e1db Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 19:01:04 -0700 Subject: [PATCH 407/661] more specific check to enable forecast using raw data --- lib/plugins/ar2.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index 210ae6ff186..af859cf514b 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -80,8 +80,7 @@ function init() { function rawForecast (sbx) { var rawSGVs; - if (sbx.properties.rawbg && sbx.extendedSettings.useRaw) { - + if (sbx.properties.rawbg && sbx.extendedSettings.useRaw.toLowerCase() === 'true') { //TODO:OnlyOneCal - currently we only load the last cal, so we can't ignore future data var cal = _.last(sbx.data.cals); if (cal) { From 832fbb7644532fc142c7a25f8d6a91abb0bae0ee Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 20:29:26 -0700 Subject: [PATCH 408/661] fixed a couple warnings --- lib/plugins/boluswizardpreview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/plugins/boluswizardpreview.js b/lib/plugins/boluswizardpreview.js index 070fbcecc9a..f1af1c759ab 100644 --- a/lib/plugins/boluswizardpreview.js +++ b/lib/plugins/boluswizardpreview.js @@ -133,8 +133,8 @@ function init() { if (results.outcome > target_high) { delta = results.outcome - target_high; results.bolusEstimate = delta / sens; - results.aimTarget= target_high; - results.aimTargetString = "above high"; + results.aimTarget = target_high; + results.aimTargetString = 'above high'; } var target_low = profile.getLowBGTarget(sbx.time); @@ -143,7 +143,7 @@ function init() { delta = Math.abs(results.outcome - target_low); results.bolusEstimate = delta / sens * -1; results.aimTarget = target_low; - results.aimTargetString = "below low"; + results.aimTargetString = 'below low'; } if (results.bolusEstimate !== 0 && sbx.data.profile.getBasal(sbx.time)) { From d958cd2e1945e91f7732291022f7cbe291ede95b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 20:36:07 -0700 Subject: [PATCH 409/661] better check of useRaw option --- lib/plugins/ar2.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index af859cf514b..c4843af8205 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -80,7 +80,7 @@ function init() { function rawForecast (sbx) { var rawSGVs; - if (sbx.properties.rawbg && sbx.extendedSettings.useRaw.toLowerCase() === 'true') { + if (useRaw(sbx)) { //TODO:OnlyOneCal - currently we only load the last cal, so we can't ignore future data var cal = _.last(sbx.data.cals); if (cal) { @@ -98,6 +98,10 @@ function init() { return ar2.forecast(rawSGVs, sbx); } + function useRaw (sbx) { + return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true') + } + ar2.forecast = function forecast (sgvs, sbx) { var result = { From 7a02ce6120387d04d67c77759e6cd48ad675b73a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Jul 2015 23:12:59 -0700 Subject: [PATCH 410/661] hopefully fix some mmol bugs with /pebble --- lib/pebble.js | 17 +++++++++++++++-- tests/pebble.test.js | 18 +++++++++--------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index c1e50f19130..6fce7df05a0 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var sandbox = require('./sandbox')(); +var units = require('./units')(); var iob = require('./plugins/iob')(); var delta = require('./plugins/delta')(); @@ -33,17 +34,26 @@ function reverseAndSlice (entries, req) { return reversed.slice(0, req.count); } + function prepareSGVs (req, sbx) { var bgs = []; var data = sbx.data; + function scaleMgdlAPebbleLegacyHackThatWillNotGoAway (bg) { + if (req.mmol) { + return units.mgdlToMMOL(bg); + } else { + return bg.toString(); + } + } + //for compatibility we're keeping battery and iob here, but they would be better somewhere else if (data.sgvs.length > 0) { var cal = sbx.lastEntry(sbx.data.cals); bgs = _.map(reverseAndSlice(data.sgvs, req), function transformSGV (sgv) { var transformed = { - sgv: sbx.scaleEntry(sgv).toString() + sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl) , trend: directionToTrend(sgv.direction) , direction: sgv.direction , datetime: sgv.mills @@ -63,7 +73,10 @@ function prepareSGVs (req, sbx) { //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = String(deltaResult && deltaResult.scaled || 0); + bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + if (req.mmol) { + bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); + } bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); if (req.iob) { diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 82abfa46be0..4cb0ca59a49 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -47,7 +47,7 @@ ctx.data.sgvs = updateMills([ noise: 1 } , { device: 'dexcom', - mgdl: 84, + mgdl: 92, direction: 'Flat', type: 'sgv', filtered: 115680, @@ -56,7 +56,7 @@ ctx.data.sgvs = updateMills([ noise: 1 } , { device: 'dexcom', - mgdl: 82, + mgdl: 90, direction: 'Flat', type: 'sgv', filtered: 113984, @@ -101,8 +101,8 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(1); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -126,7 +126,7 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(1); var bg = bgs[0]; - bg.sgv.should.equal('4.6'); + bg.sgv.should.equal('5.0'); bg.bgdelta.should.equal('-0.1'); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); @@ -150,8 +150,8 @@ describe('Pebble Endpoint', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(2); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); @@ -186,8 +186,8 @@ describe('Pebble Endpoint with Raw and IOB', function ( ) { var bgs = res.body.bgs; bgs.length.should.equal(2); var bg = bgs[0]; - bg.sgv.should.equal('82'); - bg.bgdelta.should.equal('-2'); + bg.sgv.should.equal('90'); + bg.bgdelta.should.equal(-2); bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(now); From 657f7d241d8d37354a5f5ea3f479319041fe45a0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 09:23:09 -0700 Subject: [PATCH 411/661] ; --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index c4843af8205..acbf82298e0 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -99,7 +99,7 @@ function init() { } function useRaw (sbx) { - return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true') + return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); } ar2.forecast = function forecast (sgvs, sbx) { From 2550ea5d477a5b3d7c149006ed4cd3a6706b1c03 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 15:31:07 -0700 Subject: [PATCH 412/661] another check to see if we should try using raw for ar2 --- lib/plugins/ar2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/ar2.js b/lib/plugins/ar2.js index acbf82298e0..53b33a62b03 100644 --- a/lib/plugins/ar2.js +++ b/lib/plugins/ar2.js @@ -99,7 +99,7 @@ function init() { } function useRaw (sbx) { - return sbx.properties.rawbg && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); + return sbx.properties.rawbg && sbx.extendedSettings.useRaw !== undefined && (sbx.extendedSettings.useRaw === true || sbx.extendedSettings.useRaw.toLowerCase() === 'true'); } ar2.forecast = function forecast (sgvs, sbx) { From 91d2c0c77815c1fa0490b1b036465b06bd36b6ad Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 22:59:19 -0700 Subject: [PATCH 413/661] use moment.js for all date parsing+manipulation; add test; wrap in clousure; adjust size of inputs --- bundle/bundle.source.js | 1 + lib/utils.js | 33 +------ static/css/drawer.css | 8 ++ static/index.html | 4 +- static/js/treatment.js | 214 ++++++++++++++++++++-------------------- tests/utils.test.js | 9 ++ 6 files changed, 132 insertions(+), 137 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index ae04f5f72bf..05f8c2292e7 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,6 +1,7 @@ (function () { window._ = require('lodash'); + window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; window.Nightscout = { diff --git a/lib/utils.js b/lib/utils.js index c9b061cf5d3..f584e371e05 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,7 @@ 'use strict'; +var moment = require('moment-timezone'); + function init() { function utils() { @@ -60,35 +62,8 @@ function init() { }; // some helpers for input "date" - utils.mergeInputTime = function mergeInputTime(timestring,datestring) { - var eventTimeParts = timestring.split(':'); - var eventTime = new Date(datestring); - eventTime.setHours(eventTimeParts[0]); - eventTime.setMinutes(eventTimeParts[1]); - eventTime.setSeconds(0); - eventTime.setMilliseconds(0); - return eventTime; - }; - - utils.toDateInputValue = function toDateInputValue(datetime) { - var local = new Date(datetime); - local.setMinutes(datetime.getMinutes() - datetime.getTimezoneOffset()); - return local.toJSON().slice(0,10); - }; - - utils.addMinutes = function addMinutes(datetime,h) { - datetime.setTime(datetime.getTime() + (h*60*1000)); - return datetime; - }; - - utils.addHours = function addHours(datetime,h) { - datetime.setTime(datetime.getTime() + (h*60*60*1000)); - return datetime; - }; - - utils.addDays = function addDays(datetime,h) { - datetime.setTime(datetime.getTime() + (h*24*60*60*1000)); - return datetime; + utils.mergeInputTime = function mergeInputTime(timestring, datestring) { + return moment(datestring + ' ' + timestring, 'YYYY-MM-D HH:mm'); }; return utils(); diff --git a/static/css/drawer.css b/static/css/drawer.css index 021e5ab3ee5..6598bf558f8 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -100,6 +100,14 @@ input[type=number]:invalid { display: inline-block; } +#treatmentDrawer .eventdate { + width: 120px; +} + +#treatmentDrawer .eventtime { + width: 110px; +} + #treatmentDrawer #glucoseValue { width: 230px; } diff --git a/static/index.html b/static/index.html index ccb8c3b5f52..1bd5516e2e4 100644 --- a/static/index.html +++ b/static/index.html @@ -234,8 +234,8 @@

    Nightscout

    Other - - + +
    diff --git a/static/js/treatment.js b/static/js/treatment.js index a9a9e29ebc5..2df54c9ab54 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -1,118 +1,120 @@ 'use strict'; -function initTreatmentDrawer() { - $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); - $('#meter').prop('checked', true); - $('#carbsGiven').val(''); - $('#insulinGiven').val(''); - $('#preBolus').val(0); - $('#notes').val(''); - $('#enteredBy').val(browserStorage.get('enteredBy') || ''); - $('#nowtime').prop('checked', true); - $('#eventTimeValue').val(new Date().toTimeString().slice(0,5)); - $('#eventDateValue').val(Nightscout.utils.toDateInputValue(new Date())); -} - -function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); +(function () { + function initTreatmentDrawer() { + $('#eventType').val('BG Check'); + $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#meter').prop('checked', true); + $('#carbsGiven').val(''); + $('#insulinGiven').val(''); + $('#preBolus').val(0); + $('#notes').val(''); + $('#enteredBy').val(browserStorage.get('enteredBy') || ''); + $('#nowtime').prop('checked', true); + $('#eventTimeValue').val(moment().format('HH:mm')); + $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); + function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); + } + + if (isNaN(data.carbs)) { + errors.push('Carbs must be a number'); + } + + if (isNaN(data.insulin)) { + errors.push('Insulin must be a number'); + } + + if (errors.length > 0) { + window.alert(errors.join('\n')); + } else { + if ($('#othertime').is(':checked')) { + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + } + confirmPost(data); + } + + if (event) { + event.preventDefault(); + } } - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); + function confirmPost(data) { + var confirmtext = + 'Please verify that the data entered is correct: ' + + '\nEvent type: ' + data.eventType; + confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; + confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : ''; + + if (window.confirm(confirmtext)) { + var dataJson = JSON.stringify(data, null, ' '); + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/v1/treatments/', true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(dataJson); + + browserStorage.set('enteredBy', data.enteredBy); + + closeDrawer('#treatmentDrawer'); + } } - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - } - confirmPost(data); - } - - if (event) { + $('#treatmentDrawerToggle').click(function (event) { + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); event.preventDefault(); - } -} - -function confirmPost(data) { - var confirmtext = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType; - confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; - confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.toLocaleString(): ''; - - if (window.confirm(confirmtext)) { - var dataJson = JSON.stringify(data, null, ' '); - var xhr = new XMLHttpRequest(); - xhr.open('POST', '/api/v1/treatments/', true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - xhr.send(dataJson); - - browserStorage.set('enteredBy', data.enteredBy); - - closeDrawer('#treatmentDrawer'); - } -} + }); -$('#treatmentDrawerToggle').click(function(event) { - toggleDrawer('#treatmentDrawer', initTreatmentDrawer); - event.preventDefault(); -}); + $('#treatmentDrawer').find('button').click(treatmentSubmit); -$('#treatmentDrawer').find('button').click(treatmentSubmit); + $('#eventTime input:radio').change(function (event) { + if ($('#othertime').is(':checked')) { + $('#eventTimeValue').focus(); + } + event.preventDefault(); + }); -$('#eventTime input:radio').change(function (event){ - if ($('#othertime').is(':checked')) { - $('#eventTimeValue').focus(); - } - event.preventDefault(); -}); - -$('.eventtimeinput').focus(function (event) { - $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - $(this).attr('oldminutes',time.getMinutes()); - $(this).attr('oldhours',time.getHours()); - event.preventDefault(); -}); - -$('.eventtimeinput').change(function (event) { - $('#othertime').prop('checked', true); - var time = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(),$('#eventDateValue').val()); - if ($(this).attr('oldminutes')==='59' && time.getMinutes()===0) { - Nightscout.utils.addHours(time,1); - } - if ($(this).attr('oldminutes')==='0' && time.getMinutes()===59) { - Nightscout.utils.addHours(time,-1); - } - $('#eventTimeValue').val(time.toTimeString().slice(0,5)); - $('#eventDateValue').val(Nightscout.utils.toDateInputValue(time)); - $(this).attr('oldminutes',time.getMinutes()); - $(this).attr('oldhours',time.getHours()); - event.preventDefault(); -}); + $('.eventinput').focus(function (event) { + $('#othertime').prop('checked', true); + var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + $(this).attr('oldminutes', moment.minutes()); + $(this).attr('oldhours', moment.hours()); + event.preventDefault(); + }); + + $('.eventinput').change(function (event) { + $('#othertime').prop('checked', true); + var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + if ($(this).attr('oldminutes') === ' 59' && moment.minutes() === 0) { + moment.add(1, 'hours'); + } + if ($(this).attr('oldminutes') === '0' && moment.minutes() === 59) { + moment.add(-1, 'hours'); + } + $('#eventTimeValue').val(moment.format('HH:mm')); + $('#eventDateValue').val(moment.format('YYYY-MM-D')); + $(this).attr('oldminutes', moment.minutes()); + $(this).attr('oldhours', moment.hours()); + event.preventDefault(); + }); +})(); \ No newline at end of file diff --git a/tests/utils.test.js b/tests/utils.test.js index 3dab434df78..7d21f3abf31 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -21,4 +21,13 @@ describe('utils', function ( ) { result.status.should.equal('current'); }); + it('merge date and time', function () { + var result = utils.mergeInputTime('22:35', '2015-07-14'); + result.hours().should.equal(22); + result.minutes().should.equal(35); + result.year().should.equal(2015); + result.format('MMM').should.equal('Jul'); + result.date().should.equal(14); + }); + }); From adee6c95b91a769ff8e50690fe7b8d446895c2d8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:13:13 -0700 Subject: [PATCH 414/661] refactoring to lower complexity; also a little jquery optimization --- static/js/treatment.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 2df54c9ab54..1ac25010f72 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -15,19 +15,7 @@ $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - function treatmentSubmit(event) { - - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; - + function checkForErrors(data) { var errors = []; if (isNaN(data.glucose)) { errors.push('Blood glucose must be a number'); @@ -40,6 +28,23 @@ if (isNaN(data.insulin)) { errors.push('Insulin must be a number'); } + return errors; + } + + function treatmentSubmit(event) { + + var data = {}; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); + data.glucoseType = $('#treatment-form').find('input[name=glucoseType]:checked').val(); + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = parseInt($('#preBolus').val()); + data.notes = $('#notes').val(); + data.units = browserSettings.units; + + var errors = checkForErrors(data); if (errors.length > 0) { window.alert(errors.join('\n')); From 5b59afb775fad9debd283a5026984c67925b2963 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:22:56 -0700 Subject: [PATCH 415/661] one more shot to try making codacy happy --- static/js/treatment.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 1ac25010f72..124840e36ee 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -31,27 +31,34 @@ return errors; } - function treatmentSubmit(event) { + function prepareData() { + var data = { + enteredBy: $('#enteredBy').val() + , eventType: $('#eventType').val() + , glucose: $('#glucoseValue').val() + , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val() + , carbs: $('#carbsGiven').val() + , insulin: $('#insulinGiven').val() + , preBolus: parseInt($('#preBolus').val()) + , notes: $('#notes').val() + , units: browserSettings.units + }; + + if ($('#othertime').is(':checked')) { + data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); + } - var data = {}; - data.enteredBy = $('#enteredBy').val(); - data.eventType = $('#eventType').val(); - data.glucose = $('#glucoseValue').val(); - data.glucoseType = $('#treatment-form').find('input[name=glucoseType]:checked').val(); - data.carbs = $('#carbsGiven').val(); - data.insulin = $('#insulinGiven').val(); - data.preBolus = parseInt($('#preBolus').val()); - data.notes = $('#notes').val(); - data.units = browserSettings.units; + return data; + } + + function treatmentSubmit(event) { + var data = prepareData(); var errors = checkForErrors(data); if (errors.length > 0) { window.alert(errors.join('\n')); } else { - if ($('#othertime').is(':checked')) { - data.eventTime = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); - } confirmPost(data); } From 5e9951971905075b39683aac1e1e742bc7b2f417 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 15 Jul 2015 23:48:01 -0700 Subject: [PATCH 416/661] always a little more refactoring to do --- static/js/treatment.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 124840e36ee..65acf7680ae 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -67,19 +67,22 @@ } } - function confirmPost(data) { - var confirmtext = + function buildConfirmText(data) { + var text = 'Please verify that the data entered is correct: ' + '\nEvent type: ' + data.eventType; - confirmtext += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - confirmtext += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - confirmtext += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - confirmtext += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - confirmtext += data.notes ? '\nNotes: ' + data.notes : ''; - confirmtext += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - confirmtext += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : ''; - - if (window.confirm(confirmtext)) { + text += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; + text += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; + text += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; + text += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; + text += data.notes ? '\nNotes: ' + data.notes : ''; + text += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; + text += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : moment().format('LLL'); + return text; + } + + function confirmPost(data) { + if (window.confirm(buildConfirmText(data))) { var dataJson = JSON.stringify(data, null, ' '); var xhr = new XMLHttpRequest(); xhr.open('POST', '/api/v1/treatments/', true); @@ -99,7 +102,7 @@ $('#treatmentDrawer').find('button').click(treatmentSubmit); - $('#eventTime input:radio').change(function (event) { + $('#eventTime').find('input:radio').change(function (event) { if ($('#othertime').is(':checked')) { $('#eventTimeValue').focus(); } @@ -112,9 +115,8 @@ $(this).attr('oldminutes', moment.minutes()); $(this).attr('oldhours', moment.hours()); event.preventDefault(); - }); - - $('.eventinput').change(function (event) { + }) + .change(function (event) { $('#othertime').prop('checked', true); var moment = Nightscout.utils.mergeInputTime($('#eventTimeValue').val(), $('#eventDateValue').val()); if ($(this).attr('oldminutes') === ' 59' && moment.minutes() === 0) { From 0637dc76c811031d4ee20b2979d38503169b678e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 00:15:50 -0700 Subject: [PATCH 417/661] one more try to clear the codacy warning, seems a little cleaner anyway --- static/js/treatment.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 65acf7680ae..bdc70d06b41 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -68,17 +68,24 @@ } function buildConfirmText(data) { - var text = - 'Please verify that the data entered is correct: ' + - '\nEvent type: ' + data.eventType; - text += data.glucose ? '\nBlood glucose: ' + data.glucose + '\nMethod: ' + data.glucoseType : ''; - text += data.carbs ? '\nCarbs Given: ' + data.carbs : ''; - text += data.insulin ? '\nInsulin Given: ' + data.insulin : ''; - text += data.preBolus ? '\nPre Bolus: ' + data.preBolus : ''; - text += data.notes ? '\nNotes: ' + data.notes : ''; - text += data.enteredBy ? '\nEntered By: ' + data.enteredBy : ''; - text += data.eventTime ? '\nEvent Time: ' + data.eventTime.format('LLL') : moment().format('LLL'); - return text; + var text = [ + 'Please verify that the data entered is correct: ' + , 'Event type: ' + data.eventType + ]; + + if (data.glucose) { + text.push('Blood glucose: ' + data.glucose); + text.push('Method: ' + data.glucoseType) + } + + if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } + if (data.insulin) { text.push('Insulin Given: ' + data.insulin); } + if (data.preBolus) { text.push('Insulin Given: ' + data.insulin); } + if (data.notes) { text.push('Notes: ' + data.notes); } + if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } + + text.push('Event Time: ' + (data.eventTime ? data.eventTime.format('LLL') : moment().format('LLL'))); + return text.join('\n'); } function confirmPost(data) { From 9a29a9ddfcaeb1018c1eb79f84319d2bf5e4e47d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 00:19:47 -0700 Subject: [PATCH 418/661] ;... --- static/js/treatment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index bdc70d06b41..7dd0df20b2e 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -75,7 +75,7 @@ if (data.glucose) { text.push('Blood glucose: ' + data.glucose); - text.push('Method: ' + data.glucoseType) + text.push('Method: ' + data.glucoseType); } if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } From 89b901a234d8186de3b49ecb8307316ff3a009c2 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 16 Jul 2015 15:56:50 +0300 Subject: [PATCH 419/661] Fixes old CAGE notes being displayed on newer site change events, as reported on https://github.com/nightscout/cgm-remote-monitor/issues/699 --- lib/plugins/cannulaage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/plugins/cannulaage.js b/lib/plugins/cannulaage.js index 8dbd306d67f..2c2be701364 100644 --- a/lib/plugins/cannulaage.js +++ b/lib/plugins/cannulaage.js @@ -86,6 +86,8 @@ cage.findLatestTimeChange = function findLatestTimeChange(sbx) { returnValue.age = hours; if (treatment.notes) { returnValue.message = treatment.notes; + } else { + returnValue.message = ''; } } } From 0cf101837cafb4e14c5effd149f153070de6c844 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Thu, 16 Jul 2015 16:11:45 +0300 Subject: [PATCH 420/661] Added a test for the site change event notes --- tests/cannulaage.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/cannulaage.test.js b/tests/cannulaage.test.js index 9f9a0bce64d..efa7c642444 100644 --- a/tests/cannulaage.test.js +++ b/tests/cannulaage.test.js @@ -25,12 +25,16 @@ describe('cage', function ( ) { var clientSettings = {}; var data = { - treatments: [{eventType: 'Site Change', mills: Date.now() - 24 * 60 * 60000}] + treatments: [ + {eventType: 'Site Change', notes: 'Foo', mills: Date.now() - 48 * 60 * 60000} + , {eventType: 'Site Change', notes: 'Bar', mills: Date.now() - 24 * 60 * 60000} + ] }; var pluginBase = { updatePillText: function mockedUpdatePillText (plugin, options) { options.value.should.equal('24h'); + options.info[1].value.should.equal('Bar'); done(); } }; From 7c4092f398dfb71c7bb8278273826432318a8690 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:03:24 -0700 Subject: [PATCH 421/661] add errorcodes to the special plugins list --- lib/plugins/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/index.js b/lib/plugins/index.js index b17ca89991f..ebc0ab2959b 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -87,7 +87,7 @@ function init() { }; //these plugins are either always on or have custom settings - plugins.specialPlugins = 'ar2 delta direction upbat rawbg'; + plugins.specialPlugins = 'ar2 delta direction upbat rawbg errorcodes'; plugins.shownPlugins = function(sbx) { return _.filter(enabledPlugins, function filterPlugins(plugin) { From aadc7fae6b7dc71257ce8bd671bb9eac4ffe7f19 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:04:16 -0700 Subject: [PATCH 422/661] refactor env.js, auto enable treatmentnotify if careportal, pushover, or maker is enabled --- env.js | 225 ++++++++++++++++++++++++++++---------------------- lib/data.js | 4 +- lib/pebble.js | 4 +- 3 files changed, 131 insertions(+), 102 deletions(-) diff --git a/env.js b/env.js index 5de71fb2acf..4dfa33aa1ef 100644 --- a/env.js +++ b/env.js @@ -5,18 +5,74 @@ var _ = require('lodash'); var crypto = require('crypto'); var consts = require('./lib/constants'); var fs = require('fs'); + // Module to constrain all config and environment parsing to one spot. +// See the function config ( ) { /* - * First inspect a bunch of environment variables: - * * PORT - serve http on this port - * * MONGO_CONNECTION, CUSTOMCONNSTR_mongo - mongodb://... uri - * * CUSTOMCONNSTR_mongo_collection - name of mongo collection with `sgv` documents - * * API_SECRET - if defined, this passphrase is fed to a sha1 hash digest, the hex output is used to create a single-use token for API authorization - * * NIGHTSCOUT_STATIC_FILES - the `base directory` to use for serving - * static files over http. Default value is the included `static` - * directory. + * See README.md for info about all the supported ENV VARs */ + env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); + env.PORT = readENV('PORT', 1337); + env.baseUrl = readENV('BASE_URL'); + env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); + + setSSL(); + setAPISecret(); + setVersion(); + setMongo(); + setAlarmType(); + setEnableAndExtendedSettnigs(); + setDefaults(); + setThresholds(); + + env.isEnabled = isEnabled; + env.anyEnabled = anyEnabled; + + return env; +} + +function isEnabled (feature) { + return env.enable.indexOf(feature) > -1; +} + +function anyEnabled (features) { + return _.findIndex(features, isEnabled) > -1; +} + +function setSSL() { + env.SSL_KEY = readENV('SSL_KEY'); + env.SSL_CERT = readENV('SSL_CERT'); + env.SSL_CA = readENV('SSL_CA'); + env.ssl = false; + if (env.SSL_KEY && env.SSL_CERT) { + env.ssl = { + key: fs.readFileSync(env.SSL_KEY), cert: fs.readFileSync(env.SSL_CERT) + }; + if (env.SSL_CA) { + env.ca = fs.readFileSync(env.SSL_CA); + } + } +} + +// A little ugly, but we don't want to read the secret into a var +function setAPISecret() { + var useSecret = (readENV('API_SECRET') && readENV('API_SECRET').length > 0); + //TODO: should we clear API_SECRET from process env? + env.api_secret = null; + // if a passphrase was provided, get the hex digest to mint a single token + if (useSecret) { + if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { + var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; + throw new Error(msg.join(' ')); + } + var shasum = crypto.createHash('sha1'); + shasum.update(readENV('API_SECRET')); + env.api_secret = shasum.digest('hex'); + } +} + +function setVersion() { var software = require('./package.json'); var git = require('git-rev'); @@ -24,20 +80,21 @@ function config ( ) { env.head = require('./scm-commit-id.json'); console.log('SCM COMMIT ID', env.head); } else { - git.short(function record_git_head (head) { + git.short(function record_git_head(head) { console.log('GIT HEAD', head); env.head = head || readENV('SCM_COMMIT_ID') || readENV('COMMIT_HASH', ''); }); } env.version = software.version; env.name = software.name; - env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); - env.PORT = readENV('PORT', 1337); +} + +function setMongo() { env.mongo = readENV('MONGO_CONNECTION') || readENV('MONGO') || readENV('MONGOLAB_URI'); env.mongo_collection = readENV('MONGO_COLLECTION', 'entries'); env.MQTT_MONITOR = readENV('MQTT_MONITOR', null); if (env.MQTT_MONITOR) { - var hostDbCollection = [env.mongo.split('mongodb://').pop().split('@').pop( ), env.mongo_collection].join('/'); + var hostDbCollection = [env.mongo.split('mongodb://').pop().split('@').pop(), env.mongo_collection].join('/'); var mongoHash = crypto.createHash('sha1'); mongoHash.update(hostDbCollection); //some MQTT servers only allow the client id to be 23 chars @@ -53,9 +110,55 @@ function config ( ) { env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); + // TODO: clean up a bit + // Some people prefer to use a json configuration file instead. + // This allows a provided json config to override environment variables + var DB = require('./database_configuration.json'), + DB_URL = DB.url ? DB.url : env.mongo, + DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; + env.mongo = DB_URL; + env.mongo_collection = DB_COLLECTION; +} + +function setAlarmType() { +//if any of the BG_* thresholds are set, default to `simple` and `predict` otherwise default to only `predict` + var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple predict' : 'predict'); +} + +function setEnableAndExtendedSettnigs() { env.enable = readENV('ENABLE', ''); + //TODO: maybe get rid of ALARM_TYPES and only use enable? + if (env.alarm_types.indexOf('simple') > -1) { + env.enable = 'simplealarms ' + env.enable; + } + if (env.alarm_types.indexOf('predict') > -1) { + env.enable = 'ar2 ' + env.enable; + } + + // For pushing notifications to Pushover. + //TODO: handle PUSHOVER_ as generic plugin props + env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); + env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); + if (env.pushover_api_token && env.pushover_user_key) { + env.enable += ' pushover'; + //TODO: after config changes are documented this shouldn't be auto enabled + } - env.defaults = { // currently supported keys must defined be here + if (anyEnabled(['careportal', 'pushover', 'maker'])) { + env.enable += ' treatmentnotify'; + } + + //TODO: figure out something for default plugins, how can they be disabled? + env.enable += ' delta direction upbat errorcodes'; + + env.extendedSettings = findExtendedSettings(env.enable, process.env); +} + +function setDefaults() { + + // currently supported keys must defined be here + env.defaults = { 'units': 'mg/dL' , 'timeFormat': '12' , 'nightMode': false @@ -71,11 +174,12 @@ function config ( ) { , 'alarmTimeAgoUrgent': true , 'alarmTimeAgoUrgentMins': 30 , 'language': 'en' // not used yet - } ; + }; // add units from separate variable + //TODO: figure out where this is used, should only be in 1 spot env.defaults.units = env.DISPLAY_UNITS; - + // Highest priority per line defaults env.defaults.timeFormat = readENV('TIME_FORMAT', env.defaults.timeFormat); env.defaults.nightMode = readENV('NIGHT_MODE', env.defaults.nightMode); @@ -99,43 +203,9 @@ function config ( ) { env.defaults.showPlugins += 'rawbg'; } } +} - //console.log(JSON.stringify(env.defaults)); - - env.SSL_KEY = readENV('SSL_KEY'); - env.SSL_CERT = readENV('SSL_CERT'); - env.SSL_CA = readENV('SSL_CA'); - env.ssl = false; - if (env.SSL_KEY && env.SSL_CERT) { - env.ssl = { - key: fs.readFileSync(env.SSL_KEY) - , cert: fs.readFileSync(env.SSL_CERT) - }; - if (env.SSL_CA) { - env.ca = fs.readFileSync(env.SSL_CA); - } - } - - var shasum = crypto.createHash('sha1'); - - ///////////////////////////////////////////////////////////////// - // A little ugly, but we don't want to read the secret into a var - ///////////////////////////////////////////////////////////////// - var useSecret = (readENV('API_SECRET') && readENV('API_SECRET').length > 0); - env.api_secret = null; - // if a passphrase was provided, get the hex digest to mint a single token - if (useSecret) { - if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) { - var msg = ['API_SECRET should be at least', consts.MIN_PASSPHRASE_LENGTH, 'characters']; - var err = new Error(msg.join(' ')); - // console.error(err); - throw err; - process.exit(1); - } - shasum.update(readENV('API_SECRET')); - env.api_secret = shasum.digest('hex'); - } - +function setThresholds() { env.thresholds = { bg_high: readIntENV('BG_HIGH', 260) , bg_target_top: readIntENV('BG_TARGET_TOP', 180) @@ -168,59 +238,18 @@ function config ( ) { env.thresholds.bg_high = env.thresholds.bg_target_top + 1; console.warn('BG_HIGH is now ' + env.thresholds.bg_high); } - - //if any of the BG_* thresholds are set, default to `simple` otherwise default to `predict` to preserve current behavior - var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); - - //TODO: maybe get rid of ALARM_TYPES and only use enable? - if (env.alarm_types.indexOf('simple') > -1) { - env.enable = 'simplealarms ' + env.enable; - } - if (env.alarm_types.indexOf('predict') > -1) { - env.enable = 'ar2 ' + env.enable; - } - - // For pushing notifications to Pushover. - //TODO: handle PUSHOVER_ as generic plugin props - env.pushover_api_token = readENV('PUSHOVER_API_TOKEN'); - env.pushover_user_key = readENV('PUSHOVER_USER_KEY') || readENV('PUSHOVER_GROUP_KEY'); - if (env.pushover_api_token && env.pushover_user_key) { - env.enable += ' pushover'; - //TODO: after config changes are documented this shouldn't be auto enabled - env.enable += ' treatmentnotify'; - } - - //TODO: figure out something for default plugins, how can they be disabled? - env.enable += ' delta direction upbat errorcodes'; - - env.extendedSettings = findExtendedSettings(env.enable, process.env); - - env.baseUrl = readENV('BASE_URL'); - - // TODO: clean up a bit - // Some people prefer to use a json configuration file instead. - // This allows a provided json config to override environment variables - var DB = require('./database_configuration.json'), - DB_URL = DB.url ? DB.url : env.mongo, - DB_COLLECTION = DB.collection ? DB.collection : env.mongo_collection; - env.mongo = DB_URL; - env.mongo_collection = DB_COLLECTION; - env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/'); - - return env; } function readIntENV(varName, defaultValue) { - return parseInt(readENV(varName)) || defaultValue; + return parseInt(readENV(varName)) || defaultValue; } function readENV(varName, defaultValue) { - //for some reason Azure uses this prefix, maybe there is a good reason - var value = process.env['CUSTOMCONNSTR_' + varName] - || process.env['CUSTOMCONNSTR_' + varName.toLowerCase()] - || process.env[varName] - || process.env[varName.toLowerCase()]; + //for some reason Azure uses this prefix, maybe there is a good reason + var value = process.env['CUSTOMCONNSTR_' + varName] + || process.env['CUSTOMCONNSTR_' + varName.toLowerCase()] + || process.env[varName] + || process.env[varName.toLowerCase()]; if (typeof value === 'string' && value.toLowerCase() === 'on') { value = true; } if (typeof value === 'string' && value.toLowerCase() === 'off') { value = false; } diff --git a/lib/data.js b/lib/data.js index dfff92ebf58..6951c3ad4d4 100644 --- a/lib/data.js +++ b/lib/data.js @@ -148,7 +148,7 @@ function init(env, ctx) { function mgdlByTime() { var withBGs = _.filter(data.sgvs, function(d) { - return d.mgdl > 39 || env.enable.indexOf('rawbg') > -1; + return d.mgdl > 39 || env.isEnabled('rawbg'); }); var beforeTreatment = _.findLast(withBGs, function (d) { @@ -179,7 +179,7 @@ function init(env, ctx) { function calcRaw (entry) { var raw; - if (entry && env.enable.indexOf('rawbg') > -1) { + if (entry && env.isEnabled('rawbg')) { var cal = _.last(data.cals); if (cal) { raw = rawbg.calc(entry, cal); diff --git a/lib/pebble.js b/lib/pebble.js index 6fce7df05a0..4e1075a4a6b 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -127,8 +127,8 @@ function configure (env, ctx) { function middle (req, res, next) { req.env = env; req.ctx = ctx; - req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1; - req.iob = env.enable && env.enable.indexOf('iob') > -1; + req.rawbg = env.enable && env.isEnabled('rawbg'); + req.iob = env.enable && env.isEnabled('iob'); req.mmol = (req.query.units || env.DISPLAY_UNITS) === 'mmol'; req.count = parseInt(req.query.count) || 1; From fe02f99d680293aec92c818d1ae2964c7dffb452 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 16 Jul 2015 23:34:15 -0700 Subject: [PATCH 423/661] some env test to cover areas not covered already --- env.js | 4 +-- tests/env.test.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/env.test.js diff --git a/env.js b/env.js index 4dfa33aa1ef..23e04210ad2 100644 --- a/env.js +++ b/env.js @@ -120,10 +120,10 @@ function setMongo() { env.mongo_collection = DB_COLLECTION; } -function setAlarmType() { //if any of the BG_* thresholds are set, default to `simple` and `predict` otherwise default to only `predict` +function setAlarmType() { var thresholdsSet = readIntENV('BG_HIGH') || readIntENV('BG_TARGET_TOP') || readIntENV('BG_TARGET_BOTTOM') || readIntENV('BG_LOW'); - env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple predict' : 'predict'); + env.alarm_types = readENV('ALARM_TYPES') || (thresholdsSet ? 'simple' : 'predict'); } function setEnableAndExtendedSettnigs() { diff --git a/tests/env.test.js b/tests/env.test.js new file mode 100644 index 00000000000..687ecbf0da1 --- /dev/null +++ b/tests/env.test.js @@ -0,0 +1,80 @@ +'use strict'; + +require('should'); + +describe('env', function ( ) { + + it('set thresholds', function () { + process.env.BG_HIGH = 200; + process.env.BG_TARGET_TOP = 170; + process.env.BG_TARGET_BOTTOM = 70; + process.env.BG_LOW = 60; + + var env = require('../env')(); + + env.thresholds.bg_high.should.equal(200); + env.thresholds.bg_target_top.should.equal(170); + env.thresholds.bg_target_bottom.should.equal(70); + env.thresholds.bg_low.should.equal(60); + + env.alarm_types.should.equal('simple'); + + + delete process.env.BG_HIGH; + delete process.env.BG_TARGET_TOP; + delete process.env.BG_TARGET_BOTTOM; + delete process.env.BG_LOW; + }); + + it('handle screwed up thresholds in a way that will display something that looks wrong', function () { + process.env.BG_HIGH = 89; + process.env.BG_TARGET_TOP = 90; + process.env.BG_TARGET_BOTTOM = 95; + process.env.BG_LOW = 96; + + var env = require('../env')(); + + env.thresholds.bg_high.should.equal(91); + env.thresholds.bg_target_top.should.equal(90); + env.thresholds.bg_target_bottom.should.equal(89); + env.thresholds.bg_low.should.equal(88); + + env.alarm_types.should.equal('simple'); + + + delete process.env.BG_HIGH; + delete process.env.BG_TARGET_TOP; + delete process.env.BG_TARGET_BOTTOM; + delete process.env.BG_LOW; + }); + + it('show the right plugins', function () { + process.env.SHOW_PLUGINS = 'iob'; + process.env.ENABLE = 'iob cob'; + + var env = require('../env')(); + env.defaults.showPlugins.should.containEql('iob'); + env.defaults.showPlugins.should.containEql('delta'); + env.defaults.showPlugins.should.containEql('direction'); + env.defaults.showPlugins.should.containEql('upbat'); + env.defaults.showPlugins.should.not.containEql('cob'); + + delete process.env.SHOW_PLUGINS; + delete process.env.ENABLE; + }); + + it('get extended settings', function () { + process.env.ENABLE = 'scaryplugin'; + process.env.SCARYPLUGIN_DO_THING = 'yes'; + + var env = require('../env')(); + env.isEnabled('scaryplugin').should.equal(true); + + //Note the camelCase + env.extendedSettings.scaryplugin.doThing.should.equal('yes'); + + delete process.env.ENABLE; + delete process.env.SCARYPLUGIN_DO_THING; + }); + +}); From 243d3f86e18da5be9e46c1630536d4791698617e Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:28:02 -0700 Subject: [PATCH 424/661] alias subscription to /downloads/$username/protobuf /downloads/$username/protobuf is alias for /downloads/protobuf --- lib/mqtt.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/mqtt.js b/lib/mqtt.js index 1ed2d32fa36..5066770d5c4 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -5,12 +5,17 @@ var Long = require('long'); var decoders = require('sgvdata/lib/protobuf'); var direction = require('sgvdata/lib/utils').direction; var moment = require('moment'); +var url = require('url'); function init (env, ctx) { function mqtt ( ) { return mqtt; } + var info = url.parse(env.MQTT_MONITOR); + var username = info.auth.split(':').slice(0, -1).join(''); + var shared_topic = '/downloads/' + username + '/#'; + env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); var downloads = downloader(); @@ -28,6 +33,11 @@ function init (env, ctx) { function listenForMessages ( ) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); + // XXX: ugly hack + if (topic == shared_topic) { + topic = '/downloads/protobuf'; + } + console.log(topic, 'on message', 'msg', msg.length); switch (topic) { case '/uploader': @@ -59,6 +69,7 @@ function init (env, ctx) { function connect (env) { var uri = env.MQTT_MONITOR; + var shared_topic = env.mqtt_shared_topic; if (!uri) { return null; } @@ -75,6 +86,7 @@ function connect (env) { client.subscribe('sgvs'); client.subscribe('published'); client.subscribe('/downloads/protobuf', {qos: 2}, granted); + client.subscribe(shared_topic, {qos: 2}, granted); client.subscribe('/uploader', granted); client.subscribe('/entries/sgv', granted); From f897e0dc0f660f9298cd90b32db6743c213348e7 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:30:55 -0700 Subject: [PATCH 425/661] common throw-away vms/docker containers needs root Allow root to run bower.... --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a974b93ab98..9bf59f340fb 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "scripts": { "start": "node server.js", "test": "make test", - "postinstall": "node node_modules/bower/bin/bower install" + "postinstall": "node node_modules/bower/bin/bower --allow-root install" }, "config": { "blanket": { From 7c44512fed4b7c51eabc0201df2bc91047257977 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 17 Jul 2015 15:33:43 -0700 Subject: [PATCH 426/661] fix logic error --- lib/mqtt.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 5066770d5c4..11e81c9679d 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -15,6 +15,7 @@ function init (env, ctx) { var info = url.parse(env.MQTT_MONITOR); var username = info.auth.split(':').slice(0, -1).join(''); var shared_topic = '/downloads/' + username + '/#'; + var alias_topic = '/downloads/' + username + '/protobuf'; env.mqtt_shared_topic = shared_topic; mqtt.client = connect(env); @@ -34,7 +35,7 @@ function init (env, ctx) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); // XXX: ugly hack - if (topic == shared_topic) { + if (topic == alias_topic) { topic = '/downloads/protobuf'; } From e696d85160f229e6a7b453803d07cbf5ef00f92f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:05:48 -0700 Subject: [PATCH 427/661] try to prevent the stale-switch bug; also prevent stale data alarms on mobile browsers when suspended tab is opened --- static/js/client.js | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fabdb346b33..e4dc23eac07 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -67,6 +67,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , contextHeight , dateFn = function (d) { return new Date(d.mills) } , documentHidden = false + , visibilityChanging = false , brush , brushTimer , brushInProgress = false @@ -233,7 +234,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be // required to happen when 'now' event is sent from websocket.js every minute. When fixed, // remove this code and all references to `type: 'server-forecast'` - var last = _.last(data); + var last = _.findLast(data, {type: 'sgv'}); var lastTime = last && last.mills; if (!lastTime) { console.error('Bad Data, last point has no mills', last); @@ -629,7 +630,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init) { + var updateChart = _.debounce(function debouncedUpdateChart(init, callback) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -976,6 +977,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.select('.x') .call(xAxis2); + if (callback) { + callback(); + } + }, DEBOUNCE_MS); function sgvToColor(sgv) { @@ -1237,6 +1242,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); + if (visibilityChanging) { + console.info('visibility is changing now, wait till next tick to check time ago'); + return; + } + lastEntry.removeClass('current warn urgent'); lastEntry.addClass(ago.status); @@ -1330,12 +1340,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - //updateChart is _.debounce'd - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); + function refreshChart(updateToNow, callback) { + //updateChart is _.debounce'd + updateChart(false, function ( ) { + if (updateToNow) { + updateBrushToNow(); + } + if (callback) { + callback(); + } + }); } function visibilityChanged() { @@ -1344,7 +1358,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); + visibilityChanging = true; + refreshChart(true, function ( ) { + visibilityChanging = false; + }); } } From dec5a0f9e65af4bb351f9d9fce1c5e3850b36b24 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:21:24 -0700 Subject: [PATCH 428/661] fix the mqtt test so the mqtt uri is defined --- tests/mqtt.test.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/mqtt.test.js b/tests/mqtt.test.js index 4afe5f5f8c3..44ffc8a4281 100644 --- a/tests/mqtt.test.js +++ b/tests/mqtt.test.js @@ -6,7 +6,19 @@ var FIVE_MINS = 5 * 60 * 1000; describe('mqtt', function ( ) { - var mqtt = require('../lib/mqtt')({}, {}); + var self = this; + + before(function () { + process.env.MQTT_MONITOR = 'mqtt://user:password@localhost:12345'; + process.env.MONGO='mongodb://localhost/test_db'; + process.env.MONGO_COLLECTION='test_sgvs'; + self.env = require('../env')(); + self.mqtt = require('../lib/mqtt')(self.env, {}); + }); + + after(function () { + delete process.env.MQTT_MONITOR; + }); var now = Date.now() , prev1 = now - FIVE_MINS @@ -14,11 +26,7 @@ describe('mqtt', function ( ) { ; it('setup env correctly', function (done) { - process.env.MONGO='mongodb://localhost/test_db'; - process.env.MONGO_COLLECTION='test_sgvs'; - process.env.MQTT_MONITOR = 'mqtt://user:password@m10.cloudmqtt.com:12345'; - var env = require('../env')(); - env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); + self.env.mqtt_client_id.should.equal('fSjoHx8buyCtAc474tg8Dt3'); done(); }); @@ -31,7 +39,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -53,7 +61,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -77,7 +85,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sgv.length); @@ -103,7 +111,7 @@ describe('mqtt', function ( ) { ] }; - var merged = mqtt.sgvSensorMerge(packet); + var merged = self.mqtt.sgvSensorMerge(packet); merged.length.should.equal(packet.sensor.length); From 29fdaf7254fb13602909664dbb5266e1a35248a6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:22:27 -0700 Subject: [PATCH 429/661] add an = --- lib/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 11e81c9679d..7876cffc912 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -35,7 +35,7 @@ function init (env, ctx) { mqtt.client.on('message', function (topic, msg) { console.log('topic', topic); // XXX: ugly hack - if (topic == alias_topic) { + if (topic === alias_topic) { topic = '/downloads/protobuf'; } From 052165242daea9886a25d6ca942b886763471dda Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:21:41 -0700 Subject: [PATCH 430/661] smoother page load and tab switching --- static/js/client.js | 69 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index e4dc23eac07..073e2dcaa9e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -8,6 +8,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds + , TWO_SEC_IN_MS = 2000 + , FIVE_SEC_IN_MS = 5000 , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 @@ -67,7 +69,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , contextHeight , dateFn = function (d) { return new Date(d.mills) } , documentHidden = false - , visibilityChanging = false + , visibilityChangedAt = Date.now() , brush , brushTimer , brushInProgress = false @@ -567,7 +569,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.select('.now-line') .transition() - .duration(UPDATE_TRANS_MS) + .duration(UPDATE_TRANS_MS * 1.3) .attr('x1', xScale(nowDate)) .attr('y1', yScale(scaleBg(36))) .attr('x2', xScale(nowDate)) @@ -630,7 +632,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init, callback) { + var updateChart = _.debounce(function debouncedUpdateChart(init) { if (documentHidden && !init) { console.info('Document Hidden, not updating - ' + (new Date())); @@ -976,11 +978,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // update x axis domain context.select('.x') .call(xAxis2); - - if (callback) { - callback(); - } - }, DEBOUNCE_MS); function sgvToColor(sgv) { @@ -1236,28 +1233,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - function updateTimeAgo() { + function updateTimeAgo(forceUpdate) { var lastEntry = $('#lastEntry') , time = latestSGV ? latestSGV.mills : -1 , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); - if (visibilityChanging) { + if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); - return; - } - - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); + } else { + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); - if (ago.status !== 'current') { - updateTitle(); - } + if (ago.status !== 'current') { + updateTitle(); + } - if ( - (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); + if ((browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { + checkTimeAgoAlarm(ago); + } } container.toggleClass('alarming-timeago', ago.status !== 'current'); @@ -1340,16 +1335,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); - function refreshChart(updateToNow, callback) { - //updateChart is _.debounce'd - updateChart(false, function ( ) { - if (updateToNow) { - updateBrushToNow(); - } - if (callback) { - callback(); - } - }); + function updateTimeAgoSoon() { + setTimeout(function updatingTimeAgoNow() { + var forceUpdate = true; + updateTimeAgo(forceUpdate); + }, TWO_SEC_IN_MS); + } + + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); + updateTimeAgoSoon(); } function visibilityChanged() { @@ -1358,10 +1356,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (prevHidden && !documentHidden) { console.info('Document now visible, updating - ' + (new Date())); - visibilityChanging = true; - refreshChart(true, function ( ) { - visibilityChanging = false; - }); + visibilityChangedAt = Date.now(); + refreshChart(true); } } @@ -1371,6 +1367,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClock(); + updateTimeAgoSoon(); var silenceDropdown = new Dropdown('.dropdown-menu'); From 6b4fcd7a3ba16dc52a3f99a2b11b06bb4604361f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:33:36 -0700 Subject: [PATCH 431/661] some refactoring --- static/js/client.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 073e2dcaa9e..432f61024e2 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1220,7 +1220,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var level = ago.status , alarm = getClientAlarm(level + 'TimeAgo'); - if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + var isStale = browserSettings.alarmTimeAgoWarn && ago.status === 'warn' + || browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent'; + + if (isStale && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { currentAlarmType = alarm.type; console.info('generating timeAgoAlarm', alarm.type); container.addClass('alarming-timeago'); @@ -1248,11 +1251,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (ago.status !== 'current') { updateTitle(); } - - if ((browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); - } + checkTimeAgoAlarm(ago); } container.toggleClass('alarming-timeago', ago.status !== 'current'); From 8c548632f8fa0862987fe5330ec24e8b28a22eac Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 00:43:25 -0700 Subject: [PATCH 432/661] more refactoring, maybe codacy will like it --- static/js/client.js | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 432f61024e2..31ee48abdaa 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1228,11 +1228,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; console.info('generating timeAgoAlarm', alarm.type); container.addClass('alarming-timeago'); var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; - if (level === 'warn') { - generateAlarm(alarmSound, message); - } else { - generateAlarm(urgentAlarmSound, message); - } + var sound = level === 'warn' ? alarmSound : urgentAlarmSound; + generateAlarm(sound, message); + } + + container.toggleClass('alarming-timeago', ago.status !== 'current'); + + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { + stopAlarm(true, ONE_MIN_IN_MS); } } @@ -1242,6 +1245,20 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , ago = timeAgo(time, browserSettings) , retroMode = inRetroMode(); + //TODO: move this ao a plugin? + function updateTimeAgoPill() { + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); + } + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); } else { @@ -1254,23 +1271,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; checkTimeAgoAlarm(ago); } - container.toggleClass('alarming-timeago', ago.status !== 'current'); - - if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { - stopAlarm(true, ONE_MIN_IN_MS); - } - - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } - - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } + updateTimeAgoPill(); } function init() { From 57204ca4da4595d26136ac7d4862a5f920c63e62 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 02:25:36 -0700 Subject: [PATCH 433/661] some improvements for generateTitle/updateTitle --- static/js/client.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 31ee48abdaa..fcf5c412d35 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -125,10 +125,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) , ago = timeAgo(time, browserSettings); - if (browserSettings.customTitle) { - $('.customTitle').text(browserSettings.customTitle); - } - if (ago && ago.status !== 'current') { bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; } else if (latestSGV) { @@ -144,20 +140,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return bg_title; } - function updateTitle(skipPageTitle) { + function updateTitle() { - var bg_title = browserSettings.customTitle || ''; + var bg_title; if (alarmMessage && alarmInProgress) { - bg_title = alarmMessage + ': ' + generateTitle(); $('.customTitle').text(alarmMessage); + if (!isTimeAgoAlarmType(currentAlarmType)) { + bg_title = alarmMessage + ': ' + generateTitle(); + } + } else if (browserSettings.customTitle) { + $('.customTitle').text(browserSettings.customTitle); } else { - bg_title = generateTitle(); + $('.customTitle').text('Nightscout'); } - if (!skipPageTitle) { - $(document).attr('title', bg_title); + if (bg_title === undefined) { + bg_title = generateTitle(); } + + $(document).attr('title', bg_title); } // initial setup of chart when data is first made available @@ -1036,8 +1038,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); - var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); - updateTitle(skipPageTitle); + updateTitle(); } function playAlarm(audio) { From f19774df169e7b1f605143db11da3aaf7ea5e1f3 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 18 Jul 2015 14:39:11 -0700 Subject: [PATCH 434/661] try to limit mongo queries in null finds --- lib/entries.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/entries.js b/lib/entries.js index e54b3f48d89..49afc353c20 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -21,6 +21,7 @@ function find_sgv_query (opts) { return opts; } +var TWO_DAYS = 172800000; function storage(env, ctx) { // TODO: Code is a little redundant. @@ -37,6 +38,13 @@ function storage(env, ctx) { // determine find options function find ( ) { var finder = find_sgv_query(opts); + var query = finder && finder.find ? finder.find : null; + if (query == null) { + query = { + date: { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } + }; + } + return query; return finder && finder.find ? finder.find : { }; // return this.find(q); } From a69eac5be438132d23dec78ea29c9cc9b04dcd12 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 18 Jul 2015 14:52:58 -0700 Subject: [PATCH 435/661] try to ensure all finds have a date query --- lib/entries.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 49afc353c20..84ec0268021 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -38,11 +38,9 @@ function storage(env, ctx) { // determine find options function find ( ) { var finder = find_sgv_query(opts); - var query = finder && finder.find ? finder.find : null; - if (query == null) { - query = { - date: { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } - }; + var query = finder && finder.find ? finder.find : { }; + if (!query.date) { + query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } } return query; return finder && finder.find ? finder.find : { }; From 25dd834c95a4e7207fc504c4b59a6ddbb6cab129 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 15:34:23 -0700 Subject: [PATCH 436/661] wait even longer after load/tab switch before showing time ago alarms --- static/js/client.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index fcf5c412d35..0ae33581bfb 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -8,8 +8,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , DEBOUNCE_MS = 10 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds - , TWO_SEC_IN_MS = 2000 - , FIVE_SEC_IN_MS = 5000 + , TEN_SEC_IN_MS = 10000 , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 , THREE_HOURS_MS = 3 * 60 * 60 * 1000 @@ -1260,7 +1259,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - if (Date.now() - visibilityChangedAt <= FIVE_SEC_IN_MS && !forceUpdate) { + if (Date.now() - visibilityChangedAt <= TEN_SEC_IN_MS && !forceUpdate) { console.info('visibility is changing now, wait till next tick to check time ago'); } else { lastEntry.removeClass('current warn urgent'); @@ -1340,7 +1339,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; setTimeout(function updatingTimeAgoNow() { var forceUpdate = true; updateTimeAgo(forceUpdate); - }, TWO_SEC_IN_MS); + }, TEN_SEC_IN_MS); } function refreshChart(updateToNow) { From 99ea9fa58e6191aa4f347a66ec5935b83eb7fb6d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 16:26:08 -0700 Subject: [PATCH 437/661] throttle updateData to prevent loading way too many times --- lib/bootevent.js | 10 ++++--- lib/bus.js | 8 +++--- tests/update-throttle.test.js | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 tests/update-throttle.test.js diff --git a/lib/bootevent.js b/lib/bootevent.js index 634710f7641..27d62c7868c 100644 --- a/lib/bootevent.js +++ b/lib/bootevent.js @@ -1,5 +1,9 @@ 'use strict'; +var _ = require('lodash'); + +var UPDATE_THROTTLE = 1000; + function boot (env) { function setupMongo (ctx, next) { @@ -45,11 +49,11 @@ function boot (env) { } function setupListeners (ctx, next) { - function updateData ( ) { + var updateData = _.debounce(function debouncedUpdateData ( ) { ctx.data.update(function dataUpdated () { ctx.bus.emit('data-loaded'); }); - } + }, UPDATE_THROTTLE); ctx.bus.on('tick', function timedReloadData (tick) { console.info('tick', tick.now); @@ -57,7 +61,7 @@ function boot (env) { }); ctx.bus.on('data-received', function forceReloadData ( ) { - console.info('got data-received event, reloading now'); + console.info('got data-received event, requesting reload'); updateData(); }); diff --git a/lib/bus.js b/lib/bus.js index 6ec88a5a2a2..10dbad2e3d1 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -3,13 +3,12 @@ var Stream = require('stream'); function init (env) { var beats = 0; var started = new Date( ); - var id; - var interval = env.HEARTBEAT || 20000; + var interval = env.HEARTBEAT || 60000; var stream = new Stream; function ictus ( ) { - var tick = { + return { now: new Date( ) , type: 'heartbeat' , sig: 'internal://' + ['heartbeat', beats ].join('/') @@ -17,7 +16,6 @@ function init (env) { , interval: interval , started: started }; - return tick; } function repeat ( ) { @@ -26,7 +24,7 @@ function init (env) { stream.readable = true; stream.uptime = repeat; - id = setInterval(repeat, interval); + setInterval(repeat, interval); return stream; } module.exports = init; diff --git a/tests/update-throttle.test.js b/tests/update-throttle.test.js new file mode 100644 index 00000000000..a7dc8de868d --- /dev/null +++ b/tests/update-throttle.test.js @@ -0,0 +1,51 @@ +'use strict'; + +var _ = require('lodash'); +var request = require('supertest'); +require('should'); + +describe('Throttle', function ( ) { + var self = this; + + var api = require('../lib/api/'); + before(function (done) { + process.env.API_SECRET = 'this is my long pass phrase'; + self.env = require('../env')(); + this.wares = require('../lib/middleware/')(self.env); + self.app = require('express')(); + self.app.enable('api'); + require('../lib/bootevent')(self.env).boot(function booted(ctx) { + self.ctx = ctx; + self.app.use('/api', api(self.env, ctx)); + done(); + }); + }); + + after(function () { + delete process.env.API_SECRET; + }); + + it('only update once when there are multiple posts', function (done) { + + //if the data-loaded event is triggered more than once the test will fail + self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { + done() + }); + + function post () { + request(self.app) + .post('/api/entries/') + .set('api-secret', self.env.api_secret || '') + .send({type: 'sgv', sgv: 100, date: Date.now()}) + .expect(200) + .end(function(err) { + if (err) { + done(err); + } + }); + } + + _.times(10, post); + }); + +}); \ No newline at end of file From 02995a0a7f91a22411137cd5e2077e1d68168a16 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 16:54:35 -0700 Subject: [PATCH 438/661] ; --- tests/update-throttle.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/update-throttle.test.js b/tests/update-throttle.test.js index a7dc8de868d..9b15c68fb6b 100644 --- a/tests/update-throttle.test.js +++ b/tests/update-throttle.test.js @@ -29,7 +29,7 @@ describe('Throttle', function ( ) { //if the data-loaded event is triggered more than once the test will fail self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { - done() + done(); }); function post () { From d00f87d1364fdc6789e5595a84e16b1e45860a6b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:34:33 -0700 Subject: [PATCH 439/661] also check for dateString --- lib/entries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index 84ec0268021..a6ed19c9955 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -39,7 +39,7 @@ function storage(env, ctx) { function find ( ) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; - if (!query.date) { + if (!query.date && !query.dateString) { query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } } return query; From 19b5004d4a91da427346c1bfebf3b702a9439525 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:35:20 -0700 Subject: [PATCH 440/661] updated entries api test to use date filters for retro data and added current entry --- tests/api.entries.test.js | 10 +++++-- tests/fixtures/example.json | 60 ++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/tests/api.entries.test.js b/tests/api.entries.test.js index 779c32823db..e47c633771a 100644 --- a/tests/api.entries.test.js +++ b/tests/api.entries.test.js @@ -17,7 +17,10 @@ describe('Entries REST api', function ( ) { require('../lib/bootevent')(env).boot(function booted (ctx) { self.app.use('/', entries(self.app, self.wares, ctx)); self.archive = require('../lib/entries')(env, ctx); - self.archive.create(load('json'), done); + + var creating = load('json'); + creating.push({type: 'sgv', sgv: 100, date: Date.now()}); + self.archive.create(creating, done); }); }); @@ -32,7 +35,7 @@ describe('Entries REST api', function ( ) { it('gets requested number of entries', function (done) { var count = 30; request(this.app) - .get('/entries.json?count=' + count) + .get('/entries.json?find[dateString][$gte]=2014-07-19&count=' + count) .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(count); @@ -43,7 +46,7 @@ describe('Entries REST api', function ( ) { it('gets default number of entries', function (done) { var defaultCount = 10; request(this.app) - .get('/entries.json') + .get('/entries/sgv.json?find[dateString][$gte]=2014-07-19&find[dateString][$lte]=2014-07-20') .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(defaultCount); @@ -57,6 +60,7 @@ describe('Entries REST api', function ( ) { .expect(200) .end(function (err, res) { res.body.should.be.instanceof(Array).and.have.lengthOf(1); + res.body[0].sgv.should.equal(100); done(); }); }); diff --git a/tests/fixtures/example.json b/tests/fixtures/example.json index 0f3b6802c41..f2c3e6de02f 100644 --- a/tests/fixtures/example.json +++ b/tests/fixtures/example.json @@ -2,7 +2,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:49:15 AM", + "dateString": "2014-07-19T10:49:15.000-07:00", "date": 1405792155000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -10,7 +10,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:44:15 AM", + "dateString": "2014-07-19T10:44:15.000-07:00", "date": 1405791855000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -18,7 +18,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:39:15 AM", + "dateString": "2014-07-19T10:39:15.000-07:00", "date": 1405791555000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -26,7 +26,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:34:15 AM", + "dateString": "2014-07-19T10:34:15.000-07:00", "date": 1405791255000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -34,7 +34,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:29:15 AM", + "dateString": "2014-07-19T10:29:15.000-07:00", "date": 1405790955000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -42,7 +42,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:24:15 AM", + "dateString": "2014-07-19T10:24:15.000-07:00", "date": 1405790655000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -50,7 +50,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:19:15 AM", + "dateString": "2014-07-19T10:19:15.000-07:00", "date": 1405790355000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -58,7 +58,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:14:15 AM", + "dateString": "2014-07-19T10:14:15.000-07:00", "date": 1405790055000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -66,7 +66,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:09:15 AM", + "dateString": "2014-07-19T10:09:15.000-07:00", "date": 1405789755000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -74,7 +74,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 10:04:15 AM", + "dateString": "2014-07-19T10:04:15.000-07:00", "date": 1405789455000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -82,7 +82,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 09:59:15 AM", + "dateString": "2014-07-19T09:59:15.000-07:00", "date": 1405789155000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -90,7 +90,7 @@ { "type": "sgv", "sgv": "5", - "dateString": "07\/19\/2014 09:54:15 AM", + "dateString": "2014-07-19T09:54:15.000-07:00", "date": 1405788855000, "device": "dexcom", "direction": "NOT COMPUTABLE" @@ -98,7 +98,7 @@ { "type": "sgv", "sgv": "178", - "dateString": "07\/19\/2014 03:59:15 AM", + "dateString": "2014-07-19T03:59:15.000-07:00", "date": 1405767555000, "device": "dexcom", "direction": "Flat" @@ -106,7 +106,7 @@ { "type": "sgv", "sgv": "179", - "dateString": "07\/19\/2014 03:54:15 AM", + "dateString": "2014-07-19T03:54:15.000-07:00", "date": 1405767255000, "device": "dexcom", "direction": "Flat" @@ -114,7 +114,7 @@ { "type": "sgv", "sgv": "178", - "dateString": "07\/19\/2014 03:49:15 AM", + "dateString": "2014-07-19T03:49:15.000-07:00", "date": 1405766955000, "device": "dexcom", "direction": "Flat" @@ -122,7 +122,7 @@ { "type": "sgv", "sgv": "177", - "dateString": "07\/19\/2014 03:44:15 AM", + "dateString": "2014-07-19T03:44:15.000-07:00", "date": 1405766655000, "device": "dexcom", "direction": "Flat" @@ -130,7 +130,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:39:15 AM", + "dateString": "2014-07-19T03:39:15.000-07:00", "date": 1405766355000, "device": "dexcom", "direction": "Flat" @@ -138,7 +138,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:34:15 AM", + "dateString": "2014-07-19T03:34:15.000-07:00", "date": 1405766055000, "device": "dexcom", "direction": "Flat" @@ -146,7 +146,7 @@ { "type": "sgv", "sgv": "175", - "dateString": "07\/19\/2014 03:29:16 AM", + "dateString": "2014-07-19T03:29:16.000-07:00", "date": 1405765756000, "device": "dexcom", "direction": "Flat" @@ -154,7 +154,7 @@ { "type": "sgv", "sgv": "174", - "dateString": "07\/19\/2014 03:24:15 AM", + "dateString": "2014-07-19T03:24:15.000-07:00", "date": 1405765455000, "device": "dexcom", "direction": "Flat" @@ -162,7 +162,7 @@ { "type": "sgv", "sgv": "174", - "dateString": "07\/19\/2014 03:19:15 AM", + "dateString": "2014-07-19T03:19:15.000-07:00", "date": 1405765155000, "device": "dexcom", "direction": "Flat" @@ -170,7 +170,7 @@ { "type": "sgv", "sgv": "175", - "dateString": "07\/19\/2014 03:14:15 AM", + "dateString": "2014-07-19T03:14:15.000-07:00", "date": 1405764855000, "device": "dexcom", "direction": "Flat" @@ -178,7 +178,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:09:15 AM", + "dateString": "2014-07-19T03:09:15.000-07:00", "date": 1405764555000, "device": "dexcom", "direction": "Flat" @@ -186,7 +186,7 @@ { "type": "sgv", "sgv": "176", - "dateString": "07\/19\/2014 03:04:15 AM", + "dateString": "2014-07-19T03:04:15.000-07:00", "date": 1405764255000, "device": "dexcom", "direction": "Flat" @@ -194,7 +194,7 @@ { "type": "sgv", "sgv": "173", - "dateString": "07\/19\/2014 02:59:15 AM", + "dateString": "2014-07-19T02:59:15.000-07:00", "date": 1405763955000, "device": "dexcom", "direction": "Flat" @@ -202,7 +202,7 @@ { "type": "sgv", "sgv": "171", - "dateString": "07\/19\/2014 02:54:15 AM", + "dateString": "2014-07-19T02:54:15.000-07:00", "date": 1405763655000, "device": "dexcom", "direction": "Flat" @@ -210,7 +210,7 @@ { "type": "sgv", "sgv": "170", - "dateString": "07\/19\/2014 02:49:15 AM", + "dateString": "2014-07-19T02:49:15.000-07:00", "date": 1405763355000, "device": "dexcom", "direction": "Flat" @@ -218,7 +218,7 @@ { "type": "sgv", "sgv": "171", - "dateString": "07\/19\/2014 02:44:15 AM", + "dateString": "2014-07-19T02:44:15.000-07:00", "date": 1405763055000, "device": "dexcom", "direction": "Flat" @@ -226,7 +226,7 @@ { "type": "sgv", "sgv": "169", - "dateString": "07\/19\/2014 02:39:15 AM", + "dateString": "2014-07-19T02:39:15.000-07:00", "date": 1405762755000, "device": "dexcom", "direction": "Flat" @@ -234,7 +234,7 @@ { "type": "sgv", "sgv": "169", - "dateString": "07\/19\/2014 02:34:15 AM", + "dateString": "2014-07-19T02:34:15.000-07:00", "date": 1405762455000, "device": "dexcom", "direction": "Flat" From 224dfb22a6dba908d0f03d78a61ec3296ef9bd3b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:36:17 -0700 Subject: [PATCH 441/661] quotes --- lib/entries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entries.js b/lib/entries.js index a6ed19c9955..1fc956bce5b 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -40,7 +40,7 @@ function storage(env, ctx) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; if (!query.date && !query.dateString) { - query.date = { "$gte": Date.now( ) - ( TWO_DAYS * 2 ) } + query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) } } return query; return finder && finder.find ? finder.find : { }; From 9643ccef81b4392b8710339b85beb8b8fc3c2d0a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 17:38:14 -0700 Subject: [PATCH 442/661] more clean up --- lib/entries.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 1fc956bce5b..47df1ab103a 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -40,11 +40,9 @@ function storage(env, ctx) { var finder = find_sgv_query(opts); var query = finder && finder.find ? finder.find : { }; if (!query.date && !query.dateString) { - query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) } + query.date = { $gte: Date.now( ) - ( TWO_DAYS * 2 ) }; } return query; - return finder && finder.find ? finder.find : { }; - // return this.find(q); } // determine sort options From c96035b57e6efca8b214e2dea0c5471f861103d3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 19:31:06 -0700 Subject: [PATCH 443/661] added some basic tests for the treatment api and loading --- tests/api.treatments.test.js | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 tests/api.treatments.test.js diff --git a/tests/api.treatments.test.js b/tests/api.treatments.test.js new file mode 100644 index 00000000000..715010740eb --- /dev/null +++ b/tests/api.treatments.test.js @@ -0,0 +1,67 @@ +'use strict'; + +var _ = require('lodash'); +var request = require('supertest'); +require('should'); + +describe('Treatment API', function ( ) { + var self = this; + + var api = require('../lib/api/'); + before(function (done) { + process.env.API_SECRET = 'this is my long pass phrase'; + self.env = require('../env')(); + self.env.enable = 'careportal'; + this.wares = require('../lib/middleware/')(self.env); + self.app = require('express')(); + self.app.enable('api'); + require('../lib/bootevent')(self.env).boot(function booted(ctx) { + self.ctx = ctx; + self.app.use('/api', api(self.env, ctx)); + done(); + }); + }); + + after(function () { + delete process.env.API_SECRET; + }); + + it('post a some treatments', function (done) { + self.ctx.bus.on('data-loaded', function dataWasLoaded ( ) { + self.ctx.data.treatments.length.should.equal(3); + self.ctx.data.treatments[0].mgdl.should.equal(100); + + self.ctx.data.treatments[1].mgdl.should.equal(100); + self.ctx.data.treatments[1].insulin.should.equal('2.00'); + self.ctx.data.treatments[2].carbs.should.equal('30'); + + done(); + }); + + self.ctx.treatments().remove({ }, function ( ) { + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'BG Check', glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } + }); + + request(self.app) + .post('/api/treatments/') + .set('api-secret', self.env.api_secret || '') + .send({eventType: 'Meal Bolus', carbs: '30', insulin: '2.00', preBolus: 15, glucose: 100, glucoseType: 'Finger', units: 'mg/dl'}) + .expect(200) + .end(function (err) { + if (err) { + done(err); + } + }); + + }); + }); + +}); \ No newline at end of file From f37da2ca3ee938dc82eee629f94890db3237feda Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 19:45:21 -0700 Subject: [PATCH 444/661] removed unused _ --- tests/api.treatments.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api.treatments.test.js b/tests/api.treatments.test.js index 715010740eb..ff52e1766ff 100644 --- a/tests/api.treatments.test.js +++ b/tests/api.treatments.test.js @@ -1,6 +1,5 @@ 'use strict'; -var _ = require('lodash'); var request = require('supertest'); require('should'); From 413e7c98618405e841693c45e699d3dff38951aa Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:28:28 -0700 Subject: [PATCH 445/661] use jsdom to be able to create a test for pluginbase using jquery --- bower.json | 2 +- bundle/bundle.source.js | 1 + package.json | 4 +++- static/index.html | 1 - tests/pluginbase.test.js | 47 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/pluginbase.test.js diff --git a/bower.json b/bower.json index da22cab72e3..c4169354c7d 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", "d3": "~3.5.5", - "jquery": "2.1.0", + "jquery": "2.1.4", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", "tipsy-jmalonzo": "~1.0.1" diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 05f8c2292e7..759d9d592cd 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -1,6 +1,7 @@ (function () { window._ = require('lodash'); + window.$ = window.jQuery = require('jquery'); window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; diff --git a/package.json b/package.json index a974b93ab98..3baaaa1a59e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "express-extension-to-accept": "0.0.2", "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", + "jquery": "^2.1.4", "lodash": "^3.9.1", "long": "~2.2.3", "moment": "2.8.1", @@ -74,6 +75,7 @@ "istanbul": "~0.3.5", "mocha": "~1.20.1", "should": "~4.0.4", - "supertest": "~0.13.0" + "supertest": "~0.13.0", + "jsdom": "^3.1.2" } } diff --git a/static/index.html b/static/index.html index a7a03c4d3dd..ed6bdaf8ec2 100644 --- a/static/index.html +++ b/static/index.html @@ -253,7 +253,6 @@ - diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js new file mode 100644 index 00000000000..7cb83fc2caa --- /dev/null +++ b/tests/pluginbase.test.js @@ -0,0 +1,47 @@ +'use strict'; + +require('should'); + +describe('pluginbase', function ( ) { + + //jsdom(); + + var jsdom = require("jsdom").jsdom; + var doc = jsdom("hello world"); + var window = doc.parentWindow; + + + global.$ = global.jQuery = require('jquery')(window); + + function div (clazz) { + return $('
    '); + } + + var container = div('container') + , bgStatus = div('bgStatus').appendTo(container) + , currentBG = div('currentBG').appendTo(bgStatus) + , majorPills = div('majorPills').appendTo(bgStatus) + , minorPills = div('minorPills').appendTo(bgStatus) + , statusPills = div('statusPills').appendTo(bgStatus) + , tooltip = div('tooltip').appendTo(container) + ; + + var fake = { + name: 'fake' + , label: 'Insulin-on-Board' + , pluginType: 'pill-major' + }; + + it('does stuff', function() { + var pluginbase = require('../lib/plugins/pluginbase')(majorPills, minorPills, statusPills, bgStatus, tooltip); + + pluginbase.updatePillText(fake, { + value: '123' + , label: 'TEST' + , info: [{label: 'Label', value: 'Value'}] + }); + + majorPills.length.should.equal(1); + }); + +}); \ No newline at end of file From 89929631f3fcb2319af3872b7c2019b1e8944c5c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:36:24 -0700 Subject: [PATCH 446/661] revert bower change --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index c4169354c7d..da22cab72e3 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", "d3": "~3.5.5", - "jquery": "2.1.4", + "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", "tipsy-jmalonzo": "~1.0.1" From 003ea6817c0b3a9fc614bc246bdd298b21df2d2c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 23:56:04 -0700 Subject: [PATCH 447/661] codacy clean up --- tests/pluginbase.test.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 7cb83fc2caa..0751a53104c 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -4,10 +4,8 @@ require('should'); describe('pluginbase', function ( ) { - //jsdom(); - - var jsdom = require("jsdom").jsdom; - var doc = jsdom("hello world"); + var jsdom = require('jsdom').jsdom; + var doc = jsdom(''); var window = doc.parentWindow; @@ -19,7 +17,6 @@ describe('pluginbase', function ( ) { var container = div('container') , bgStatus = div('bgStatus').appendTo(container) - , currentBG = div('currentBG').appendTo(bgStatus) , majorPills = div('majorPills').appendTo(bgStatus) , minorPills = div('minorPills').appendTo(bgStatus) , statusPills = div('statusPills').appendTo(bgStatus) From 2672c6be2b53cf1ea444a448adf672991bdc6cb4 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 20 Jul 2015 20:36:51 +0200 Subject: [PATCH 448/661] date field too short on eurpean style of date --- static/css/drawer.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index e5383f25423..0f95b6b1fc9 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -101,7 +101,7 @@ input[type=number]:invalid { } #treatmentDrawer .eventdate { - width: 120px; + width: 130px; } #treatmentDrawer .eventtime { From 1bf4e4237cbf7e1646d4b327d12285fee574bd09 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Mon, 20 Jul 2015 22:29:02 +0200 Subject: [PATCH 449/661] selecting now resets time to current --- static/js/treatment.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/js/treatment.js b/static/js/treatment.js index 7dd0df20b2e..22f4742441f 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -112,6 +112,9 @@ $('#eventTime').find('input:radio').change(function (event) { if ($('#othertime').is(':checked')) { $('#eventTimeValue').focus(); + } else { + $('#eventTimeValue').val(moment().format('HH:mm')); + $('#eventDateValue').val(moment().format('YYYY-MM-D')); } event.preventDefault(); }); From 1a3a59b7dba45914b931886051d17e4b10db2ff5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 17:50:37 +0200 Subject: [PATCH 450/661] basic translation module --- README.md | 1 + bundle/bundle.source.js | 1 + env.js | 3 +- lib/language.js | 857 ++++++++++++++++++++++++++++++++++++++++ server.js | 2 +- static/index.html | 2 +- static/js/client.js | 1 + 7 files changed, 864 insertions(+), 3 deletions(-) create mode 100644 lib/language.js diff --git a/README.md b/README.md index 8cac0c4a525..9a873f2bea4 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm * `SHOW_PLUGINS` - enabled plugins that should have their visualizations shown, defaults to all enabled + * `LANGUAGE` (`en`) - language of Nighscout. If not available english is used ### Plugins diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 759d9d592cd..5bf8e7902bc 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,6 +9,7 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), + language: require('../lib/language')(serverSettings.defaults.language), plugins: require('../lib/plugins/')().registerClientDefaults(), sandbox: require('../lib/sandbox')() }; diff --git a/env.js b/env.js index 23e04210ad2..618777968bb 100644 --- a/env.js +++ b/env.js @@ -173,7 +173,7 @@ function setDefaults() { , 'alarmTimeAgoWarnMins': 15 , 'alarmTimeAgoUrgent': true , 'alarmTimeAgoUrgentMins': 30 - , 'language': 'en' // not used yet + , 'language': 'en' }; // add units from separate variable @@ -195,6 +195,7 @@ function setDefaults() { env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent); env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins); env.defaults.showPlugins = readENV('SHOW_PLUGINS', ''); + env.defaults.language = readENV('LANGUAGE', env.defaults.language); //TODO: figure out something for some plugins to have them shown by default if (env.defaults.showPlugins !== '') { diff --git a/lib/language.js b/lib/language.js new file mode 100644 index 00000000000..049913e9f26 --- /dev/null +++ b/lib/language.js @@ -0,0 +1,857 @@ +'use strict'; + +var _ = require('lodash'); + +function init(lang) { + + function language() { + return language; + } + + var translations = { + // Client + ,'Mo' : { + cs: 'Po' + ,de: 'Mo' + } + ,'Tu' : { + cs: 'Út' + ,de: 'Di' + }, + ',We' : { + cs: 'St' + ,de: 'Mi' + } + ,'Th' : { + cs: 'Čt' + ,de: 'Do' + } + ,'Fr' : { + cs: 'Pá' + ,de: 'Fr' + } + ,'Sa' : { + cs: 'So' + ,de: 'Sa' + } + ,'Su' : { + cs: 'Ne' + ,de: 'So' + } + ,'Monday' : { + cs: 'Pondělí' + ,de: 'Montag' + } + ,'Tuesday' : { + cs: 'Úterý' + ,de: 'Dienstag' + } + ,'Wednesday' : { + cs: 'Středa' + ,de: 'Mittwoch' + } + ,'Thursday' : { + cs: 'Čtvrtek' + ,de: 'Donnerstag' + } + ,'Friday' : { + cs: 'Pátek' + ,de: 'Freitag' + } + ,'Saturday' : { + cs: 'Sobota' + ,de: 'Samstag' + } + ,'Sunday' : { + cs: 'Neděle' + ,de: 'Sonntag' + } + ,'Category' : { + cs: 'Kategorie' + ,de: 'Kategorie' + } + ,'Subcategory' : { + cs: 'Podkategorie' + ,de: 'Unterkategorie' + } + ,'Name' : { + cs: 'Jméno' + ,de: 'Name' + } + ,'Today' : { + cs: 'Dnes' + ,de: 'Heute' + } + ,'Last 2 days' : { + cs: 'Poslední 2 dny' + ,de: 'letzte 2 Tage' + } + ,'Last 3 days' : { + cs: 'Poslední 3 dny' + ,de: 'letzte 3 Tage' + } + ,'Last week' : { + cs: 'Poslední týden' + ,de: 'letzte Woche' + } + ,'Last 2 weeks' : { + cs: 'Poslední 2 týdny' + ,de: 'letzte 2 Wochen' + } + ,'Last month' : { + cs: 'Poslední měsíc' + ,de: 'letzter Monat' + } + ,'Last 3 months' : { + cs: 'Poslední 3 měsíce' + ,de: 'letzte 3 Monate' + } + ,'From' : { + cs: 'Od' + ,de: 'Von' + } + ,'To' : { + cs: 'Do' + ,de: 'Bis' + } + ,'Notes' : { + cs: 'Poznámky' + ,de: 'Notiz' + } + ,'Food' : { + cs: 'Jídlo' + ,de: 'Essen' + } + ,'Insulin' : { + cs: 'Inzulín' + ,de: 'Insulin' + } + ,'Carbs' : { + cs: 'Sacharidy' + ,de: 'Kohlenhydrate' + } + ,'Notes contain' : { + cs: 'Poznámky obsahují' + ,de: 'Notizen beinhalten' + } + ,'Event type contains' : { + cs: 'Typ události obsahuje' + ,de: 'Ereignis-Typ beinhaltet' + } + ,'Target bg range bottom' : { + cs: 'Cílová glykémie spodní' + ,de: 'Untergrenze des Blutzuckerzielbereichs' + } + ,'top' : { + cs: 'horní' + ,de: 'oben' + } + ,'Show' : { + cs: 'Zobraz' + ,de: 'Zeige' + } + ,'Display' : { + cs: 'Zobraz' + ,de: 'Zeige' + } + ,'Loading' : { + cs: 'Nahrávám' + ,de: 'Laden' + } + ,'Loading profile' : { + cs: 'Nahrávám profil' + ,de: 'Lade Profil' + } + ,'Loading status' : { + cs: 'Nahrávám status' + ,de: 'Lade Status' + } + ,'Loading food database' : { + cs: 'Nahrávám databázi jídel' + ,de: 'Lade Essensdatenbank' + } + ,'not displayed' : { + cs: 'není zobrazeno' + ,de: 'nicht angezeigt' + } + ,'Loading CGM data of' : { + cs: 'Nahrávám CGM data' + ,de: 'Lade CGM-Daten von' + } + ,'Loading treatments data of' : { + cs: 'Nahrávám data ošetření' + ,de: 'Lade Behandlungsdaten von' + } + ,'Processing data of' : { + cs: 'Zpracovávám data' + ,de: 'Verarbeite Daten von' + } + ,'Portion' : { + cs: 'Porce' + ,de: 'Portion' + } + ,'Size' : { + cs: 'Rozměr' + ,de: 'Größe' + } + ,'(none)' : { + cs: '(Prázdný)' + ,de: '(nichts)' + } + ,'Result is empty' : { + cs: 'Prázdný výsledek' + ,de: 'Leeres Ergebnis' + } +// ported reporting + ,'Day to day' : { + cs: 'Den po dni' + } + ,'Daily Stats' : { + cs: 'Denní statistiky' + } + ,'Percentile Chart' : { + cs: 'Percentil' + } + ,'Distribution' : { + cs: 'Rozložení' + } + ,'Hourly stats' : { + cs: 'Statistika po hodinách' + } + ,'Weekly success' : { + cs: 'Statistika po týdnech' + } + ,'No data available' : { + cs: 'Žádná dostupná data' + } + ,'Low' : { + cs: 'Nízká' + } + ,'In Range' : { + cs: 'V rozsahu' + } + ,'Period' : { + cs: 'Období' + } + ,'High' : { + cs: 'Vysoká' + } + ,'Average' : { + cs: 'Průměrná' + } + ,'Low Quartile' : { + cs: 'Nízký kvartil' + } + ,'Upper Quartile' : { + cs: 'Vysoký kvartil' + } + ,'Quartile' : { + cs: 'Kvartil' + } + ,'Date' : { + cs: 'Datum' + } + ,'Normal' : { + cs: 'Normální' + } + ,'Median' : { + cs: 'Medián' + } + ,'Readings' : { + cs: 'Záznamů' + } + ,'StDev' : { + cs: 'St. odchylka' + } + ,'Daily stats report' : { + cs: 'Denní statistiky' + } + ,'Glucose Percentile report' : { + cs: 'Tabulka percentil glykémií' + } + ,'Glucose distribution' : { + cs: 'Rozložení glykémií' + } + ,'days total' : { + cs: 'dní celkem' + } + ,'Overall' : { + cs: 'Celkem' + } + ,'Range' : { + cs: 'Rozsah' + } + ,'% of Readings' : { + cs: '% záznamů' + } + ,'# of Readings' : { + cs: 'počet záznamů' + } + ,'Mean' : { + cs: 'Střední hodnota' + } + ,'Standard Deviation' : { + cs: 'Standardní odchylka' + } + ,'Max' : { + cs: 'Max' + } + ,'Min' : { + cs: 'Min' + } + ,'A1c estimation*' : { + cs: 'Předpokládané HBA1c*' + } + ,'Weekly Success' : { + cs: 'Týdenní úspěšnost' + } + ,'There is not sufficient data to run this report. Select more days.' : { + cs: 'Není dostatek dat. Vyberte delší časové období.' + } +// food editor + ,'Using stored API secret hash' : { + cs: 'Používám uložený hash API hesla' + } + ,'No API secret hash stored yet. You need to enter API secret.' : { + cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' + } + ,'Database loaded' : { + cs: 'Databáze načtena' + } + ,'Error: Database failed to load' : { + cs: 'Chyba při načítání databáze' + } + ,'Create new record' : { + cs: 'Vytvořit nový záznam' + } + ,'Save record' : { + cs: 'Uložit záznam' + } + ,'Portions' : { + cs: 'Porcí' + } + ,'Unit' : { + cs: 'Jedn' + } + ,'GI' : { + cs: 'GI' + } + ,'Edit record' : { + cs: 'Upravit záznam' + } + ,'Delete record' : { + cs: 'Smazat záznam' + } + ,'Move to the top' : { + cs: 'Přesuň na začátek' + } + ,'Hidden' : { + cs: 'Skrytý' + } + ,'Hide after use' : { + cs: 'Skryj po použití' + } + ,'Your API secret must be at least 12 characters long' : { + cs: 'Vaše API heslo musí mít alespoň 12 znaků' + } + ,'Bad API secret' : { + cs: 'Chybné API heslo' + } + ,'API secret hash stored' : { + cs: 'Hash API hesla uložen' + } + ,'Status' : { + cs: 'Status' + } + ,'Not loaded' : { + cs: 'Nenačtený' + } + ,'Food editor' : { + cs: 'Editor jídel' + } + ,'Your database' : { + cs: 'Vaše databáze' + } + ,'Filter' : { + cs: 'Filtr' + } + ,'Save' : { + cs: 'Ulož' + } + ,'Clear' : { + cs: 'Vymaž' + } + ,'Record' : { + cs: 'Záznam' + } + ,'Quick picks' : { + cs: 'Rychlý výběr' + } + ,'Show hidden' : { + cs: 'Zobraz skryté' + } + ,'Your API secret' : { + cs: 'Vaše API heslo' + } + ,'Store hash on this computer (Use only on private computers)' : { + cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' + } + ,'Treatments' : { + cs: 'Ošetření' + } + ,'Time' : { + cs: 'Čas' + } + ,'Event Type' : { + cs: 'Typ události' + } + ,'Blood Glucose' : { + cs: 'Glykémie' + } + ,'Entered By' : { + cs: 'Zadal' + } + ,'Delete this treatment?' : { + cs: 'Vymazat toto ošetření?' + } + ,'Carbs Given' : { + cs: 'Sacharidů' + } + ,'Inzulin Given' : { + cs: 'Inzulínu' + } + ,'Event Time' : { + cs: 'Čas události' + } + ,'Please verify that the data entered is correct' : { + cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' + } + ,'BG' : { + cs: 'Glykémie' + } + ,'Use BG correction in calculation' : { + cs: 'Použij korekci na glykémii' + } + ,'BG from CGM (autoupdated)' : { + cs: 'Glykémie z CGM (automaticky aktualizovaná)' + } + ,'BG from meter' : { + cs: 'Glykémie z glukoměru' + } + ,'Manual BG' : { + cs: 'Ručně zadaná glykémie' + } + ,'Quickpick' : { + cs: 'Rychlý výběr' + } + ,'or' : { + cs: 'nebo' + } + ,'Add from database' : { + cs: 'Přidat z databáze' + } + ,'Use carbs correction in calculation' : { + cs: 'Použij korekci na sacharidy' + } + ,'Use COB correction in calculation' : { + cs: 'Použij korekci na COB' + } + ,'Use IOB in calculation' : { + cs: 'Použij IOB ve výpočtu' + } + ,'Other correction' : { + cs: 'Jiná korekce' + } + ,'Rounding' : { + cs: 'Zaokrouhlení' + } + ,'Enter insulin correction in treatment' : { + cs: 'Zahrň inzulín do záznamu ošetření' + } + ,'Insulin needed' : { + cs: 'Potřebný inzulín' + } + ,'Carbs needed' : { + cs: 'Potřebné sach' + } + ,'Carbs needed if Insulin total is negative value' : { + cs: 'Chybějící sacharidy v případě, že výsledek je záporný' + } + ,'Basal rate' : { + cs: 'Bazál' + } + ,'Eating' : { + cs: 'Jídlo' + } + ,'60 minutes before' : { + cs: '60 min předem' + } + ,'45 minutes before' : { + cs: '45 min předem' + } + ,'30 minutes before' : { + cs: '30 min předem' + } + ,'20 minutes before' : { + cs: '20 min předem' + } + ,'15 minutes before' : { + cs: '15 min předem' + } + ,'Time in minutes' : { + cs: 'Čas v minutách' + } + ,'15 minutes after' : { + cs: '15 min po' + } + ,'20 minutes after' : { + cs: '20 min po' + } + ,'30 minutes after' : { + cs: '30 min po' + } + ,'45 minutes after' : { + cs: '45 min po' + } + ,'60 minutes after' : { + cs: '60 min po' + } + ,'Additional Notes, Comments:' : { + cs: 'Dalši poznámky, komentáře:' + } + ,'RETRO MODE' : { + cs: 'V MINULOSTI' + } + ,'Now' : { + cs: 'Nyní' + } + ,'Other' : { + cs: 'Jiný' + } + ,'Submit Form' : { + cs: 'Odeslat formulář' + } + ,'Profile editor' : { + cs: 'Editor profilu' + } + ,'Reporting tool' : { + cs: 'Výkazy' + } + ,'Add food from your database' : { + cs: 'Přidat jidlo z Vaší databáze' + } + ,'Reload database' : { + cs: 'Znovu nahraj databázi' + } + ,'Add' : { + cs: 'Přidej' + } + ,'Unauthorized' : { + cs: 'Neautorizováno' + } + ,'Entering record failed' : { + cs: 'Vložení záznamu selhalo' + } + ,'Device authenticated' : { + cs: 'Zařízení ověřeno' + } + ,'Device not authenticated' : { + cs: 'Zařízení není ověřeno' + } + ,'Authentication status' : { + cs: 'Stav ověření' + } + ,'Authenticate' : { + cs: 'Ověřit' + } + ,'Remove' : { + cs: 'Vymazat' + } + ,'Your device is not authenticated yet' : { + cs: 'Toto zařízení nebylo dosud ověřeno' + } + ,'Sensor' : { + cs: 'Senzor' + } + ,'Finger' : { + cs: 'Glukoměr' + } + ,'Manual' : { + cs: 'Ručně' + } + ,'Scale' : { + cs: 'Měřítko' + } + ,'Linear' : { + cs: 'lineární' + } + ,'Logarithmic' : { + cs: 'logaritmické' + } + ,'Silence for 30 minutes' : { + cs: 'Ztlumit na 30 minut' + } + ,'Silence for 60 minutes' : { + cs: 'Ztlumit na 60 minut' + } + ,'Silence for 90 minutes' : { + cs: 'Ztlumit na 90 minut' + } + ,'Silence for 120 minutes' : { + cs: 'Ztlumit na 120 minut' + } + ,'3HR' : { + cs: '3hod' + } + ,'6HR' : { + cs: '6hod' + } + ,'12HR' : { + cs: '12hod' + } + ,'24HR' : { + cs: '24hod' + } + ,'Sttings' : { + cs: 'Nastavení' + } + ,'Units' : { + cs: 'Jednotky' + } + ,'Date format' : { + cs: 'Formát datumu' + } + ,'12 hours' : { + cs: '12 hodin' + } + ,'24 hours' : { + cs: '24 hodin' + } + ,'Log a Treatment' : { + cs: 'Záznam ošetření' + } + ,'BG Check' : { + cs: 'Kontrola glykémie' + } + ,'Meal Bolus' : { + cs: 'Bolus na jídlo' + } + ,'Snack Bolus' : { + cs: 'Bolus na svačinu' + } + ,'Correction Bolus' : { + cs: 'Bolus na glykémii' + } + ,'Carb Correction' : { + cs: 'Přídavek sacharidů' + } + ,'Note' : { + cs: 'Poznámka' + } + ,'Question' : { + cs: 'Otázka' + } + ,'Exercise' : { + cs: 'Cvičení' + } + ,'Pump Site Change' : { + cs: 'Přepíchnutí kanyly' + } + ,'Sensor Start' : { + cs: 'Spuštění sensoru' + } + ,'Sensor Change' : { + cs: 'Výměna sensoru' + } + ,'Dexcom Sensor Start' : { + cs: 'Spuštění sensoru' + } + ,'Dexcom Sensor Change' : { + cs: 'Výměna sensoru' + } + ,'Insulin Cartridge Change' : { + cs: 'Výměna inzulínu' + } + ,'D.A.D. Alert' : { + cs: 'D.A.D. Alert' + } + ,'Glucose Reading' : { + cs: 'Hodnota glykémie' + } + ,'Measurement Method' : { + cs: 'Metoda měření' + } + ,'Meter' : { + cs: 'Glukoměr' + } + ,'Insulin Given' : { + cs: 'Inzulín' + } + ,'Amount in grams' : { + cs: 'Množství v gramech' + } + ,'Amount in units' : { + cs: 'Množství v jednotkách' + } + ,'View all treatments' : { + cs: 'Zobraz všechny ošetření' + } + ,'Enable Alarms' : { + cs: 'Povolit alarmy' + } + ,'When enabled an alarm may sound.' : { + cs: 'Při povoleném alarmu zní zvuk' + } + ,'Urgent High Alarm' : { + cs: 'Urgentní vysoká glykémie' + } + ,'High Alarm' : { + cs: 'Vysoká glykémie' + } + ,'Low Alarm' : { + cs: 'Nízká glykémie' + } + ,'Urgent Low Alarm' : { + cs: 'Urgentní nízká glykémie' + } + ,'Stale Data: Warn' : { + cs: 'Zastaralá data' + } + ,'Stale Data: Urgent' : { + cs: 'Zastaralá data urgentní' + } + ,'mins' : { + cs: 'min' + } + ,'Night Mode' : { + cs: 'Noční mód' + } + ,'When enabled the page will be dimmed from 10pm - 6am.' : { + cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' + } + ,'Enable' : { + cs: 'Povoleno' + } + ,'Settings' : { + cs: 'Nastavení' + } + ,'Show Raw BG Data' : { + cs: 'Zobraz RAW data' + } + ,'Never' : { + cs: 'Nikdy' + } + ,'Always' : { + cs: 'Vždy' + } + ,'When there is noise' : { + cs: 'Při šumu' + } + ,'When enabled small white dots will be disaplyed for raw BG data' : { + cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' + } + ,'Custom Title' : { + cs: 'Vlastní název stránky' + } + ,'Theme' : { + cs: 'Téma' + } + ,'Default' : { + cs: 'Výchozí' + } + ,'Colors' : { + cs: 'Barevné' + } + ,'Reset, and use defaults' : { + cs: 'Vymaž a nastav výchozí hodnoty' + } + ,'Calibrations' : { + cs: 'Kalibrace' + } + ,'Alarm Test / Smartphone Enable' : { + cs: 'Test alarmu' + } + ,'Bolus Wizard' : { + cs: 'Bolusový kalkulátor' + } + ,'in the future' : { + cs: 'v budoucnosti' + } + ,'time ago' : { + cs: 'min zpět' + } + ,'hr ago' : { + cs: 'hod zpět' + } + ,'hrs ago' : { + cs: 'hod zpět' + } + ,'min ago' : { + cs: 'min zpět' + } + ,'mins ago' : { + cs: 'min zpět' + } + ,'day ago' : { + cs: 'den zpět' + } + ,'days ago' : { + cs: 'dnů zpět' + } + ,'long ago' : { + cs: 'dlouho zpět' + } + ,'Clean' : { + cs: 'Čistý' + } + ,'Light' : { + cs: 'Lehký' + } + ,'Medium' : { + cs: 'Střední' + } + ,'Heavy' : { + cs: 'Velký' + } + ,'Treatment type' : { + cs: 'Typ ošetření' + } + ,'Raw BG' : { + cs: 'Glykémie z RAW dat' + } + ,'Device' : { + cs: 'Zařízení' + } + ,'Noise' : { + cs: 'Šum' + } + ,'Calibration' : { + cs: 'Kalibrace' + } + ,'1' : { + cs: '1' + } + + }; + + language.translate = function translate(text) { + if (translations[text] && translations[text][lang]) + return translations[text][lang]; + return text; + } + + if (typeof window !== 'undefined') { + // do translation of static text on load + $('.translate').each(function (s) { + $(this).text(language.translate($(this).text())); + }); + $('.titletranslate').each(function (s) { + $(this).attr('title',language.translate($(this).attr('title'))); + $(this).attr('original-title',language.translate($(this).attr('original-title'))); + $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); + }); + } + return language(); +} + +module.exports = init; \ No newline at end of file diff --git a/server.js b/server.js index db868d242a9..846524a61a2 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,7 @@ /////////////////////////////////////////////////// var env = require('./env')( ); - +var translate = require('./lib/language')(env.defaults.language).translate; /////////////////////////////////////////////////// // setup http server diff --git a/static/index.html b/static/index.html index ed6bdaf8ec2..594fbe9157c 100644 --- a/static/index.html +++ b/static/index.html @@ -249,9 +249,9 @@
    + - diff --git a/static/js/client.js b/static/js/client.js index fabdb346b33..25469507f26 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -50,6 +50,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , delta = Nightscout.plugins('delta') , direction = Nightscout.plugins('direction') , errorcodes = Nightscout.plugins('errorcodes') + , translate = Nightscout.language.translate , timeAgo = Nightscout.utils.timeAgo; var jqWindow From 4464e296099dad291c2cd2b0f4321b03a28589f5 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:19:50 +0200 Subject: [PATCH 451/661] date locale fix --- static/js/treatment.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/treatment.js b/static/js/treatment.js index 22f4742441f..770f90205ac 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -84,7 +84,7 @@ if (data.notes) { text.push('Notes: ' + data.notes); } if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } - text.push('Event Time: ' + (data.eventTime ? data.eventTime.format('LLL') : moment().format('LLL'))); + text.push('Event Time: ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); return text.join('\n'); } From a3d6fccd446fef7e04a160e17efecd74f2141ed6 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:25:57 +0200 Subject: [PATCH 452/661] codacy fix 1 --- lib/language.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/language.js b/lib/language.js index 049913e9f26..c88c9546d5a 100644 --- a/lib/language.js +++ b/lib/language.js @@ -1,7 +1,5 @@ 'use strict'; -var _ = require('lodash'); - function init(lang) { function language() { @@ -10,7 +8,7 @@ function init(lang) { var translations = { // Client - ,'Mo' : { + 'Mo' : { cs: 'Po' ,de: 'Mo' } From 540b578660cab8f3bd8ff95a34288a079f41c4f1 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:32:26 +0200 Subject: [PATCH 453/661] codacy fix 2 --- lib/language.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/language.js b/lib/language.js index c88c9546d5a..1cca4010071 100644 --- a/lib/language.js +++ b/lib/language.js @@ -833,17 +833,18 @@ function init(lang) { }; language.translate = function translate(text) { - if (translations[text] && translations[text][lang]) + if (translations[text] && translations[text][lang]) { return translations[text][lang]; + } return text; } if (typeof window !== 'undefined') { // do translation of static text on load - $('.translate').each(function (s) { + $('.translate').each(function () { $(this).text(language.translate($(this).text())); }); - $('.titletranslate').each(function (s) { + $('.titletranslate').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); From e4b0e82840a015a38edd6ceb7ad8dc0159729699 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 18:35:03 +0200 Subject: [PATCH 454/661] codacy fix 3 --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 1cca4010071..d8cb49e09b1 100644 --- a/lib/language.js +++ b/lib/language.js @@ -837,7 +837,7 @@ function init(lang) { return translations[text][lang]; } return text; - } + }; if (typeof window !== 'undefined') { // do translation of static text on load From 047cfa649e9fa48a83b89d8b7122e1bbf3b50223 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 19:09:27 +0200 Subject: [PATCH 455/661] some examples added --- lib/language.js | 6 +++++- server.js | 2 +- static/index.html | 10 +++++----- static/js/client.js | 22 +++++++++++----------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/language.js b/lib/language.js index d8cb49e09b1..6cf1f8bb161 100644 --- a/lib/language.js +++ b/lib/language.js @@ -7,8 +7,12 @@ function init(lang) { } var translations = { + // Server + 'Listening on port' : { + cs: 'Poslouchám na portu' + } // Client - 'Mo' : { + ,'Mo' : { cs: 'Po' ,de: 'Mo' } diff --git a/server.js b/server.js index 846524a61a2..a2478b1dfb5 100644 --- a/server.js +++ b/server.js @@ -46,7 +46,7 @@ function create (app) { require('./lib/bootevent')(env).boot(function booted (ctx) { var app = require('./app')(env, ctx); var server = create(app).listen(PORT); - console.log('listening', PORT); + console.log(translate('Listening on port'), PORT); if (env.MQTT_MONITOR) { ctx.mqtt = require('./lib/mqtt')(env, ctx); diff --git a/static/index.html b/static/index.html index 594fbe9157c..9d2ab4b79ea 100644 --- a/static/index.html +++ b/static/index.html @@ -70,10 +70,10 @@
      -
    • 3HR
    • -
    • 6HR
    • -
    • 12HR
    • -
    • 24HR
    • +
    • 3HR
    • +
    • 6HR
    • +
    • 12HR
    • +
    • 24HR
    @@ -96,7 +96,7 @@
    -
    Enable Alarms
    +
    Enable Alarms
    diff --git a/static/js/client.js b/static/js/client.js index 25469507f26..a5268de6440 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -611,11 +611,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; prepareTreatCircles(treatCircles.enter().append('circle')) .on('mouseover', function (d) { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(new Date(d.mills)) + '
    ' + - (d.eventType ? 'Treatment type: ' + d.eventType + '
    ' : '') + - (d.glucose ? 'BG: ' + d.glucose + (d.glucoseType ? ' (' + d.glucoseType + ')': '') + '
    ' : '') + - (d.enteredBy ? 'Entered by: ' + d.enteredBy + '
    ' : '') + - (d.notes ? 'Notes: ' + d.notes : '') + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + + (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + + (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + + (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + + (d.notes ? ''+translate('Notes')+': ' + d.notes : '') ) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); @@ -1131,12 +1131,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') .on('mouseover', function () { tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('Time: ' + formatTime(new Date(treatment.mills)) + '
    ' + 'Treatment type: ' + treatment.eventType + '
    ' + - (treatment.carbs ? 'Carbs: ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? 'Insulin: ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? 'BG: ' + treatment.glucose + (treatment.glucoseType ? ' (' + treatment.glucoseType + ')': '') + '
    ' : '') + - (treatment.enteredBy ? 'Entered by: ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? 'Notes: ' + treatment.notes : '') + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + + (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + + (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') ) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY + 15) + 'px'); From 9205bca9158e7d01d619a41214040764817fb804 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 19:49:08 +0200 Subject: [PATCH 456/661] move serverSettings outside bundle --- bundle/bundle.source.js | 2 +- lib/language.js | 11 +++++++++-- server.js | 3 ++- static/js/client.js | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 5bf8e7902bc..f0e43488b6f 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -9,7 +9,7 @@ units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), - language: require('../lib/language')(serverSettings.defaults.language), + language: require('../lib/language')(), plugins: require('../lib/plugins/')().registerClientDefaults(), sandbox: require('../lib/sandbox')() }; diff --git a/lib/language.js b/lib/language.js index 6cf1f8bb161..b7197a65770 100644 --- a/lib/language.js +++ b/lib/language.js @@ -1,6 +1,7 @@ 'use strict'; -function init(lang) { +function init() { + var lang; function language() { return language; @@ -843,7 +844,7 @@ function init(lang) { return text; }; - if (typeof window !== 'undefined') { + language.DOMtranslate = function DOMtranslate() { // do translation of static text on load $('.translate').each(function () { $(this).text(language.translate($(this).text())); @@ -854,6 +855,12 @@ function init(lang) { $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); }); } + + language.set = function set(newlang) { + lang = newlang; + return language(); + } + return language(); } diff --git a/server.js b/server.js index a2478b1dfb5..95ac7176945 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,8 @@ /////////////////////////////////////////////////// var env = require('./env')( ); -var translate = require('./lib/language')(env.defaults.language).translate; +var language = require('./lib/language')(); +var translate = language.set(env.defaults.language).translate; /////////////////////////////////////////////////// // setup http server diff --git a/static/js/client.js b/static/js/client.js index a5268de6440..2e03c6a9c1a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -81,6 +81,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , statusPills = $('.status .statusPills') ; + Nightscout.language.set(serverSettings.defaults.language).DOMtranslate(); + function formatTime(time, compact) { var timeFormat = getTimeFormat(false, compact); time = d3.time.format(timeFormat)(time); From e0e6643e91a6cdad6cca5e820257f6b5c364053f Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Tue, 21 Jul 2015 20:50:03 +0200 Subject: [PATCH 457/661] codacy fix 4 --- lib/language.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/language.js b/lib/language.js index b7197a65770..369a17c253d 100644 --- a/lib/language.js +++ b/lib/language.js @@ -854,12 +854,12 @@ function init() { $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); }); - } + }; language.set = function set(newlang) { lang = newlang; return language(); - } + }; return language(); } From 1daf2f90875fe3834f6a1f7b45e1dd87d086b496 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Jul 2015 23:23:56 -0700 Subject: [PATCH 458/661] use the status.js that was already loaded instead of getting the same with status.json again --- static/js/client.js | 142 ++++++++++++++++++------------------------ static/js/ui-utils.js | 42 ++++++------- 2 files changed, 83 insertions(+), 101 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 2e03c6a9c1a..7f5c0a4018f 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,5 +1,5 @@ //TODO: clean up -var app = {}, browserSettings = {}, browserStorage = $.localStorage; +var browserSettings = {}, browserStorage = $.localStorage; (function () { 'use strict'; @@ -365,7 +365,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - sbx = Nightscout.sandbox.clientInit(app, browserSettings, time, pluginBase, { + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { sgvs: sgvs , cals: [cal] , treatments: treatments @@ -721,9 +721,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'high-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -731,9 +731,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'target-top-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -741,9 +741,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'target-bottom-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -751,9 +751,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'low-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) .style('stroke-dasharray', ('1, 6')) .attr('stroke', '#777'); @@ -787,9 +787,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('line') .attr('class', 'high-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -797,9 +797,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('line') .attr('class', 'low-line') .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .style('stroke-dasharray', ('3, 3')) .attr('stroke', 'grey'); @@ -846,33 +846,33 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_high))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_high))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); focus.select('.target-top-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_top))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); focus.select('.target-bottom-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_target_bottom))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); focus.select('.low-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(app.thresholds.bg_low))) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(app.thresholds.bg_low))); + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); // transition open-top line to correct location focus.select('.open-top') @@ -906,18 +906,18 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_top))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_top))); + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); // transition low line to correct location context.select('.low-line') .transition() .duration(UPDATE_TRANS_MS) .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(app.thresholds.bg_target_bottom))) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(app.thresholds.bg_target_bottom))); + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); } } @@ -985,15 +985,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var color = 'grey'; if (browserSettings.theme === 'colors') { - if (sgv > app.thresholds.bg_high) { + if (sgv > serverSettings.thresholds.bg_high) { color = 'red'; - } else if (sgv > app.thresholds.bg_target_top) { + } else if (sgv > serverSettings.thresholds.bg_target_top) { color = 'yellow'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { color = '#4cff00'; - } else if (sgv < app.thresholds.bg_low) { + } else if (sgv < serverSettings.thresholds.bg_low) { color = 'red'; - } else if (sgv < app.thresholds.bg_target_bottom) { + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { color = 'yellow'; } } @@ -1005,15 +1005,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var range = ''; if (browserSettings.theme === 'colors') { - if (sgv > app.thresholds.bg_high) { + if (sgv > serverSettings.thresholds.bg_high) { range = 'urgent'; - } else if (sgv > app.thresholds.bg_target_top) { + } else if (sgv > serverSettings.thresholds.bg_target_top) { range = 'warning'; - } else if (sgv >= app.thresholds.bg_target_bottom && sgv <= app.thresholds.bg_target_top) { + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { range = 'inrange'; - } else if (sgv < app.thresholds.bg_low) { + } else if (sgv < serverSettings.thresholds.bg_low) { range = 'urgent'; - } else if (sgv < app.thresholds.bg_target_bottom) { + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { range = 'warning'; } } @@ -1284,21 +1284,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (browserSettings.units === 'mmol') { tickValues = [ 2.0 - , Math.round(scaleBg(app.thresholds.bg_low)) - , Math.round(scaleBg(app.thresholds.bg_target_bottom)) + , Math.round(scaleBg(serverSettings.thresholds.bg_low)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) , 6.0 - , Math.round(scaleBg(app.thresholds.bg_target_top)) - , Math.round(scaleBg(app.thresholds.bg_high)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) + , Math.round(scaleBg(serverSettings.thresholds.bg_high)) , 22.0 ]; } else { tickValues = [ 40 - , app.thresholds.bg_low - , app.thresholds.bg_target_bottom + , serverSettings.thresholds.bg_low + , serverSettings.thresholds.bg_target_bottom , 120 - , app.thresholds.bg_target_top - , app.thresholds.bg_high + , serverSettings.thresholds.bg_target_top + , serverSettings.thresholds.bg_high , 400 ]; } @@ -1494,13 +1494,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a HIGH we can only check if it's >= the bottom of the target function isAlarmForHigh() { - return latestSGV.mgdl >= app.thresholds.bg_target_bottom; + return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; } //with predicted alarms, latestSGV may still be in target so to see if the alarm // is for a LOW we can only check if it's <= the top of the target function isAlarmForLow() { - return latestSGV.mgdl <= app.thresholds.bg_target_top; + return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; } socket.on('alarm', function (notify) { @@ -1552,39 +1552,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); } - $.ajax('/api/v1/status.json', { - success: function (xhr) { - app = { name: xhr.name - , version: xhr.version - , head: xhr.head - , apiEnabled: xhr.apiEnabled - , enabledOptions: xhr.enabledOptions || '' - , extendedSettings: xhr.extendedSettings - , thresholds: xhr.thresholds - , alarm_types: xhr.alarm_types - , units: xhr.units - , careportalEnabled: xhr.careportalEnabled - , defaults: xhr.defaults - }; - - //TODO: currently we need the ar2 plugin for the cone - if (app.enabledOptions.indexOf('ar2') < 0) { - app.enabledOptions += ' ar2'; - } - } - }).done(function() { - $('.appName').text(app.name); - $('.version').text(app.version); - $('.head').text(app.head); - if (app.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(app.careportalEnabled); - Nightscout.plugins.init(app); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(app, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - init(); - }); + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + Nightscout.plugins.init(serverSettings); + browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); })(); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 4fcc6b85a4a..93651c0cc22 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -3,7 +3,7 @@ var openDraw = null; function rawBGsEnabled() { - return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; + return serverSettings.enabledOptions && serverSettings.enabledOptions.indexOf('rawbg') > -1; } function getBrowserSettings(storage) { @@ -18,7 +18,7 @@ function getBrowserSettings(storage) { } function appendThresholdValue(threshold) { - return app.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; + return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; } try { @@ -41,54 +41,54 @@ function getBrowserSettings(storage) { }; // Default browser units to server units if undefined. - json.units = setDefault(json.units, app.units); + json.units = setDefault(json.units, serverSettings.units); if (json.units === 'mmol') { $('#mmol-browser').prop('checked', true); } else { $('#mgdl-browser').prop('checked', true); } - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, app.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); + json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); $('#nightmode-browser').prop('checked', json.nightMode); if (rawBGsEnabled()) { $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, app.defaults.showRawbg); + json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); $('#show-rawbg-' + json.showRawbg).prop('checked', true); } else { json.showRawbg = 'never'; $('#show-rawbg-option').hide(); } - json.customTitle = setDefault(json.customTitle, app.defaults.customTitle); + json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); $('h1.customTitle').text(json.customTitle); $('input#customTitle').prop('value', json.customTitle); - json.theme = setDefault(json.theme, app.defaults.theme); + json.theme = setDefault(json.theme, serverSettings.defaults.theme); if (json.theme === 'colors') { $('#theme-colors-browser').prop('checked', true); } else { $('#theme-default-browser').prop('checked', true); } - json.timeFormat = setDefault(json.timeFormat, app.defaults.timeFormat); + json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); if (json.timeFormat === '24') { $('#24-browser').prop('checked', true); @@ -96,7 +96,7 @@ function getBrowserSettings(storage) { $('#12-browser').prop('checked', true); } - json.showPlugins = setDefault(json.showPlugins, app.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); + json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); var showPluginsSettings = $('#show-plugins'); Nightscout.plugins.eachEnabledPlugin(function each(plugin) { if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { From de952454f74f3f0500ef238f99d4f406f4d1eecc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Jul 2015 23:26:00 -0700 Subject: [PATCH 459/661] move more to init() --- static/js/client.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 7f5c0a4018f..76059967aa3 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1550,23 +1550,25 @@ var browserSettings = {}, browserStorage = $.localStorage; }); event.preventDefault(); }); - } - if (serverSettings.enabledOptions.indexOf('ar2') < 0) { - serverSettings.enabledOptions += ' ar2'; - } + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + Nightscout.plugins.init(serverSettings); + browserSettings = getBrowserSettings(browserStorage); + sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - $('.appName').text(serverSettings.name); - $('.version').text(serverSettings.version); - $('.head').text(serverSettings.head); - if (serverSettings.apiEnabled) { - $('.serverSettings').show(); } - $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); - Nightscout.plugins.init(serverSettings); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); + init(); })(); From 63ebc0f9188f90d6ce7d01b5140b597160f7f05c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:13:34 -0700 Subject: [PATCH 460/661] move client.js to a module; needs massive clean up; but there is a very basic test --- bower.json | 1 - bundle/bundle.source.js | 6 +- lib/client/index.js | 1710 +++++++++++++++++++++++++++++++++++++++ package.json | 6 +- static/js/client.js | 1575 +----------------------------------- static/js/ui-utils.js | 139 ---- tests/client.test.js | 53 ++ 7 files changed, 1774 insertions(+), 1716 deletions(-) create mode 100644 lib/client/index.js create mode 100644 tests/client.test.js diff --git a/bower.json b/bower.json index da22cab72e3..1ca7b038629 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,6 @@ "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", - "d3": "~3.5.5", "jquery": "2.1.0", "jQuery-Storage-API": "~1.7.2", "jsSHA": "~1.5.0", diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index f0e43488b6f..22569054a2c 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -5,15 +5,19 @@ window.moment = require('moment-timezone'); window.Nightscout = window.Nightscout || {}; + var plugins = require('../lib/plugins/')().registerClientDefaults(); + window.Nightscout = { units: require('../lib/units')(), utils: require('../lib/utils')(), profile: require('../lib/profilefunctions')(), language: require('../lib/language')(), - plugins: require('../lib/plugins/')().registerClientDefaults(), + plugins: plugins, sandbox: require('../lib/sandbox')() }; + window.Nightscout.client = require('../lib/client'); + console.info('Nightscout bundle ready', window.Nightscout); })(); diff --git a/lib/client/index.js b/lib/client/index.js new file mode 100644 index 00000000000..9d69c25c606 --- /dev/null +++ b/lib/client/index.js @@ -0,0 +1,1710 @@ +'use strict'; + +var $ = (global && global.$) || require('jquery'); +var _ = require('lodash'); +var d3 = (global && global.d3) || require('d3'); + +var language = require('../language')(); +var profile = require('../profilefunctions')(); +var sandbox = require('../sandbox')(); +var utils = require('../utils')(); + +function init(plugins, serverSettings) { + + var BRUSH_TIMEOUT = 300000 // 5 minutes in ms + , DEBOUNCE_MS = 10 + , TOOLTIP_TRANS_MS = 200 // milliseconds + , UPDATE_TRANS_MS = 750 // milliseconds + , ONE_MIN_IN_MS = 60000 + , FIVE_MINS_IN_MS = 300000 + , THREE_HOURS_MS = 3 * 60 * 60 * 1000 + , TWENTY_FIVE_MINS_IN_MS = 1500000 + , THIRTY_MINS_IN_MS = 1800000 + , SIXTY_MINS_IN_MS = 3600000 + , FORMAT_TIME_12 = '%-I:%M %p' + , FORMAT_TIME_12_COMAPCT = '%-I:%M' + , FORMAT_TIME_24 = '%H:%M%' + , FORMAT_TIME_12_SCALE = '%-I %p' + , FORMAT_TIME_24_SCALE = '%H' + , WIDTH_SMALL_DOTS = 400 + , WIDTH_BIG_DOTS = 800; + + var socket + , isInitialData = false + , SGVdata = [] + , MBGdata = [] + , latestSGV + , latestUpdateTime + , prevSGV + , treatments + , cal + , devicestatusData + , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , opacity = {current: 1, DAY: 1, NIGHT: 0.5} + , now = Date.now() + , data = [] + , foucusRangeMS = THREE_HOURS_MS + , clientAlarms = {} + , alarmInProgress = false + , alarmMessage + , currentAlarmType = null + , alarmSound = 'alarm.mp3' + , urgentAlarmSound = 'alarm2.mp3'; + + var sbx + , rawbg = plugins('rawbg') + , delta = plugins('delta') + , direction = plugins('direction') + , errorcodes = plugins('errorcodes') + , translate = language.translate + , timeAgo = utils.timeAgo; + + var tooltip + , tickValues + , charts + , futureOpacity + , focus + , context + , xScale, xScale2, yScale, yScale2 + , xAxis, yAxis, xAxis2, yAxis2 + , prevChartWidth = 0 + , prevChartHeight = 0 + , focusHeight + , contextHeight + , dateFn = function (d) { return new Date(d.mills) } + , documentHidden = false + , brush + , brushTimer + , brushInProgress = false + , clip; + + var container = $('.container') + , bgStatus = $('.bgStatus') + , currentBG = $('.bgStatus .currentBG') + , majorPills = $('.bgStatus .majorPills') + , minorPills = $('.bgStatus .minorPills') + , statusPills = $('.status .statusPills') + ; + + var browserSettings = getBrowserSettings(); + language.set(serverSettings.defaults.language).DOMtranslate(); + + function formatTime(time, compact) { + var timeFormat = getTimeFormat(false, compact); + time = d3.time.format(timeFormat)(time); + if (!isTimeFormat24()) { + time = time.toLowerCase(); + } + return time; + } + + function isTimeFormat24() { + return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; + } + + function getTimeFormat(isForScale, compact) { + var timeFormat = FORMAT_TIME_12; + if (isTimeFormat24()) { + timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; + } else { + timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); + } + + return timeFormat; + } + +// lixgbg: Convert mg/dL BG value to metric mmol + function scaleBg(bg) { + if (browserSettings.units === 'mmol') { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function generateTitle() { + + function s(value, sep) { return value ? value + ' ' : sep || ''; } + + var bg_title = ''; + + var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) + , ago = timeAgo(time, browserSettings); + + if (browserSettings.customTitle) { + $('.customTitle').text(browserSettings.customTitle); + } + + if (ago && ago.status !== 'current') { + bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; + } else if (latestSGV) { + var currentMgdl = latestSGV.mgdl; + + if (currentMgdl < 39) { + bg_title = s(errorcodes.toDisplay(currentMgdl), ' - ') + bg_title; + } else { + var deltaDisplay = delta.calc(prevSGV, latestSGV, sbx).display; + bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(direction.info(latestSGV).label) + bg_title; + } + } + return bg_title; + } + + function updateTitle(skipPageTitle) { + + var bg_title = browserSettings.customTitle || ''; + + if (alarmMessage && alarmInProgress) { + bg_title = alarmMessage + ': ' + generateTitle(); + $('.customTitle').text(alarmMessage); + } else { + bg_title = generateTitle(); + } + + if (!skipPageTitle) { + $(document).attr('title', bg_title); + } + } + +// initial setup of chart when data is first made available + function initializeCharts() { + + // define the parts of the axis that aren't dependent on width or height + xScale = d3.time.scale() + .domain(d3.extent(data, dateFn)); + + yScale = d3.scale.log() + .domain([scaleBg(30), scaleBg(510)]); + + xScale2 = d3.time.scale() + .domain(d3.extent(data, dateFn)); + + yScale2 = d3.scale.log() + .domain([scaleBg(36), scaleBg(420)]); + + var tickFormat = d3.time.format.multi( [ + ['.%L', function(d) { return d.getMilliseconds(); }], + [':%S', function(d) { return d.getSeconds(); }], + ['%I:%M', function(d) { return d.getMinutes(); }], + [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], + ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], + ['%b %d', function(d) { return d.getDate() !== 1; }], + ['%B', function(d) { return d.getMonth(); }], + ['%Y', function() { return true; }] + ]); + + xAxis = d3.svg.axis() + .scale(xScale) + .tickFormat(tickFormat) + .ticks(4) + .orient('bottom'); + + yAxis = d3.svg.axis() + .scale(yScale) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('left'); + + xAxis2 = d3.svg.axis() + .scale(xScale2) + .tickFormat(tickFormat) + .ticks(6) + .orient('bottom'); + + yAxis2 = d3.svg.axis() + .scale(yScale2) + .tickFormat(d3.format('d')) + .tickValues(tickValues) + .orient('right'); + + // setup a brush + brush = d3.svg.brush() + .x(xScale2) + .on('brushstart', brushStarted) + .on('brush', brushed) + .on('brushend', brushEnded); + + updateChart(true); + } + +// get the desired opacity for context chart based on the brush extent + function highlightBrushPoints(data) { + if (data.mills >= brush.extent()[0].getTime() && data.mills <= brush.extent()[1].getTime()) { + return futureOpacity(data.mills - latestSGV.mills); + } else { + return 0.5; + } + } + + function addPlaceholderPoints () { + // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') + // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with + // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be + // required to happen when 'now' event is sent from websocket.js every minute. When fixed, + // remove this code and all references to `type: 'server-forecast'` + var last = _.last(data); + var lastTime = last && last.mills; + if (!lastTime) { + console.error('Bad Data, last point has no mills', last); + lastTime = Date.now(); + } + + var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; + for (var i = 1; i <= n; i++) { + data.push({ + mills: lastTime + (i * FIVE_MINS_IN_MS), mgdl: 100, color: 'none', type: 'server-forecast' + }); + } + } + +// clears the current user brush and resets to the current real time data + function updateBrushToNow(skipBrushing) { + + // get current time range + var dataRange = d3.extent(data, dateFn); + + // update brush and focus chart with recent data + d3.select('.brush') + .transition() + .duration(UPDATE_TRANS_MS) + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + + addPlaceholderPoints(); + + if (!skipBrushing) { + brushed(true); + + // clear user brush tracking + brushInProgress = false; + } + } + + function brushStarted() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', 1); + } + + function brushEnded() { + // update the opacity of the context data points to brush extent + context.selectAll('circle') + .data(data) + .style('opacity', function (d) { return highlightBrushPoints(d) }); + } + + function alarmingNow() { + return container.hasClass('alarming'); + } + + function inRetroMode() { + if (!brush) { + return false; + } + + var time = brush.extent()[1].getTime(); + + return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; + } + + function brushed(skipTimer) { + + if (!skipTimer) { + // set a timer to reset focus chart to real-time data + clearTimeout(brushTimer); + brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); + brushInProgress = true; + } + + var brushExtent = brush.extent(); + + // ensure that brush extent is fixed at 3.5 hours + if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { + // ensure that brush updating is with the time range + if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { + brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } else { + brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); + d3.select('.brush') + .call(brush.extent([brushExtent[0], brushExtent[1]])); + } + } + + var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); + + function updateCurrentSGV(entry) { + var value = entry.mgdl + , ago = timeAgo(entry.mills, browserSettings) + , isCurrent = ago.status === 'current'; + + if (value === 9) { + currentBG.text(''); + } else if (value < 39) { + currentBG.html(errorcodes.toDisplay(value)); + } else if (value < 40) { + currentBG.text('LOW'); + } else if (value > 400) { + currentBG.text('HIGH'); + } else { + currentBG.text(scaleBg(value)); + } + + bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); + if (!alarmingNow()) { + container.removeClass('urgent warning inrange'); + if (isCurrent && !inRetroMode()) { + container.addClass(sgvToColoredRange(value)); + } + } + + currentBG.toggleClass('icon-hourglass', value === 9); + currentBG.toggleClass('error-code', value < 39); + currentBG.toggleClass('bg-limit', value === 39 || value > 400); + + $('.container').removeClass('loading'); + + } + + function updatePlugins (sgvs, time) { + + var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); + + sbx = sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { + sgvs: sgvs + , cals: [cal] + , treatments: treatments + , profile: Nightscout.profile + , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery + }); + + //all enabled plugins get a chance to set properties, even if they aren't shown + plugins.setProperties(sbx); + + //only shown plugins get a chance to update visualisations + plugins.updateVisualisations(sbx); + } + + var nowData = data.filter(function(d) { + return d.type === 'sgv'; + }); + + if (inRetroMode()) { + var retroTime = brushExtent[1].getTime() - THIRTY_MINS_IN_MS; + + nowData = nowData.filter(function(d) { + return d.mills >= brushExtent[1].getTime() - (2 * THIRTY_MINS_IN_MS) && + d.mills <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; + }); + + // sometimes nowData contains duplicates. uniq it. + var lastDate = 0; + nowData = nowData.filter(function(d) { + var ok = lastDate + ONE_MIN_IN_MS < d.mills; + lastDate = d.mills; + return ok; + }); + + var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; + if (focusPoint) { + updateCurrentSGV(focusPoint); + } else { + currentBG.text('---'); + container.removeClass('urgent warning inrange'); + } + + updatePlugins(nowData, retroTime); + + $('#currentTime') + .text(formatTime(new Date(retroTime), true)) + .css('text-decoration','line-through'); + + updateTimeAgo(); + } else { + // if the brush comes back into the current time range then it should reset to the current time and sg + nowData = nowData.slice(nowData.length - 2, nowData.length); + nowDate = now; + + updateCurrentSGV(latestSGV); + updateClockDisplay(); + updateTimeAgo(); + updatePlugins(nowData, nowDate); + + } + + xScale.domain(brush.extent()); + + // get slice of data so that concatenation of predictions do not interfere with subsequent updates + var focusData = data.slice(); + + if (sbx.pluginBase.forecastPoints) { + focusData = focusData.concat(sbx.pluginBase.forecastPoints); + } + + // bind up the focus chart data to an array of circles + // selects all our data into data and uses date function to get current max date + var focusCircles = focus.selectAll('circle').data(focusData, dateFn); + + var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); + + var dotRadius = function(type) { + var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + if (type === 'mbg') { + radius *= 2; + } else if (type === 'forecast') { + radius = Math.min(3, radius - 1); + } else if (type === 'rawbg') { + radius = Math.min(2, radius - 1); + } + + return radius / focusRangeAdjustment; + }; + + function isDexcom(device) { + return device && device.toLowerCase().indexOf('dexcom') === 0; + } + + function prepareFocusCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { + if (!d) { + console.error('Bad data', d); + return xScale(new Date(0)); + } else if (!d.mills) { + console.error('Bad data, no mills', d); + return xScale(new Date(0)); + } else { + return xScale(new Date(d.mills)); + } + }) + .attr('cy', function (d) { + var scaled = sbx.scaleEntry(d); + if (isNaN(scaled)) { + badData.push(d); + return yScale(scaleBg(450)); + } else { + return yScale(scaled); + } + }) + .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) + .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) + .attr('stroke', function (d) { + return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); + }) + .attr('r', function (d) { return dotRadius(d.type); }); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareFocusCircles(focusCircles.enter().append('circle')) + .on('mouseover', function (d) { + if (d.type === 'sgv' || d.type === 'mbg') { + var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) + , rawbgValue = 0 + , noiseLabel = ''; + + if (d.type === 'sgv') { + if (rawbg.showRawBGs(d.mgdl, d.noise, cal, sbx)) { + rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); + } + noiseLabel = rawbg.noiseCodeToDisplay(d.mgdl, d.noise); + } + + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html('' + bgType + ' BG: ' + sbx.scaleEntry(d) + + (d.type === 'mbg' ? '
    Device: ' + d.device : '') + + (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + + (noiseLabel ? '
    Noise: ' + noiseLabel : '') + + '
    Time: ' + formatTime(new Date(d.mills))) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + } + }) + .on('mouseout', function (d) { + if (d.type === 'sgv' || d.type === 'mbg') { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + } + }); + + focusCircles.exit() + .remove(); + + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location + d3.selectAll('.path').remove(); + + // add treatment bubbles + // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) + var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; + + focus.selectAll('circle') + .data(treatments) + .each(function (d) { drawTreatment(d, bubbleScale, true) }); + + // transition open-top line to correct location + focus.select('.open-top') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .attr('x1', xScale2(brush.extent()[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[0])) + .attr('y2', prevChartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .attr('x1', xScale2(brush.extent()[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(brush.extent()[1])) + .attr('y2', prevChartHeight); + + focus.select('.now-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(nowDate)) + .attr('y1', yScale(scaleBg(36))) + .attr('x2', xScale(nowDate)) + .attr('y2', yScale(scaleBg(420))); + + context.select('.now-line') + .transition() + .attr('x1', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) + .attr('y2', yScale2(scaleBg(420))); + + // update x axis + focus.select('.x.axis') + .call(xAxis); + + // add clipping path so that data stays within axis + focusCircles.attr('clip-path', 'url(#clip)'); + + function prepareTreatCircles(sel) { + sel.attr('cx', function (d) { return xScale(new Date(d.mills)); }) + .attr('cy', function (d) { return yScale(sbx.scaleEntry(d)); }) + .attr('r', function () { return dotRadius('mbg'); }) + .attr('stroke-width', 2) + .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) + .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); + + return sel; + } + + //NOTE: treatments with insulin or carbs are drawn by drawTreatment() + // bind up the focus chart data to an array of circles + var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { + return !treatment.carbs && !treatment.insulin; + })); + + // if already existing then transition each circle to its new position + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareTreatCircles(treatCircles.enter().append('circle')) + .on('mouseover', function (d) { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + + (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + + (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + + (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + + (d.notes ? ''+translate('Notes')+': ' + d.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatCircles.attr('clip-path', 'url(#clip)'); + } + +// called for initial update and updates for resize + var updateChart = _.debounce(function debouncedUpdateChart(init) { + + if (documentHidden && !init) { + console.info('Document Hidden, not updating - ' + (new Date())); + return; + } + // get current data range + var dataRange = d3.extent(data, dateFn); + + // get the entire container height and width subtracting the padding + var chartWidth = (document.getElementById('chartContainer') + .getBoundingClientRect().width) - padding.left - padding.right; + + var chartHeight = (document.getElementById('chartContainer') + .getBoundingClientRect().height) - padding.top - padding.bottom; + + // get the height of each chart based on its container size ratio + focusHeight = chartHeight * .7; + contextHeight = chartHeight * .2; + + // get current brush extent + var currentBrushExtent = brush.extent(); + + // only redraw chart if chart size has changed + if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { + + prevChartWidth = chartWidth; + prevChartHeight = chartHeight; + + //set the width and height of the SVG element + charts.attr('width', chartWidth + padding.left + padding.right) + .attr('height', chartHeight + padding.top + padding.bottom); + + // ranges are based on the width and height available so reset + xScale.range([0, chartWidth]); + xScale2.range([0, chartWidth]); + yScale.range([focusHeight, 0]); + yScale2.range([chartHeight, chartHeight - contextHeight]); + + if (init) { + + // if first run then just display axis with no transition + focus.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focus.select('.y') + .attr('transform', 'translate(' + chartWidth + ',0)') + .call(yAxis); + + // if first run then just display axis with no transition + context.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + context.append('g') + .attr('class', 'x brush') + .call(d3.svg.brush().x(xScale2).on('brush', brushed)) + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // disable resizing of brush + d3.select('.x.brush').select('.background').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); + d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); + + // create a clipPath for when brushing + clip = charts.append('defs') + .append('clipPath') + .attr('id', 'clip') + .append('rect') + .attr('height', chartHeight) + .attr('width', chartWidth); + + // add a line that marks the current time + focus.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that shows the high bg threshold + focus.append('line') + .attr('class', 'target-top-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'target-bottom-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + focus.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .style('stroke-dasharray', ('1, 6')) + .attr('stroke', '#777'); + + // add a y-axis line that opens up the brush extent from the context to the focus + focus.append('line') + .attr('class', 'open-top') + .attr('stroke', 'black') + .attr('stroke-width', 2); + + // add a x-axis line that closes the the brush container on left side + focus.append('line') + .attr('class', 'open-left') + .attr('stroke', 'white'); + + // add a x-axis line that closes the the brush container on right side + focus.append('line') + .attr('class', 'open-right') + .attr('stroke', 'white'); + + // add a line that marks the current time + context.append('line') + .attr('class', 'now-line') + .attr('x1', xScale(new Date(now))) + .attr('y1', yScale2(scaleBg(36))) + .attr('x2', xScale(new Date(now))) + .attr('y2', yScale2(scaleBg(420))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the high bg threshold + context.append('line') + .attr('class', 'high-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + // add a y-axis line that shows the low bg threshold + context.append('line') + .attr('class', 'low-line') + .attr('x1', xScale(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .style('stroke-dasharray', ('3, 3')) + .attr('stroke', 'grey'); + + } else { + + // for subsequent updates use a transition to animate the axis to the new position + var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); + + focusTransition.select('.x') + .attr('transform', 'translate(0,' + focusHeight + ')') + .call(xAxis); + + focusTransition.select('.y') + .attr('transform', 'translate(' + chartWidth + ', 0)') + .call(yAxis); + + var contextTransition = context.transition().duration(UPDATE_TRANS_MS); + + contextTransition.select('.x') + .attr('transform', 'translate(0,' + chartHeight + ')') + .call(xAxis2); + + if (clip) { + // reset clip to new dimensions + clip.transition() + .attr('width', chartWidth) + .attr('height', chartHeight); + } + + // reset brush location + context.select('.x.brush') + .selectAll('rect') + .attr('y', focusHeight) + .attr('height', chartHeight - focusHeight); + + // clear current brush + d3.select('.brush').call(brush.clear()); + + // redraw old brush with new dimensions + d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); + + // transition lines to correct location + focus.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); + + focus.select('.target-top-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); + + focus.select('.target-bottom-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); + + focus.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) + .attr('x2', xScale(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); + + // transition open-top line to correct location + focus.select('.open-top') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', yScale(scaleBg(30))) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', yScale(scaleBg(30))); + + // transition open-left line to correct location + focus.select('.open-left') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[0])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[0])) + .attr('y2', chartHeight); + + // transition open-right line to correct location + focus.select('.open-right') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(currentBrushExtent[1])) + .attr('y1', focusHeight) + .attr('x2', xScale2(currentBrushExtent[1])) + .attr('y2', chartHeight); + + // transition high line to correct location + context.select('.high-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); + + // transition low line to correct location + context.select('.low-line') + .transition() + .duration(UPDATE_TRANS_MS) + .attr('x1', xScale2(dataRange[0])) + .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) + .attr('x2', xScale2(dataRange[1])) + .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); + } + } + + // update domain + xScale2.domain(dataRange); + + // only if a user brush is not active, update brush and focus chart with recent data + // else, just transition brush + var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); + if (!brushInProgress) { + updateBrush + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); + brushed(true); + } else { + updateBrush + .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); + brushed(true); + } + + // bind up the context chart data to an array of circles + var contextCircles = context.selectAll('circle') + .data(data); + + function prepareContextCircles(sel) { + var badData = []; + sel.attr('cx', function (d) { return xScale2(new Date(d.mills)); }) + .attr('cy', function (d) { + var scaled = sbx.scaleEntry(d); + if (isNaN(scaled)) { + badData.push(d); + return yScale2(scaleBg(450)); + } else { + return yScale2(scaled); + } + }) + .attr('fill', function (d) { return d.color; }) + .style('opacity', function (d) { return highlightBrushPoints(d) }) + .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) + .attr('stroke', function ( ) { return 'white'; }) + .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); + + if (badData.length > 0) { + console.warn('Bad Data: isNaN(sgv)', badData); + } + + return sel; + } + + // if already existing then transition each circle to its new position + prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); + + // if new circle then just display + prepareContextCircles(contextCircles.enter().append('circle')); + + contextCircles.exit() + .remove(); + + // update x axis domain + context.select('.x') + .call(xAxis2); + + }, DEBOUNCE_MS); + + function sgvToColor(sgv) { + var color = 'grey'; + + if (browserSettings.theme === 'colors') { + if (sgv > serverSettings.thresholds.bg_high) { + color = 'red'; + } else if (sgv > serverSettings.thresholds.bg_target_top) { + color = 'yellow'; + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { + color = '#4cff00'; + } else if (sgv < serverSettings.thresholds.bg_low) { + color = 'red'; + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { + color = 'yellow'; + } + } + + return color; + } + + function sgvToColoredRange(sgv) { + var range = ''; + + if (browserSettings.theme === 'colors') { + if (sgv > serverSettings.thresholds.bg_high) { + range = 'urgent'; + } else if (sgv > serverSettings.thresholds.bg_target_top) { + range = 'warning'; + } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { + range = 'inrange'; + } else if (sgv < serverSettings.thresholds.bg_low) { + range = 'urgent'; + } else if (sgv < serverSettings.thresholds.bg_target_bottom) { + range = 'warning'; + } + } + + return range; + } + + + function generateAlarm(file, reason) { + alarmInProgress = true; + alarmMessage = reason && reason.title; + var selector = '.audio.alarms audio.' + file; + + if (!alarmingNow()) { + d3.select(selector).each(function () { + var audio = this; + playAlarm(audio); + $(this).addClass('playing'); + }); + } + + container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); + + var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); + updateTitle(skipPageTitle); + } + + function playAlarm(audio) { + // ?mute=true disables alarms to testers. + if (querystring.mute !== 'true') { + audio.play(); + } else { + showNotification('Alarm was muted (?mute=true)'); + } + } + + function stopAlarm(isClient, silenceTime) { + alarmInProgress = false; + alarmMessage = null; + container.removeClass('urgent warning'); + d3.selectAll('audio.playing').each(function () { + var audio = this; + audio.pause(); + $(this).removeClass('playing'); + }); + + closeNotification(); + container.removeClass('alarming'); + + updateTitle(); + + // only emit ack if client invoke by button press + if (isClient) { + if (isTimeAgoAlarmType(currentAlarmType)) { + container.removeClass('alarming-timeago'); + var alarm = getClientAlarm(currentAlarmType); + alarm.lastAckTime = Date.now(); + alarm.silenceTime = silenceTime; + } + socket.emit('ack', currentAlarmType || 'alarm', silenceTime); + } + + brushed(false); + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//draw a compact visualization of a treatment (carbs, insulin) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function drawTreatment(treatment, scale, showValues) { + + if (!treatment.carbs && !treatment.insulin) { return; } + + // don't render the treatment if it's not visible + if (Math.abs(xScale(new Date(treatment.mills))) > window.innerWidth) { return; } + + var CR = treatment.CR || 20; + var carbs = treatment.carbs || CR; + var insulin = treatment.insulin || 1; + + var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, + R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, + R3 = R2 + 8 / scale; + + if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { + console.warn('Bad Data: Found isNaN value in treatment', treatment); + return; + } + + var arc_data = [ + { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, + { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, + { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } + ]; + + arc_data[0].outlineOnly = !treatment.carbs; + arc_data[2].outlineOnly = !treatment.insulin; + + if (treatment.carbs > 0) { + arc_data[1].element = Math.round(treatment.carbs) + ' g'; + } + + if (treatment.insulin > 0) { + arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; + } + + var arc = d3.svg.arc() + .innerRadius(function (d) { return 5 * d.inner; }) + .outerRadius(function (d) { return 5 * d.outer; }) + .endAngle(function (d) { return d.start; }) + .startAngle(function (d) { return d.end; }); + + var treatmentDots = focus.selectAll('treatment-dot') + .data(arc_data) + .enter() + .append('g') + .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') + .on('mouseover', function () { + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + + (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + + (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + + (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + + (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + + (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') + ) + .style('left', (d3.event.pageX) + 'px') + .style('top', (d3.event.pageY + 15) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + + treatmentDots.append('path') + .attr('class', 'path') + .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) + .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) + .attr('stroke', function (d) { return d.color; }) + .attr('id', function (d, i) { return 's' + i; }) + .attr('d', arc); + + + // labels for carbs and insulin + if (showValues) { + var label = treatmentDots.append('g') + .attr('class', 'path') + .attr('id', 'label') + .style('fill', 'white'); + label.append('text') + .style('font-size', 40 / scale) + .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') + .attr('text-anchor', 'middle') + .attr('dy', '.35em') + .attr('transform', function (d) { + d.outerRadius = d.outerRadius * 2.1; + d.innerRadius = d.outerRadius * 2.1; + return 'translate(' + arc.centroid(d) + ')'; + }) + .text(function (d) { return d.element; }); + } + } + + function updateClock() { + updateClockDisplay(); + var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; + setTimeout(updateClock,interval); + + updateTimeAgo(); + + // Dim the screen by reducing the opacity when at nighttime + if (browserSettings.nightMode) { + var dateTime = new Date(); + if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + $('body').css({ 'opacity': opacity.NIGHT }); + } else { + $('body').css({ 'opacity': opacity.DAY }); + } + } + } + + function updateClockDisplay() { + if (inRetroMode()) { + return; + } + now = Date.now(); + $('#currentTime').text(formatTime(new Date(now), true)).css('text-decoration', ''); + } + + function getClientAlarm(type) { + var alarm = clientAlarms[type]; + if (!alarm) { + alarm = { type: type }; + clientAlarms[type] = alarm; + } + return alarm; + } + + function isTimeAgoAlarmType(alarmType) { + return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; + } + + function checkTimeAgoAlarm(ago) { + var level = ago.status + , alarm = getClientAlarm(level + 'TimeAgo'); + + if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { + currentAlarmType = alarm.type; + console.info('generating timeAgoAlarm', alarm.type); + container.addClass('alarming-timeago'); + var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; + if (level === 'warn') { + generateAlarm(alarmSound, message); + } else { + generateAlarm(urgentAlarmSound, message); + } + } + } + + function updateTimeAgo() { + var lastEntry = $('#lastEntry') + , time = latestSGV ? latestSGV.mills : -1 + , ago = timeAgo(time, browserSettings) + , retroMode = inRetroMode(); + + lastEntry.removeClass('current warn urgent'); + lastEntry.addClass(ago.status); + + if (ago.status !== 'current') { + updateTitle(); + } + + if ( + (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { + checkTimeAgoAlarm(ago); + } + + container.toggleClass('alarming-timeago', ago.status !== 'current'); + + if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { + stopAlarm(true, ONE_MIN_IN_MS); + } + + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); + } else { + lastEntry.find('em').show().text(ago.value); + } + + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } + } + + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); + + // Tick Values + if (browserSettings.units === 'mmol') { + tickValues = [ + 2.0 + , Math.round(scaleBg(serverSettings.thresholds.bg_low)) + , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) + , 6.0 + , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) + , Math.round(scaleBg(serverSettings.thresholds.bg_high)) + , 22.0 + ]; + } else { + tickValues = [ + 40 + , serverSettings.thresholds.bg_low + , serverSettings.thresholds.bg_target_bottom + , 120 + , serverSettings.thresholds.bg_target_top + , serverSettings.thresholds.bg_high + , 400 + ]; + } + + futureOpacity = d3.scale.linear( ) + .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) + .range([0.8, 0.1]); + + // create svg and g to contain the chart contents + charts = d3.select('#chartContainer').append('svg') + .append('g') + .attr('class', 'chartContainer') + .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + + focus = charts.append('g'); + + // create the x axis container + focus.append('g') + .attr('class', 'x axis'); + + // create the y axis container + focus.append('g') + .attr('class', 'y axis'); + + context = charts.append('g'); + + // create the x axis container + context.append('g') + .attr('class', 'x axis'); + + // create the y axis container + context.append('g') + .attr('class', 'y axis'); + + //updateChart is _.debounce'd + function refreshChart(updateToNow) { + if (updateToNow) { + updateBrushToNow(); + } + updateChart(false); + } + + function visibilityChanged() { + var prevHidden = documentHidden; + documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); + + if (prevHidden && !documentHidden) { + console.info('Document now visible, updating - ' + (new Date())); + refreshChart(true); + } + } + + window.onresize = refreshChart; + + document.addEventListener('webkitvisibilitychange', visibilityChanged); + + + updateClock(); + + function Dropdown(el) { + this.ddmenuitem = 0; + + this.$el = $(el); + var that = this; + + $(document).click(function() { that.close(); }); + } + Dropdown.prototype.close = function () { + if (this.ddmenuitem) { + this.ddmenuitem.css('visibility', 'hidden'); + this.ddmenuitem = 0; + } + }; + Dropdown.prototype.open = function (e) { + this.close(); + this.ddmenuitem = $(this.$el).css('visibility', 'visible'); + e.stopPropagation(); + }; + + var silenceDropdown = new Dropdown('.dropdown-menu'); + + $('.bgButton').click(function (e) { + if (alarmingNow()) { + silenceDropdown.open(e); + } + }); + + $('#silenceBtn').find('a').click(function (e) { + stopAlarm(true, $(this).data('snooze-time')); + e.preventDefault(); + }); + + $('.focus-range li').click(function(e) { + var li = $(e.target); + $('.focus-range li').removeClass('selected'); + li.addClass('selected'); + var hours = Number(li.data('hours')); + foucusRangeMS = hours * 60 * 60 * 1000; + refreshChart(); + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Client-side code to connect to server and handle incoming data + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket = io.connect(); + + function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { + + function nsArrayDiff(oldArray, newArray) { + var seen = {}; + var l = oldArray.length; + for (var i = 0; i < l; i++) { seen[oldArray[i].mills] = true } + var result = []; + l = newArray.length; + for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].mills)) { result.push(newArray[j]); console.log('delta data found'); } } + return result; + } + + // If there was no delta data, just return the original data + if (!receivedDataArray) { + return cachedDataArray; + } + + // If this is not a delta update, replace all data + if (!isDelta) { + return receivedDataArray; + } + + // If this is delta, calculate the difference, merge and sort + var diff = nsArrayDiff(cachedDataArray,receivedDataArray); + return cachedDataArray.concat(diff).sort(function(a, b) { + return a.mills - b.mills; + }); + } + + socket.on('dataUpdate', function receivedSGV(d) { + + if (!d) { + return; + } + + // Calculate the diff to existing data and replace as needed + + SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); + MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); + treatments = mergeDataUpdate(d.delta,treatments, d.treatments); + + if (d.profiles) { + Nightscout.profile.loadData(d.profiles); + } + + if (d.cals) { cal = d.cals[d.cals.length-1]; } + if (d.devicestatus) { devicestatusData = d.devicestatus; } + + // Do some reporting on the console + console.log('Total SGV data size', SGVdata.length); + console.log('Total treatment data size', treatments.length); + + // Post processing after data is in + + if (d.sgvs) { + // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) + latestUpdateTime = Date.now(); + latestSGV = SGVdata[SGVdata.length - 1]; + prevSGV = SGVdata[SGVdata.length - 2]; + } + + var temp1 = [ ]; + if (cal && rawbg.isEnabled(sbx)) { + temp1 = SGVdata.map(function (entry) { + var rawbgValue = rawbg.showRawBGs(entry.mgdl, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; + if (rawbgValue > 0) { + return { mills: entry.mills - 2000, mgdl: rawbgValue, color: 'white', type: 'rawbg' }; + } else { + return null; + } + }).filter(function(entry) { return entry !== null; }); + } + var temp2 = SGVdata.map(function (obj) { + return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; + }); + data = []; + data = data.concat(temp1, temp2); + + addPlaceholderPoints(); + + data = data.concat(MBGdata.map(function (obj) { return { mills: obj.mills, mgdl: obj.mgdl, color: 'red', type: 'mbg', device: obj.device } })); + + data.forEach(function (d) { + if (d.mgdl < 39) { d.color = 'transparent'; } + }); + + updateTitle(); + if (!isInitialData) { + isInitialData = true; + initializeCharts(); + } + else { + updateChart(false); + } + + }); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Alarms and Text handling + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + socket.on('connect', function () { + console.log('Client connected to server.'); + }); + + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a HIGH we can only check if it's >= the bottom of the target + function isAlarmForHigh() { + return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; + } + + //with predicted alarms, latestSGV may still be in target so to see if the alarm + // is for a LOW we can only check if it's <= the top of the target + function isAlarmForLow() { + return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; + } + + socket.on('alarm', function (notify) { + console.info('alarm received from server'); + console.log('notify:',notify); + var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); + if (enabled) { + console.log('Alarm raised!'); + currentAlarmType = 'alarm'; + generateAlarm(alarmSound,notify); + } else { + console.info('alarm was disabled locally', latestSGV.mgdl, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('urgent_alarm', function (notify) { + console.info('urgent alarm received from server'); + console.log('notify:',notify); + + var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); + if (enabled) { + console.log('Urgent alarm raised!'); + currentAlarmType = 'urgent_alarm'; + generateAlarm(urgentAlarmSound,notify); + } else { + console.info('urgent alarm was disabled locally', latestSGV.mgdl, browserSettings); + } + brushInProgress = false; + updateChart(false); + }); + socket.on('clear_alarm', function () { + if (alarmInProgress) { + console.log('clearing alarm'); + stopAlarm(); + } + }); + + + $('#testAlarms').click(function(event) { + d3.selectAll('.audio.alarms audio').each(function () { + var audio = this; + playAlarm(audio); + setTimeout(function() { + audio.pause(); + }, 4000); + }); + event.preventDefault(); + }); + + if (serverSettings.enabledOptions.indexOf('ar2') < 0) { + serverSettings.enabledOptions += ' ar2'; + } + + $('.appName').text(serverSettings.name); + $('.version').text(serverSettings.version); + $('.head').text(serverSettings.head); + if (serverSettings.apiEnabled) { + $('.serverSettings').show(); + } + $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); + plugins.init(serverSettings); + sbx = sandbox.clientInit(serverSettings, browserSettings, Date.now()); + $('.container').toggleClass('has-minor-pills', plugins.hasShownType('pill-minor', browserSettings)); + + function showLocalstorageError() { + var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; + $('.browserSettings').html('Settings'+msg+''); + $('#save').hide(); + } + + function getBrowserSettings() { + var json = {}; + var storage = $.localStorage; + + function scaleBg(bg) { + if (json.units === 'mmol') { + return units.mgdlToMMOL(bg); + } else { + return bg; + } + } + + function appendThresholdValue(threshold) { + return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; + } + + try { + json = { + 'units': storage.get('units'), + 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), + 'alarmHigh': storage.get('alarmHigh'), + 'alarmLow': storage.get('alarmLow'), + 'alarmUrgentLow': storage.get('alarmUrgentLow'), + 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), + 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), + 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), + 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), + 'nightMode': storage.get('nightMode'), + 'showRawbg': storage.get('showRawbg'), + 'customTitle': storage.get('customTitle'), + 'theme': storage.get('theme'), + 'timeFormat': storage.get('timeFormat'), + 'showPlugins': storage.get('showPlugins') + }; + + // Default browser units to server units if undefined. + json.units = setDefault(json.units, serverSettings.units); + if (json.units === 'mmol') { + $('#mmol-browser').prop('checked', true); + } else { + $('#mgdl-browser').prop('checked', true); + } + + json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); + json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); + json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); + json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); + json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); + json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); + $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); + $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); + $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); + $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); + + json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); + $('#nightmode-browser').prop('checked', json.nightMode); + + if (rawBGsEnabled()) { + $('#show-rawbg-option').show(); + json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); + $('#show-rawbg-' + json.showRawbg).prop('checked', true); + } else { + json.showRawbg = 'never'; + $('#show-rawbg-option').hide(); + } + + json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); + $('h1.customTitle').text(json.customTitle); + $('input#customTitle').prop('value', json.customTitle); + + json.theme = setDefault(json.theme, serverSettings.defaults.theme); + if (json.theme === 'colors') { + $('#theme-colors-browser').prop('checked', true); + } else { + $('#theme-default-browser').prop('checked', true); + } + + json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); + + if (json.timeFormat === '24') { + $('#24-browser').prop('checked', true); + } else { + $('#12-browser').prop('checked', true); + } + + json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || plugins.enabledPluginNames()); + var showPluginsSettings = $('#show-plugins'); + plugins.eachEnabledPlugin(function each(plugin) { + if (plugins.specialPlugins.indexOf(plugin.name) > -1) { + //ignore these, they are always on for now + } else { + var id = 'plugin-' + plugin.name; + var dd = $('
    '); + showPluginsSettings.append(dd); + dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); + } + }); + + + } catch(err) { + console.error(err); + showLocalstorageError(); + } + + return json; + } +} + +module.exports = init; \ No newline at end of file diff --git a/package.json b/package.json index ef3cc1796e2..ab60c6a9494 100644 --- a/package.json +++ b/package.json @@ -69,13 +69,15 @@ "pushover-notifications": "0.2.0", "request": "^2.58.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "socket.io": "^1.3.5" + "socket.io": "^1.3.5", + "d3": "^3.5.6" }, "devDependencies": { "istanbul": "~0.3.5", "mocha": "~1.20.1", "should": "~4.0.4", "supertest": "~0.13.0", - "jsdom": "^3.1.2" + "jsdom": "^3.1.2", + "benv": "^1.1.0" } } diff --git a/static/js/client.js b/static/js/client.js index 76059967aa3..e749fbd132e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1,1574 +1,3 @@ -//TODO: clean up -var browserSettings = {}, browserStorage = $.localStorage; +'use strict'; -(function () { - 'use strict'; - - var BRUSH_TIMEOUT = 300000 // 5 minutes in ms - , DEBOUNCE_MS = 10 - , TOOLTIP_TRANS_MS = 200 // milliseconds - , UPDATE_TRANS_MS = 750 // milliseconds - , ONE_MIN_IN_MS = 60000 - , FIVE_MINS_IN_MS = 300000 - , THREE_HOURS_MS = 3 * 60 * 60 * 1000 - , TWENTY_FIVE_MINS_IN_MS = 1500000 - , THIRTY_MINS_IN_MS = 1800000 - , SIXTY_MINS_IN_MS = 3600000 - , FORMAT_TIME_12 = '%-I:%M %p' - , FORMAT_TIME_12_COMAPCT = '%-I:%M' - , FORMAT_TIME_24 = '%H:%M%' - , FORMAT_TIME_12_SCALE = '%-I %p' - , FORMAT_TIME_24_SCALE = '%H' - , WIDTH_SMALL_DOTS = 400 - , WIDTH_BIG_DOTS = 800; - - var socket - , isInitialData = false - , SGVdata = [] - , MBGdata = [] - , latestSGV - , latestUpdateTime - , prevSGV - , treatments - , profile - , cal - , devicestatusData - , padding = { top: 0, right: 10, bottom: 30, left: 10 } - , opacity = {current: 1, DAY: 1, NIGHT: 0.5} - , now = Date.now() - , data = [] - , foucusRangeMS = THREE_HOURS_MS - , clientAlarms = {} - , alarmInProgress = false - , alarmMessage - , currentAlarmType = null - , alarmSound = 'alarm.mp3' - , urgentAlarmSound = 'alarm2.mp3'; - - var sbx - , rawbg = Nightscout.plugins('rawbg') - , delta = Nightscout.plugins('delta') - , direction = Nightscout.plugins('direction') - , errorcodes = Nightscout.plugins('errorcodes') - , translate = Nightscout.language.translate - , timeAgo = Nightscout.utils.timeAgo; - - var jqWindow - , tooltip - , tickValues - , charts - , futureOpacity - , focus - , context - , xScale, xScale2, yScale, yScale2 - , xAxis, yAxis, xAxis2, yAxis2 - , prevChartWidth = 0 - , prevChartHeight = 0 - , focusHeight - , contextHeight - , dateFn = function (d) { return new Date(d.mills) } - , documentHidden = false - , brush - , brushTimer - , brushInProgress = false - , clip; - - var container = $('.container') - , bgStatus = $('.bgStatus') - , currentBG = $('.bgStatus .currentBG') - , majorPills = $('.bgStatus .majorPills') - , minorPills = $('.bgStatus .minorPills') - , statusPills = $('.status .statusPills') - ; - - Nightscout.language.set(serverSettings.defaults.language).DOMtranslate(); - - function formatTime(time, compact) { - var timeFormat = getTimeFormat(false, compact); - time = d3.time.format(timeFormat)(time); - if (!isTimeFormat24()) { - time = time.toLowerCase(); - } - return time; - } - - function isTimeFormat24() { - return browserSettings && browserSettings.timeFormat && parseInt(browserSettings.timeFormat) === 24; - } - - function getTimeFormat(isForScale, compact) { - var timeFormat = FORMAT_TIME_12; - if (isTimeFormat24()) { - timeFormat = isForScale ? FORMAT_TIME_24_SCALE : FORMAT_TIME_24; - } else { - timeFormat = isForScale ? FORMAT_TIME_12_SCALE : (compact ? FORMAT_TIME_12_COMAPCT : FORMAT_TIME_12); - } - - return timeFormat; - } - - // lixgbg: Convert mg/dL BG value to metric mmol - function scaleBg(bg) { - if (browserSettings.units === 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - } - - function generateTitle() { - - function s(value, sep) { return value ? value + ' ' : sep || ''; } - - var bg_title = ''; - - var time = latestSGV ? latestSGV.mills : (prevSGV ? prevSGV.mills : -1) - , ago = timeAgo(time, browserSettings); - - if (browserSettings.customTitle) { - $('.customTitle').text(browserSettings.customTitle); - } - - if (ago && ago.status !== 'current') { - bg_title = s(ago.value) + s(ago.label, ' - ') + bg_title; - } else if (latestSGV) { - var currentMgdl = latestSGV.mgdl; - - if (currentMgdl < 39) { - bg_title = s(errorcodes.toDisplay(currentMgdl), ' - ') + bg_title; - } else { - var deltaDisplay = delta.calc(prevSGV, latestSGV, sbx).display; - bg_title = s(scaleBg(currentMgdl)) + s(deltaDisplay) + s(direction.info(latestSGV).label) + bg_title; - } - } - return bg_title; - } - - function updateTitle(skipPageTitle) { - - var bg_title = browserSettings.customTitle || ''; - - if (alarmMessage && alarmInProgress) { - bg_title = alarmMessage + ': ' + generateTitle(); - $('.customTitle').text(alarmMessage); - } else { - bg_title = generateTitle(); - } - - if (!skipPageTitle) { - $(document).attr('title', bg_title); - } - } - - // initial setup of chart when data is first made available - function initializeCharts() { - - // define the parts of the axis that aren't dependent on width or height - xScale = d3.time.scale() - .domain(d3.extent(data, dateFn)); - - yScale = d3.scale.log() - .domain([scaleBg(30), scaleBg(510)]); - - xScale2 = d3.time.scale() - .domain(d3.extent(data, dateFn)); - - yScale2 = d3.scale.log() - .domain([scaleBg(36), scaleBg(420)]); - - var tickFormat = d3.time.format.multi( [ - ['.%L', function(d) { return d.getMilliseconds(); }], - [':%S', function(d) { return d.getSeconds(); }], - ['%I:%M', function(d) { return d.getMinutes(); }], - [isTimeFormat24() ? '%H:%M' : '%-I %p', function(d) { return d.getHours(); }], - ['%a %d', function(d) { return d.getDay() && d.getDate() !== 1; }], - ['%b %d', function(d) { return d.getDate() !== 1; }], - ['%B', function(d) { return d.getMonth(); }], - ['%Y', function() { return true; }] - ]); - - xAxis = d3.svg.axis() - .scale(xScale) - .tickFormat(tickFormat) - .ticks(4) - .orient('bottom'); - - yAxis = d3.svg.axis() - .scale(yScale) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('left'); - - xAxis2 = d3.svg.axis() - .scale(xScale2) - .tickFormat(tickFormat) - .ticks(6) - .orient('bottom'); - - yAxis2 = d3.svg.axis() - .scale(yScale2) - .tickFormat(d3.format('d')) - .tickValues(tickValues) - .orient('right'); - - // setup a brush - brush = d3.svg.brush() - .x(xScale2) - .on('brushstart', brushStarted) - .on('brush', brushed) - .on('brushend', brushEnded); - - updateChart(true); - } - - // get the desired opacity for context chart based on the brush extent - function highlightBrushPoints(data) { - if (data.mills >= brush.extent()[0].getTime() && data.mills <= brush.extent()[1].getTime()) { - return futureOpacity(data.mills - latestSGV.mills); - } else { - return 0.5; - } - } - - function addPlaceholderPoints () { - // TODO: This is a kludge to advance the time as data becomes stale by making old predictor clear (using color = 'none') - // This shouldn't need to be generated and can be fixed by using xScale.domain([x0,x1]) function with - // 2 days before now as x0 and 30 minutes from now for x1 for context plot, but this will be - // required to happen when 'now' event is sent from websocket.js every minute. When fixed, - // remove this code and all references to `type: 'server-forecast'` - var last = _.last(data); - var lastTime = last && last.mills; - if (!lastTime) { - console.error('Bad Data, last point has no mills', last); - lastTime = Date.now(); - } - - var n = Math.ceil(12 * (1 / 2 + (now - lastTime) / SIXTY_MINS_IN_MS)) + 1; - for (var i = 1; i <= n; i++) { - data.push({ - mills: lastTime + (i * FIVE_MINS_IN_MS), mgdl: 100, color: 'none', type: 'server-forecast' - }); - } - } - - // clears the current user brush and resets to the current real time data - function updateBrushToNow(skipBrushing) { - - // get current time range - var dataRange = d3.extent(data, dateFn); - - // update brush and focus chart with recent data - d3.select('.brush') - .transition() - .duration(UPDATE_TRANS_MS) - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - - addPlaceholderPoints(); - - if (!skipBrushing) { - brushed(true); - - // clear user brush tracking - brushInProgress = false; - } - } - - function brushStarted() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', 1); - } - - function brushEnded() { - // update the opacity of the context data points to brush extent - context.selectAll('circle') - .data(data) - .style('opacity', function (d) { return highlightBrushPoints(d) }); - } - - function alarmingNow() { - return container.hasClass('alarming'); - } - - function inRetroMode() { - if (!brush) { - return false; - } - - var time = brush.extent()[1].getTime(); - - return !alarmingNow() && time - TWENTY_FIVE_MINS_IN_MS < now; - } - - function brushed(skipTimer) { - - if (!skipTimer) { - // set a timer to reset focus chart to real-time data - clearTimeout(brushTimer); - brushTimer = setTimeout(updateBrushToNow, BRUSH_TIMEOUT); - brushInProgress = true; - } - - var brushExtent = brush.extent(); - - // ensure that brush extent is fixed at 3.5 hours - if (brushExtent[1].getTime() - brushExtent[0].getTime() !== foucusRangeMS) { - // ensure that brush updating is with the time range - if (brushExtent[0].getTime() + foucusRangeMS > d3.extent(data, dateFn)[1].getTime()) { - brushExtent[0] = new Date(brushExtent[1].getTime() - foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } else { - brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); - d3.select('.brush') - .call(brush.extent([brushExtent[0], brushExtent[1]])); - } - } - - var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - - function updateCurrentSGV(entry) { - var value = entry.mgdl - , ago = timeAgo(entry.mills, browserSettings) - , isCurrent = ago.status === 'current'; - - if (value === 9) { - currentBG.text(''); - } else if (value < 39) { - currentBG.html(errorcodes.toDisplay(value)); - } else if (value < 40) { - currentBG.text('LOW'); - } else if (value > 400) { - currentBG.text('HIGH'); - } else { - currentBG.text(scaleBg(value)); - } - - bgStatus.toggleClass('current', alarmingNow() || (isCurrent && !inRetroMode())); - if (!alarmingNow()) { - container.removeClass('urgent warning inrange'); - if (isCurrent && !inRetroMode()) { - container.addClass(sgvToColoredRange(value)); - } - } - - currentBG.toggleClass('icon-hourglass', value === 9); - currentBG.toggleClass('error-code', value < 39); - currentBG.toggleClass('bg-limit', value === 39 || value > 400); - - $('.container').removeClass('loading'); - - } - - function updatePlugins (sgvs, time) { - - var pluginBase = Nightscout.plugins.base(majorPills, minorPills, statusPills, bgStatus, tooltip); - - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, time, pluginBase, { - sgvs: sgvs - , cals: [cal] - , treatments: treatments - , profile: Nightscout.profile - , uploaderBattery: devicestatusData && devicestatusData.uploaderBattery - }); - - //all enabled plugins get a chance to set properties, even if they aren't shown - Nightscout.plugins.setProperties(sbx); - - //only shown plugins get a chance to update visualisations - Nightscout.plugins.updateVisualisations(sbx); - } - - var nowData = data.filter(function(d) { - return d.type === 'sgv'; - }); - - if (inRetroMode()) { - var retroTime = brushExtent[1].getTime() - THIRTY_MINS_IN_MS; - - nowData = nowData.filter(function(d) { - return d.mills >= brushExtent[1].getTime() - (2 * THIRTY_MINS_IN_MS) && - d.mills <= brushExtent[1].getTime() - TWENTY_FIVE_MINS_IN_MS; - }); - - // sometimes nowData contains duplicates. uniq it. - var lastDate = 0; - nowData = nowData.filter(function(d) { - var ok = lastDate + ONE_MIN_IN_MS < d.mills; - lastDate = d.mills; - return ok; - }); - - var focusPoint = nowData.length > 0 ? nowData[nowData.length - 1] : null; - if (focusPoint) { - updateCurrentSGV(focusPoint); - } else { - currentBG.text('---'); - container.removeClass('urgent warning inrange'); - } - - updatePlugins(nowData, retroTime); - - $('#currentTime') - .text(formatTime(new Date(retroTime), true)) - .css('text-decoration','line-through'); - - updateTimeAgo(); - } else { - // if the brush comes back into the current time range then it should reset to the current time and sg - nowData = nowData.slice(nowData.length - 2, nowData.length); - nowDate = now; - - updateCurrentSGV(latestSGV); - updateClockDisplay(); - updateTimeAgo(); - updatePlugins(nowData, nowDate); - - } - - xScale.domain(brush.extent()); - - // get slice of data so that concatenation of predictions do not interfere with subsequent updates - var focusData = data.slice(); - - if (sbx.pluginBase.forecastPoints) { - focusData = focusData.concat(sbx.pluginBase.forecastPoints); - } - - // bind up the focus chart data to an array of circles - // selects all our data into data and uses date function to get current max date - var focusCircles = focus.selectAll('circle').data(focusData, dateFn); - - var focusRangeAdjustment = foucusRangeMS === THREE_HOURS_MS ? 1 : 1 + ((foucusRangeMS - THREE_HOURS_MS) / THREE_HOURS_MS / 8); - - var dotRadius = function(type) { - var radius = prevChartWidth > WIDTH_BIG_DOTS ? 4 : (prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); - if (type === 'mbg') { - radius *= 2; - } else if (type === 'forecast') { - radius = Math.min(3, radius - 1); - } else if (type === 'rawbg') { - radius = Math.min(2, radius - 1); - } - - return radius / focusRangeAdjustment; - }; - - function isDexcom(device) { - return device && device.toLowerCase().indexOf('dexcom') === 0; - } - - function prepareFocusCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { - if (!d) { - console.error('Bad data', d); - return xScale(new Date(0)); - } else if (!d.mills) { - console.error('Bad data, no mills', d); - return xScale(new Date(0)); - } else { - return xScale(new Date(d.mills)); - } - }) - .attr('cy', function (d) { - var scaled = sbx.scaleEntry(d); - if (isNaN(scaled)) { - badData.push(d); - return yScale(scaleBg(450)); - } else { - return yScale(scaled); - } - }) - .attr('fill', function (d) { return d.type === 'forecast' ? 'none' : d.color; }) - .attr('opacity', function (d) { return futureOpacity(d.mills - latestSGV.mills); }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : d.type === 'forecast' ? 1 : 0; }) - .attr('stroke', function (d) { - return (isDexcom(d.device) ? 'white' : d.type === 'forecast' ? d.color : '#0099ff'); - }) - .attr('r', function (d) { return dotRadius(d.type); }); - - if (badData.length > 0) { - console.warn('Bad Data: isNaN(sgv)', badData); - } - - return sel; - } - - // if already existing then transition each circle to its new position - prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareFocusCircles(focusCircles.enter().append('circle')) - .on('mouseover', function (d) { - if (d.type === 'sgv' || d.type === 'mbg') { - var bgType = (d.type === 'sgv' ? 'CGM' : (isDexcom(d.device) ? 'Calibration' : 'Meter')) - , rawbgValue = 0 - , noiseLabel = ''; - - if (d.type === 'sgv') { - if (rawbg.showRawBGs(d.mgdl, d.noise, cal, sbx)) { - rawbgValue = scaleBg(rawbg.calc(d, cal, sbx)); - } - noiseLabel = rawbg.noiseCodeToDisplay(d.mgdl, d.noise); - } - - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html('' + bgType + ' BG: ' + sbx.scaleEntry(d) + - (d.type === 'mbg' ? '
    Device: ' + d.device : '') + - (rawbgValue ? '
    Raw BG: ' + rawbgValue : '') + - (noiseLabel ? '
    Noise: ' + noiseLabel : '') + - '
    Time: ' + formatTime(new Date(d.mills))) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - } - }) - .on('mouseout', function (d) { - if (d.type === 'sgv' || d.type === 'mbg') { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - } - }); - - focusCircles.exit() - .remove(); - - // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location - d3.selectAll('.path').remove(); - - // add treatment bubbles - // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; - - focus.selectAll('circle') - .data(treatments) - .each(function (d) { drawTreatment(d, bubbleScale, true) }); - - // transition open-top line to correct location - focus.select('.open-top') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .attr('x1', xScale2(brush.extent()[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[0])) - .attr('y2', prevChartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .attr('x1', xScale2(brush.extent()[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(brush.extent()[1])) - .attr('y2', prevChartHeight); - - focus.select('.now-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(nowDate)) - .attr('y1', yScale(scaleBg(36))) - .attr('x2', xScale(nowDate)) - .attr('y2', yScale(scaleBg(420))); - - context.select('.now-line') - .transition() - .attr('x1', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale2(brush.extent()[1]- THIRTY_MINS_IN_MS)) - .attr('y2', yScale2(scaleBg(420))); - - // update x axis - focus.select('.x.axis') - .call(xAxis); - - // add clipping path so that data stays within axis - focusCircles.attr('clip-path', 'url(#clip)'); - - function prepareTreatCircles(sel) { - sel.attr('cx', function (d) { return xScale(new Date(d.mills)); }) - .attr('cy', function (d) { return yScale(sbx.scaleEntry(d)); }) - .attr('r', function () { return dotRadius('mbg'); }) - .attr('stroke-width', 2) - .attr('stroke', function (d) { return d.glucose ? 'grey' : 'white'; }) - .attr('fill', function (d) { return d.glucose ? 'red' : 'grey'; }); - - return sel; - } - - //NOTE: treatments with insulin or carbs are drawn by drawTreatment() - // bind up the focus chart data to an array of circles - var treatCircles = focus.selectAll('rect').data(treatments.filter(function(treatment) { - return !treatment.carbs && !treatment.insulin; - })); - - // if already existing then transition each circle to its new position - prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareTreatCircles(treatCircles.enter().append('circle')) - .on('mouseover', function (d) { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html(''+translate('Time')+': ' + formatTime(new Date(d.mills)) + '
    ' + - (d.eventType ? ''+translate('Treatment type')+': ' + d.eventType + '
    ' : '') + - (d.glucose ? ''+translate('BG')+': ' + d.glucose + (d.glucoseType ? ' (' + translate(d.glucoseType) + ')': '') + '
    ' : '') + - (d.enteredBy ? ''+translate('Entered by')+': ' + d.enteredBy + '
    ' : '') + - (d.notes ? ''+translate('Notes')+': ' + d.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatCircles.attr('clip-path', 'url(#clip)'); - } - - // called for initial update and updates for resize - var updateChart = _.debounce(function debouncedUpdateChart(init) { - - if (documentHidden && !init) { - console.info('Document Hidden, not updating - ' + (new Date())); - return; - } - // get current data range - var dataRange = d3.extent(data, dateFn); - - // get the entire container height and width subtracting the padding - var chartWidth = (document.getElementById('chartContainer') - .getBoundingClientRect().width) - padding.left - padding.right; - - var chartHeight = (document.getElementById('chartContainer') - .getBoundingClientRect().height) - padding.top - padding.bottom; - - // get the height of each chart based on its container size ratio - focusHeight = chartHeight * .7; - contextHeight = chartHeight * .2; - - // get current brush extent - var currentBrushExtent = brush.extent(); - - // only redraw chart if chart size has changed - if ((prevChartWidth !== chartWidth) || (prevChartHeight !== chartHeight)) { - - prevChartWidth = chartWidth; - prevChartHeight = chartHeight; - - //set the width and height of the SVG element - charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); - - // ranges are based on the width and height available so reset - xScale.range([0, chartWidth]); - xScale2.range([0, chartWidth]); - yScale.range([focusHeight, 0]); - yScale2.range([chartHeight, chartHeight - contextHeight]); - - if (init) { - - // if first run then just display axis with no transition - focus.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focus.select('.y') - .attr('transform', 'translate(' + chartWidth + ',0)') - .call(yAxis); - - // if first run then just display axis with no transition - context.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - context.append('g') - .attr('class', 'x brush') - .call(d3.svg.brush().x(xScale2).on('brush', brushed)) - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // disable resizing of brush - d3.select('.x.brush').select('.background').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.e').style('cursor', 'move'); - d3.select('.x.brush').select('.resize.w').style('cursor', 'move'); - - // create a clipPath for when brushing - clip = charts.append('defs') - .append('clipPath') - .attr('id', 'clip') - .append('rect') - .attr('height', chartHeight) - .attr('width', chartWidth); - - // add a line that marks the current time - focus.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that shows the high bg threshold - focus.append('line') - .attr('class', 'target-top-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'target-bottom-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - focus.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .style('stroke-dasharray', ('1, 6')) - .attr('stroke', '#777'); - - // add a y-axis line that opens up the brush extent from the context to the focus - focus.append('line') - .attr('class', 'open-top') - .attr('stroke', 'black') - .attr('stroke-width', 2); - - // add a x-axis line that closes the the brush container on left side - focus.append('line') - .attr('class', 'open-left') - .attr('stroke', 'white'); - - // add a x-axis line that closes the the brush container on right side - focus.append('line') - .attr('class', 'open-right') - .attr('stroke', 'white'); - - // add a line that marks the current time - context.append('line') - .attr('class', 'now-line') - .attr('x1', xScale(new Date(now))) - .attr('y1', yScale2(scaleBg(36))) - .attr('x2', xScale(new Date(now))) - .attr('y2', yScale2(scaleBg(420))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the high bg threshold - context.append('line') - .attr('class', 'high-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - // add a y-axis line that shows the low bg threshold - context.append('line') - .attr('class', 'low-line') - .attr('x1', xScale(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .style('stroke-dasharray', ('3, 3')) - .attr('stroke', 'grey'); - - } else { - - // for subsequent updates use a transition to animate the axis to the new position - var focusTransition = focus.transition().duration(UPDATE_TRANS_MS); - - focusTransition.select('.x') - .attr('transform', 'translate(0,' + focusHeight + ')') - .call(xAxis); - - focusTransition.select('.y') - .attr('transform', 'translate(' + chartWidth + ', 0)') - .call(yAxis); - - var contextTransition = context.transition().duration(UPDATE_TRANS_MS); - - contextTransition.select('.x') - .attr('transform', 'translate(0,' + chartHeight + ')') - .call(xAxis2); - - if (clip) { - // reset clip to new dimensions - clip.transition() - .attr('width', chartWidth) - .attr('height', chartHeight); - } - - // reset brush location - context.select('.x.brush') - .selectAll('rect') - .attr('y', focusHeight) - .attr('height', chartHeight - focusHeight); - - // clear current brush - d3.select('.brush').call(brush.clear()); - - // redraw old brush with new dimensions - d3.select('.brush').transition().duration(UPDATE_TRANS_MS).call(brush.extent(currentBrushExtent)); - - // transition lines to correct location - focus.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_high))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_high))); - - focus.select('.target-top-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_top))); - - focus.select('.target-bottom-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_target_bottom))); - - focus.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(serverSettings.thresholds.bg_low))) - .attr('x2', xScale(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(serverSettings.thresholds.bg_low))); - - // transition open-top line to correct location - focus.select('.open-top') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', yScale(scaleBg(30))) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', yScale(scaleBg(30))); - - // transition open-left line to correct location - focus.select('.open-left') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[0])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[0])) - .attr('y2', chartHeight); - - // transition open-right line to correct location - focus.select('.open-right') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(currentBrushExtent[1])) - .attr('y1', focusHeight) - .attr('x2', xScale2(currentBrushExtent[1])) - .attr('y2', chartHeight); - - // transition high line to correct location - context.select('.high-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_top))); - - // transition low line to correct location - context.select('.low-line') - .transition() - .duration(UPDATE_TRANS_MS) - .attr('x1', xScale2(dataRange[0])) - .attr('y1', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))) - .attr('x2', xScale2(dataRange[1])) - .attr('y2', yScale2(scaleBg(serverSettings.thresholds.bg_target_bottom))); - } - } - - // update domain - xScale2.domain(dataRange); - - // only if a user brush is not active, update brush and focus chart with recent data - // else, just transition brush - var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); - if (!brushInProgress) { - updateBrush - .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); - brushed(true); - } else { - updateBrush - .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); - brushed(true); - } - - // bind up the context chart data to an array of circles - var contextCircles = context.selectAll('circle') - .data(data); - - function prepareContextCircles(sel) { - var badData = []; - sel.attr('cx', function (d) { return xScale2(new Date(d.mills)); }) - .attr('cy', function (d) { - var scaled = sbx.scaleEntry(d); - if (isNaN(scaled)) { - badData.push(d); - return yScale2(scaleBg(450)); - } else { - return yScale2(scaled); - } - }) - .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return highlightBrushPoints(d) }) - .attr('stroke-width', function (d) { return d.type === 'mbg' ? 2 : 0; }) - .attr('stroke', function ( ) { return 'white'; }) - .attr('r', function (d) { return d.type === 'mbg' ? 4 : 2; }); - - if (badData.length > 0) { - console.warn('Bad Data: isNaN(sgv)', badData); - } - - return sel; - } - - // if already existing then transition each circle to its new position - prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); - - // if new circle then just display - prepareContextCircles(contextCircles.enter().append('circle')); - - contextCircles.exit() - .remove(); - - // update x axis domain - context.select('.x') - .call(xAxis2); - - }, DEBOUNCE_MS); - - function sgvToColor(sgv) { - var color = 'grey'; - - if (browserSettings.theme === 'colors') { - if (sgv > serverSettings.thresholds.bg_high) { - color = 'red'; - } else if (sgv > serverSettings.thresholds.bg_target_top) { - color = 'yellow'; - } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { - color = '#4cff00'; - } else if (sgv < serverSettings.thresholds.bg_low) { - color = 'red'; - } else if (sgv < serverSettings.thresholds.bg_target_bottom) { - color = 'yellow'; - } - } - - return color; - } - - function sgvToColoredRange(sgv) { - var range = ''; - - if (browserSettings.theme === 'colors') { - if (sgv > serverSettings.thresholds.bg_high) { - range = 'urgent'; - } else if (sgv > serverSettings.thresholds.bg_target_top) { - range = 'warning'; - } else if (sgv >= serverSettings.thresholds.bg_target_bottom && sgv <= serverSettings.thresholds.bg_target_top) { - range = 'inrange'; - } else if (sgv < serverSettings.thresholds.bg_low) { - range = 'urgent'; - } else if (sgv < serverSettings.thresholds.bg_target_bottom) { - range = 'warning'; - } - } - - return range; - } - - - function generateAlarm(file, reason) { - alarmInProgress = true; - alarmMessage = reason && reason.title; - var selector = '.audio.alarms audio.' + file; - - if (!alarmingNow()) { - d3.select(selector).each(function () { - var audio = this; - playAlarm(audio); - $(this).addClass('playing'); - }); - } - - container.addClass('alarming').addClass(file === urgentAlarmSound ? 'urgent' : 'warning'); - - var skipPageTitle = isTimeAgoAlarmType(currentAlarmType); - updateTitle(skipPageTitle); - } - - function playAlarm(audio) { - // ?mute=true disables alarms to testers. - if (querystring.mute !== 'true') { - audio.play(); - } else { - showNotification('Alarm was muted (?mute=true)'); - } - } - - function stopAlarm(isClient, silenceTime) { - alarmInProgress = false; - alarmMessage = null; - container.removeClass('urgent warning'); - d3.selectAll('audio.playing').each(function () { - var audio = this; - audio.pause(); - $(this).removeClass('playing'); - }); - - closeNotification(); - container.removeClass('alarming'); - - updateTitle(); - - // only emit ack if client invoke by button press - if (isClient) { - if (isTimeAgoAlarmType(currentAlarmType)) { - container.removeClass('alarming-timeago'); - var alarm = getClientAlarm(currentAlarmType); - alarm.lastAckTime = Date.now(); - alarm.silenceTime = silenceTime; - } - socket.emit('ack', currentAlarmType || 'alarm', silenceTime); - } - - brushed(false); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - //draw a compact visualization of a treatment (carbs, insulin) - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function drawTreatment(treatment, scale, showValues) { - - if (!treatment.carbs && !treatment.insulin) { return; } - - // don't render the treatment if it's not visible - if (Math.abs(xScale(new Date(treatment.mills))) > window.innerWidth) { return; } - - var CR = treatment.CR || 20; - var carbs = treatment.carbs || CR; - var insulin = treatment.insulin || 1; - - var R1 = Math.sqrt(Math.min(carbs, insulin * CR)) / scale, - R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, - R3 = R2 + 8 / scale; - - if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn('Bad Data: Found isNaN value in treatment', treatment); - return; - } - - var arc_data = [ - { 'element': '', 'color': 'white', 'start': -1.5708, 'end': 1.5708, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': -1.5708, 'end': 1.5708, 'inner': R2, 'outer': R3 }, - { 'element': '', 'color': '#0099ff', 'start': 1.5708, 'end': 4.7124, 'inner': 0, 'outer': R1 }, - { 'element': '', 'color': 'transparent', 'start': 1.5708, 'end': 4.7124, 'inner': R2, 'outer': R3 } - ]; - - arc_data[0].outlineOnly = !treatment.carbs; - arc_data[2].outlineOnly = !treatment.insulin; - - if (treatment.carbs > 0) { - arc_data[1].element = Math.round(treatment.carbs) + ' g'; - } - - if (treatment.insulin > 0) { - arc_data[3].element = Math.round(treatment.insulin * 100) / 100 + ' U'; - } - - var arc = d3.svg.arc() - .innerRadius(function (d) { return 5 * d.inner; }) - .outerRadius(function (d) { return 5 * d.outer; }) - .endAngle(function (d) { return d.start; }) - .startAngle(function (d) { return d.end; }); - - var treatmentDots = focus.selectAll('treatment-dot') - .data(arc_data) - .enter() - .append('g') - .attr('transform', 'translate(' + xScale(new Date(treatment.mills)) + ', ' + yScale(sbx.scaleEntry(treatment)) + ')') - .on('mouseover', function () { - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html(''+translate('Time')+': ' + formatTime(new Date(treatment.mills)) + '
    ' + ''+translate('Treatment type')+': ' + translate(treatment.eventType) + '
    ' + - (treatment.carbs ? ''+translate('Carbs')+': ' + treatment.carbs + '
    ' : '') + - (treatment.insulin ? ''+translate('Insulin')+': ' + treatment.insulin + '
    ' : '') + - (treatment.glucose ? ''+translate('BG')+': ' + treatment.glucose + (treatment.glucoseType ? ' (' + translate(treatment.glucoseType) + ')': '') + '
    ' : '') + - (treatment.enteredBy ? ''+translate('Entered by')+': ' + treatment.enteredBy + '
    ' : '') + - (treatment.notes ? ''+translate('Notes')+': ' + treatment.notes : '') - ) - .style('left', (d3.event.pageX) + 'px') - .style('top', (d3.event.pageY + 15) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - - treatmentDots.append('path') - .attr('class', 'path') - .attr('fill', function (d) { return d.outlineOnly ? 'transparent' : d.color; }) - .attr('stroke-width', function (d) { return d.outlineOnly ? 1 : 0; }) - .attr('stroke', function (d) { return d.color; }) - .attr('id', function (d, i) { return 's' + i; }) - .attr('d', arc); - - - // labels for carbs and insulin - if (showValues) { - var label = treatmentDots.append('g') - .attr('class', 'path') - .attr('id', 'label') - .style('fill', 'white'); - label.append('text') - .style('font-size', 40 / scale) - .style('text-shadow', '0px 0px 10px rgba(0, 0, 0, 1)') - .attr('text-anchor', 'middle') - .attr('dy', '.35em') - .attr('transform', function (d) { - d.outerRadius = d.outerRadius * 2.1; - d.innerRadius = d.outerRadius * 2.1; - return 'translate(' + arc.centroid(d) + ')'; - }) - .text(function (d) { return d.element; }); - } - } - - function updateClock() { - updateClockDisplay(); - var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(updateClock,interval); - - updateTimeAgo(); - - // Dim the screen by reducing the opacity when at nighttime - if (browserSettings.nightMode) { - var dateTime = new Date(); - if (opacity.current !== opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { - $('body').css({ 'opacity': opacity.NIGHT }); - } else { - $('body').css({ 'opacity': opacity.DAY }); - } - } - } - - function updateClockDisplay() { - if (inRetroMode()) { - return; - } - now = Date.now(); - $('#currentTime').text(formatTime(new Date(now), true)).css('text-decoration', ''); - } - - function getClientAlarm(type) { - var alarm = clientAlarms[type]; - if (!alarm) { - alarm = { type: type }; - clientAlarms[type] = alarm; - } - return alarm; - } - - function isTimeAgoAlarmType(alarmType) { - return alarmType === 'warnTimeAgo' || alarmType === 'urgentTimeAgo'; - } - - function checkTimeAgoAlarm(ago) { - var level = ago.status - , alarm = getClientAlarm(level + 'TimeAgo'); - - if (Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) { - currentAlarmType = alarm.type; - console.info('generating timeAgoAlarm', alarm.type); - container.addClass('alarming-timeago'); - var message = {'title': 'Last data received ' + [ago.value, ago.label].join(' ')}; - if (level === 'warn') { - generateAlarm(alarmSound, message); - } else { - generateAlarm(urgentAlarmSound, message); - } - } - } - - function updateTimeAgo() { - var lastEntry = $('#lastEntry') - , time = latestSGV ? latestSGV.mills : -1 - , ago = timeAgo(time, browserSettings) - , retroMode = inRetroMode(); - - lastEntry.removeClass('current warn urgent'); - lastEntry.addClass(ago.status); - - if (ago.status !== 'current') { - updateTitle(); - } - - if ( - (browserSettings.alarmTimeAgoWarn && ago.status === 'warn') - || (browserSettings.alarmTimeAgoUrgent && ago.status === 'urgent')) { - checkTimeAgoAlarm(ago); - } - - container.toggleClass('alarming-timeago', ago.status !== 'current'); - - if (alarmingNow() && ago.status === 'current' && isTimeAgoAlarmType(currentAlarmType)) { - stopAlarm(true, ONE_MIN_IN_MS); - } - - if (retroMode || !ago.value) { - lastEntry.find('em').hide(); - } else { - lastEntry.find('em').show().text(ago.value); - } - - if (retroMode || ago.label) { - lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); - } else { - lastEntry.find('label').hide(); - } - } - - function init() { - - jqWindow = $(window); - - tooltip = d3.select('body').append('div') - .attr('class', 'tooltip') - .style('opacity', 0); - - // Tick Values - if (browserSettings.units === 'mmol') { - tickValues = [ - 2.0 - , Math.round(scaleBg(serverSettings.thresholds.bg_low)) - , Math.round(scaleBg(serverSettings.thresholds.bg_target_bottom)) - , 6.0 - , Math.round(scaleBg(serverSettings.thresholds.bg_target_top)) - , Math.round(scaleBg(serverSettings.thresholds.bg_high)) - , 22.0 - ]; - } else { - tickValues = [ - 40 - , serverSettings.thresholds.bg_low - , serverSettings.thresholds.bg_target_bottom - , 120 - , serverSettings.thresholds.bg_target_top - , serverSettings.thresholds.bg_high - , 400 - ]; - } - - futureOpacity = d3.scale.linear( ) - .domain([TWENTY_FIVE_MINS_IN_MS, SIXTY_MINS_IN_MS]) - .range([0.8, 0.1]); - - // create svg and g to contain the chart contents - charts = d3.select('#chartContainer').append('svg') - .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); - - focus = charts.append('g'); - - // create the x axis container - focus.append('g') - .attr('class', 'x axis'); - - // create the y axis container - focus.append('g') - .attr('class', 'y axis'); - - context = charts.append('g'); - - // create the x axis container - context.append('g') - .attr('class', 'x axis'); - - // create the y axis container - context.append('g') - .attr('class', 'y axis'); - - //updateChart is _.debounce'd - function refreshChart(updateToNow) { - if (updateToNow) { - updateBrushToNow(); - } - updateChart(false); - } - - function visibilityChanged() { - var prevHidden = documentHidden; - documentHidden = (document.hidden || document.webkitHidden || document.mozHidden || document.msHidden); - - if (prevHidden && !documentHidden) { - console.info('Document now visible, updating - ' + (new Date())); - refreshChart(true); - } - } - - window.onresize = refreshChart; - - document.addEventListener('webkitvisibilitychange', visibilityChanged); - - - updateClock(); - - var silenceDropdown = new Dropdown('.dropdown-menu'); - - $('.bgButton').click(function (e) { - if (alarmingNow()) { - silenceDropdown.open(e); - } - }); - - $('#silenceBtn').find('a').click(function (e) { - stopAlarm(true, $(this).data('snooze-time')); - e.preventDefault(); - }); - - $('.focus-range li').click(function(e) { - var li = $(e.target); - $('.focus-range li').removeClass('selected'); - li.addClass('selected'); - var hours = Number(li.data('hours')); - foucusRangeMS = hours * 60 * 60 * 1000; - refreshChart(); - }); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Client-side code to connect to server and handle incoming data - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket = io.connect(); - - function mergeDataUpdate(isDelta, cachedDataArray, receivedDataArray) { - - function nsArrayDiff(oldArray, newArray) { - var seen = {}; - var l = oldArray.length; - for (var i = 0; i < l; i++) { seen[oldArray[i].mills] = true } - var result = []; - l = newArray.length; - for (var j = 0; j < l; j++) { if (!seen.hasOwnProperty(newArray[j].mills)) { result.push(newArray[j]); console.log('delta data found'); } } - return result; - } - - // If there was no delta data, just return the original data - if (!receivedDataArray) { - return cachedDataArray; - } - - // If this is not a delta update, replace all data - if (!isDelta) { - return receivedDataArray; - } - - // If this is delta, calculate the difference, merge and sort - var diff = nsArrayDiff(cachedDataArray,receivedDataArray); - return cachedDataArray.concat(diff).sort(function(a, b) { - return a.mills - b.mills; - }); - } - - socket.on('dataUpdate', function receivedSGV(d) { - - if (!d) { - return; - } - - // Calculate the diff to existing data and replace as needed - - SGVdata = mergeDataUpdate(d.delta, SGVdata, d.sgvs); - MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs); - treatments = mergeDataUpdate(d.delta,treatments, d.treatments); - - if (d.profiles) { - profile = d.profiles[0]; - Nightscout.profile.loadData(d.profiles); - } - - if (d.cals) { cal = d.cals[d.cals.length-1]; } - if (d.devicestatus) { devicestatusData = d.devicestatus; } - - // Do some reporting on the console - console.log('Total SGV data size', SGVdata.length); - console.log('Total treatment data size', treatments.length); - - // Post processing after data is in - - if (d.sgvs) { - // change the next line so that it uses the prediction if the signal gets lost (max 1/2 hr) - latestUpdateTime = Date.now(); - latestSGV = SGVdata[SGVdata.length - 1]; - prevSGV = SGVdata[SGVdata.length - 2]; - } - - var temp1 = [ ]; - if (cal && rawbg.isEnabled(sbx)) { - temp1 = SGVdata.map(function (entry) { - var rawbgValue = rawbg.showRawBGs(entry.mgdl, entry.noise, cal, sbx) ? rawbg.calc(entry, cal, sbx) : 0; - if (rawbgValue > 0) { - return { mills: entry.mills - 2000, mgdl: rawbgValue, color: 'white', type: 'rawbg' }; - } else { - return null; - } - }).filter(function(entry) { return entry !== null; }); - } - var temp2 = SGVdata.map(function (obj) { - return { mills: obj.mills, mgdl: obj.mgdl, direction: obj.direction, color: sgvToColor(obj.mgdl), type: 'sgv', noise: obj.noise, filtered: obj.filtered, unfiltered: obj.unfiltered}; - }); - data = []; - data = data.concat(temp1, temp2); - - addPlaceholderPoints(); - - data = data.concat(MBGdata.map(function (obj) { return { mills: obj.mills, mgdl: obj.mgdl, color: 'red', type: 'mbg', device: obj.device } })); - - data.forEach(function (d) { - if (d.mgdl < 39) { d.color = 'transparent'; } - }); - - updateTitle(); - if (!isInitialData) { - isInitialData = true; - initializeCharts(); - } - else { - updateChart(false); - } - - }); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Alarms and Text handling - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - socket.on('connect', function () { - console.log('Client connected to server.'); - }); - - - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a HIGH we can only check if it's >= the bottom of the target - function isAlarmForHigh() { - return latestSGV.mgdl >= serverSettings.thresholds.bg_target_bottom; - } - - //with predicted alarms, latestSGV may still be in target so to see if the alarm - // is for a LOW we can only check if it's <= the top of the target - function isAlarmForLow() { - return latestSGV.mgdl <= serverSettings.thresholds.bg_target_top; - } - - socket.on('alarm', function (notify) { - console.info('alarm received from server'); - console.log('notify:',notify); - var enabled = (isAlarmForHigh() && browserSettings.alarmHigh) || (isAlarmForLow() && browserSettings.alarmLow); - if (enabled) { - console.log('Alarm raised!'); - currentAlarmType = 'alarm'; - generateAlarm(alarmSound,notify); - } else { - console.info('alarm was disabled locally', latestSGV.mgdl, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('urgent_alarm', function (notify) { - console.info('urgent alarm received from server'); - console.log('notify:',notify); - - var enabled = (isAlarmForHigh() && browserSettings.alarmUrgentHigh) || (isAlarmForLow() && browserSettings.alarmUrgentLow); - if (enabled) { - console.log('Urgent alarm raised!'); - currentAlarmType = 'urgent_alarm'; - generateAlarm(urgentAlarmSound,notify); - } else { - console.info('urgent alarm was disabled locally', latestSGV.mgdl, browserSettings); - } - brushInProgress = false; - updateChart(false); - }); - socket.on('clear_alarm', function () { - if (alarmInProgress) { - console.log('clearing alarm'); - stopAlarm(); - } - }); - - - $('#testAlarms').click(function(event) { - d3.selectAll('.audio.alarms audio').each(function () { - var audio = this; - playAlarm(audio); - setTimeout(function() { - audio.pause(); - }, 4000); - }); - event.preventDefault(); - }); - - if (serverSettings.enabledOptions.indexOf('ar2') < 0) { - serverSettings.enabledOptions += ' ar2'; - } - - $('.appName').text(serverSettings.name); - $('.version').text(serverSettings.version); - $('.head').text(serverSettings.head); - if (serverSettings.apiEnabled) { - $('.serverSettings').show(); - } - $('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled); - Nightscout.plugins.init(serverSettings); - browserSettings = getBrowserSettings(browserStorage); - sbx = Nightscout.sandbox.clientInit(serverSettings, browserSettings, Date.now()); - $('.container').toggleClass('has-minor-pills', Nightscout.plugins.hasShownType('pill-minor', browserSettings)); - - } - - init(); - -})(); +window.Nightscout.client(Nightscout.plugins, serverSettings); diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 93651c0cc22..d72d9f33b2c 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -6,118 +6,6 @@ function rawBGsEnabled() { return serverSettings.enabledOptions && serverSettings.enabledOptions.indexOf('rawbg') > -1; } -function getBrowserSettings(storage) { - var json = {}; - - function scaleBg(bg) { - if (json.units === 'mmol') { - return Nightscout.units.mgdlToMMOL(bg); - } else { - return bg; - } - } - - function appendThresholdValue(threshold) { - return serverSettings.alarm_types.indexOf('simple') === -1 ? '' : ' (' + scaleBg(threshold) + ')'; - } - - try { - json = { - 'units': storage.get('units'), - 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), - 'alarmHigh': storage.get('alarmHigh'), - 'alarmLow': storage.get('alarmLow'), - 'alarmUrgentLow': storage.get('alarmUrgentLow'), - 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), - 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'), - 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), - 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'), - 'nightMode': storage.get('nightMode'), - 'showRawbg': storage.get('showRawbg'), - 'customTitle': storage.get('customTitle'), - 'theme': storage.get('theme'), - 'timeFormat': storage.get('timeFormat'), - 'showPlugins': storage.get('showPlugins') - }; - - // Default browser units to server units if undefined. - json.units = setDefault(json.units, serverSettings.units); - if (json.units === 'mmol') { - $('#mmol-browser').prop('checked', true); - } else { - $('#mgdl-browser').prop('checked', true); - } - - json.alarmUrgentHigh = setDefault(json.alarmUrgentHigh, serverSettings.defaults.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, serverSettings.defaults.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, serverSettings.defaults.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, serverSettings.defaults.alarmUrgentLow); - json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, serverSettings.defaults.alarmTimeAgoWarn); - json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, serverSettings.defaults.alarmTimeAgoWarnMins); - json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, serverSettings.defaults.alarmTimeAgoUrgent); - json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, serverSettings.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(serverSettings.thresholds.bg_low)); - $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); - $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); - $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); - $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins); - - json.nightMode = setDefault(json.nightMode, serverSettings.defaults.nightMode); - $('#nightmode-browser').prop('checked', json.nightMode); - - if (rawBGsEnabled()) { - $('#show-rawbg-option').show(); - json.showRawbg = setDefault(json.showRawbg, serverSettings.defaults.showRawbg); - $('#show-rawbg-' + json.showRawbg).prop('checked', true); - } else { - json.showRawbg = 'never'; - $('#show-rawbg-option').hide(); - } - - json.customTitle = setDefault(json.customTitle, serverSettings.defaults.customTitle); - $('h1.customTitle').text(json.customTitle); - $('input#customTitle').prop('value', json.customTitle); - - json.theme = setDefault(json.theme, serverSettings.defaults.theme); - if (json.theme === 'colors') { - $('#theme-colors-browser').prop('checked', true); - } else { - $('#theme-default-browser').prop('checked', true); - } - - json.timeFormat = setDefault(json.timeFormat, serverSettings.defaults.timeFormat); - - if (json.timeFormat === '24') { - $('#24-browser').prop('checked', true); - } else { - $('#12-browser').prop('checked', true); - } - - json.showPlugins = setDefault(json.showPlugins, serverSettings.defaults.showPlugins || Nightscout.plugins.enabledPluginNames()); - var showPluginsSettings = $('#show-plugins'); - Nightscout.plugins.eachEnabledPlugin(function each(plugin) { - if (Nightscout.plugins.specialPlugins.indexOf(plugin.name) > -1) { - //ignore these, they are always on for now - } else { - var id = 'plugin-' + plugin.name; - var dd = $('
    '); - showPluginsSettings.append(dd); - dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); - } - }); - - - } catch(err) { - console.error(err); - showLocalstorageError(); - } - - return json; -} - function setDefault(variable, defaultValue) { if (typeof(variable) === 'object') { return defaultValue; @@ -207,36 +95,9 @@ function showNotification(note, type) { notify.show(); } -function showLocalstorageError() { - var msg = 'Settings are disabled.

    Please enable cookies so you may customize your Nightscout site.'; - $('.browserSettings').html('Settings'+msg+''); - $('#save').hide(); -} - var querystring = getQueryParms(); -function Dropdown(el) { - this.ddmenuitem = 0; - - this.$el = $(el); - var that = this; - - $(document).click(function() { that.close(); }); -} -Dropdown.prototype.close = function () { - if (this.ddmenuitem) { - this.ddmenuitem.css('visibility', 'hidden'); - this.ddmenuitem = 0; - } -}; -Dropdown.prototype.open = function (e) { - this.close(); - this.ddmenuitem = $(this.$el).css('visibility', 'visible'); - e.stopPropagation(); -}; - - $('#drawerToggle').click(function(event) { toggleDrawer('#drawer'); event.preventDefault(); diff --git a/tests/client.test.js b/tests/client.test.js new file mode 100644 index 00000000000..4002890efe0 --- /dev/null +++ b/tests/client.test.js @@ -0,0 +1,53 @@ +'use strict'; + +require('should'); + +var benv = require('benv'); + +describe('client', function ( ) { + + before(function (done) { + benv.setup(function() { + benv.expose({ + $: require('jquery') + , jQuery: require('jquery') + , d3: require('d3') + , io: { + connect: function mockConnect() { + return { + on: function mockOn(event, callback) {} + }; + } + } + }); +// benv.require('../static/js/ui-utils.js', 'window'); +// benv.require('../static/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js'); + done(); + }); + }); + + after(function (done) { + benv.teardown(); + done(); + }); + + it ('not blow up', function () { +// var jsdom = require('jsdom').jsdom; +// var doc = jsdom(''); +// var window = doc.parentWindow; +// +// global.$ = global.jQuery = require('jquery')(window); + + var serverSettings = { + apiEnabled: true, careportalEnabled: true, enabledOptions: "ar2 careportal delta direction upbat errorcodes", defaults: { + "units": "mg/dl", "timeFormat": "12", "nightMode": false, "showRawbg": "noise", "customTitle": "Nightscout", "theme": "colors", "alarmUrgentHigh": true, "alarmHigh": true, "alarmLow": true, "alarmUrgentLow": true, "alarmTimeAgoWarn": true, "alarmTimeAgoWarnMins": 15, "alarmTimeAgoUrgent": true, "alarmTimeAgoUrgentMins": 30, "language": "en", "showPlugins": "iob delta direction upbatrawbg" + }, "units": "mg/dl", "head": "ae71dca", "version": "0.7.0", "thresholds": { + "bg_high": 200, "bg_target_top": 170, "bg_target_bottom": 80, "bg_low": 55 + }, "alarm_types": "predict", "name": "Nightscout" + }; + + var plugins = require('../lib/plugins/')().registerClientDefaults(); + var client = require('../lib/client')(plugins, serverSettings); + }); + +}); From b4378f8782f748cfcaf9099a441e109991ae11b3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:39:01 -0700 Subject: [PATCH 461/661] cleanup tests and hoefully make them pass --- package.json | 1 - tests/client.test.js | 46 ++++++++++++++++++++----------- tests/pluginbase.test.js | 58 ++++++++++++++++++++++++++-------------- 3 files changed, 69 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index ab60c6a9494..89da4c327c0 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "mocha": "~1.20.1", "should": "~4.0.4", "supertest": "~0.13.0", - "jsdom": "^3.1.2", "benv": "^1.1.0" } } diff --git a/tests/client.test.js b/tests/client.test.js index 4002890efe0..cd03296e08f 100644 --- a/tests/client.test.js +++ b/tests/client.test.js @@ -13,15 +13,13 @@ describe('client', function ( ) { , jQuery: require('jquery') , d3: require('d3') , io: { - connect: function mockConnect() { + connect: function mockConnect ( ) { return { - on: function mockOn(event, callback) {} + on: function mockOn ( ) { } }; } } }); -// benv.require('../static/js/ui-utils.js', 'window'); -// benv.require('../static/bower_components/tipsy-jmalonzo/src/javascripts/jquery.tipsy.js'); done(); }); }); @@ -32,18 +30,36 @@ describe('client', function ( ) { }); it ('not blow up', function () { -// var jsdom = require('jsdom').jsdom; -// var doc = jsdom(''); -// var window = doc.parentWindow; -// -// global.$ = global.jQuery = require('jquery')(window); - var serverSettings = { - apiEnabled: true, careportalEnabled: true, enabledOptions: "ar2 careportal delta direction upbat errorcodes", defaults: { - "units": "mg/dl", "timeFormat": "12", "nightMode": false, "showRawbg": "noise", "customTitle": "Nightscout", "theme": "colors", "alarmUrgentHigh": true, "alarmHigh": true, "alarmLow": true, "alarmUrgentLow": true, "alarmTimeAgoWarn": true, "alarmTimeAgoWarnMins": 15, "alarmTimeAgoUrgent": true, "alarmTimeAgoUrgentMins": 30, "language": "en", "showPlugins": "iob delta direction upbatrawbg" - }, "units": "mg/dl", "head": "ae71dca", "version": "0.7.0", "thresholds": { - "bg_high": 200, "bg_target_top": 170, "bg_target_bottom": 80, "bg_low": 55 - }, "alarm_types": "predict", "name": "Nightscout" + apiEnabled: true, careportalEnabled: true, enabledOptions: 'ar2 careportal delta direction upbat errorcodes', defaults: { + units: 'mg/dl' + , timeFormat: '12' + , nightMode: false + , showRawbg: 'noise' + , customTitle: 'Nightscout' + , theme: 'colors' + , alarmUrgentHigh: true + , alarmHigh: true + , alarmLow: true + , alarmUrgentLow: true + , alarmTimeAgoWarn: true + , alarmTimeAgoWarnMins: 15 + , alarmTimeAgoUrgent: true + , alarmTimeAgoUrgentMins: 30 + , language: 'en' + , showPlugins: 'iob delta direction upbatrawbg' + } + , units: 'mg/dl' + , head: 'ae71dca' + , version: '0.7.0' + , thresholds: { + bg_high: 200 + , bg_target_top: 170 + , bg_target_bottom: 80 + , bg_low: 55 + } + , alarm_types: 'predict' + , name: 'Nightscout' }; var plugins = require('../lib/plugins/')().registerClientDefaults(); diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 0751a53104c..2b4d9566df4 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -1,35 +1,53 @@ 'use strict'; require('should'); +var benv = require('benv'); describe('pluginbase', function ( ) { - var jsdom = require('jsdom').jsdom; - var doc = jsdom(''); - var window = doc.parentWindow; + before(function (done) { + benv.setup(function() { + benv.expose({ + $: require('jquery') + , jQuery: require('jquery') + , d3: require('d3') + , io: { + connect: function mockConnect() { + return { + on: function mockOn(event, callback) {} + }; + } + } + }); + done(); + }); + }); + after(function (done) { + benv.teardown(); + done(); + }); - global.$ = global.jQuery = require('jquery')(window); + it('does stuff', function() { - function div (clazz) { - return $('
    '); - } + function div (clazz) { + return $('
    '); + } - var container = div('container') - , bgStatus = div('bgStatus').appendTo(container) - , majorPills = div('majorPills').appendTo(bgStatus) - , minorPills = div('minorPills').appendTo(bgStatus) - , statusPills = div('statusPills').appendTo(bgStatus) - , tooltip = div('tooltip').appendTo(container) - ; + var container = div('container') + , bgStatus = div('bgStatus').appendTo(container) + , majorPills = div('majorPills').appendTo(bgStatus) + , minorPills = div('minorPills').appendTo(bgStatus) + , statusPills = div('statusPills').appendTo(bgStatus) + , tooltip = div('tooltip').appendTo(container) + ; - var fake = { - name: 'fake' - , label: 'Insulin-on-Board' - , pluginType: 'pill-major' - }; + var fake = { + name: 'fake' + , label: 'Insulin-on-Board' + , pluginType: 'pill-major' + }; - it('does stuff', function() { var pluginbase = require('../lib/plugins/pluginbase')(majorPills, minorPills, statusPills, bgStatus, tooltip); pluginbase.updatePillText(fake, { From 60526158f7473ed1b6dc03de12a2afa4d1b187c4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 22 Jul 2015 01:50:17 -0700 Subject: [PATCH 462/661] clean up unused --- tests/pluginbase.test.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/pluginbase.test.js b/tests/pluginbase.test.js index 2b4d9566df4..2397720b09e 100644 --- a/tests/pluginbase.test.js +++ b/tests/pluginbase.test.js @@ -10,14 +10,6 @@ describe('pluginbase', function ( ) { benv.expose({ $: require('jquery') , jQuery: require('jquery') - , d3: require('d3') - , io: { - connect: function mockConnect() { - return { - on: function mockOn(event, callback) {} - }; - } - } }); done(); }); From 2c489dd8cdb67853507a199063ee0ec483e3b41e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 22 Jul 2015 16:45:33 +0200 Subject: [PATCH 463/661] ns translation --- lib/language.js | 15 +++- lib/treatments.js | 4 +- static/index.html | 151 +++++++++++++++++++++-------------------- static/js/treatment.js | 47 ++++--------- static/js/ui-utils.js | 11 +-- 5 files changed, 113 insertions(+), 115 deletions(-) diff --git a/lib/language.js b/lib/language.js index 369a17c253d..4d39782b63f 100644 --- a/lib/language.js +++ b/lib/language.js @@ -831,8 +831,17 @@ function init() { ,'Calibration' : { cs: 'Kalibrace' } - ,'1' : { - cs: '1' + ,'Show Plugins' : { + cs: 'Zobrazuj pluginy' + } + ,'About' : { + cs: 'O aplikaci' + } + ,'Value in' : { + cs: 'Hodnota v' + } + ,'Prebolus' : { + cs: 'Posunuté jídlo' } }; @@ -849,7 +858,7 @@ function init() { $('.translate').each(function () { $(this).text(language.translate($(this).text())); }); - $('.titletranslate').each(function () { + $('.titletranslate, .tip').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); diff --git a/lib/treatments.js b/lib/treatments.js index 8a2bdebf123..a9f0856da00 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -16,7 +16,7 @@ function storage (env, ctx) { obj.created_at = created_at.toISOString(); var preBolusCarbs = ''; - if (obj.preBolus > 0 && obj.carbs) { + if (obj.preBolus != 0 && obj.carbs) { preBolusCarbs = obj.carbs; delete obj.carbs; } @@ -36,7 +36,7 @@ function storage (env, ctx) { api( ).insert(obj, function (err, doc) { fn(null, doc); - if (obj.preBolus > 0) { + if (obj.preBolus) { //create a new object to insert copying only the needed fields var pbTreat = { created_at: (new Date(created_at.getTime() + (obj.preBolus * 60000))).toISOString(), diff --git a/static/index.html b/static/index.html index 9d2ab4b79ea..e6bf3fdabd3 100644 --- a/static/index.html +++ b/static/index.html @@ -52,10 +52,10 @@ -
    @@ -84,65 +84,65 @@
    - Settings + Settings
    -
    Units
    +
    Units
    -
    Date format
    -
    -
    +
    Date format
    +
    +
    -
    Enable Alarms
    -
    -
    -
    -
    +
    Enable Alarms
    +
    +
    +
    +
    - + - mins + mins
    - + - mins + mins
    -
    Night Mode
    -
    +
    Night Mode
    +
    -
    Show Raw BG Data
    -
    -
    -
    +
    Show Raw BG Data
    +
    +
    +
    -
    Custom Title
    +
    Custom Title
    -
    Theme
    -
    -
    +
    Theme
    +
    +
    -
    Show Plugins
    +
    Show Plugins
    - About + About
    version
    head
    @@ -164,83 +164,90 @@
    - Log a Treatment -
    - View all treatments + View all treatments
    diff --git a/static/js/treatment.js b/static/js/treatment.js index 770f90205ac..1c0a13611de 100644 --- a/static/js/treatment.js +++ b/static/js/treatment.js @@ -1,9 +1,11 @@ 'use strict'; (function () { + var translate = Nightscout.language.translate; + function initTreatmentDrawer() { $('#eventType').val('BG Check'); - $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); + $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + browserSettings.units); $('#meter').prop('checked', true); $('#carbsGiven').val(''); $('#insulinGiven').val(''); @@ -15,22 +17,6 @@ $('#eventDateValue').val(moment().format('YYYY-MM-D')); } - function checkForErrors(data) { - var errors = []; - if (isNaN(data.glucose)) { - errors.push('Blood glucose must be a number'); - } - - if (isNaN(data.carbs)) { - errors.push('Carbs must be a number'); - } - - if (isNaN(data.insulin)) { - errors.push('Insulin must be a number'); - } - return errors; - } - function prepareData() { var data = { enteredBy: $('#enteredBy').val() @@ -54,13 +40,8 @@ function treatmentSubmit(event) { var data = prepareData(); - var errors = checkForErrors(data); - if (errors.length > 0) { - window.alert(errors.join('\n')); - } else { - confirmPost(data); - } + confirmPost(data); if (event) { event.preventDefault(); @@ -69,22 +50,22 @@ function buildConfirmText(data) { var text = [ - 'Please verify that the data entered is correct: ' - , 'Event type: ' + data.eventType + translate('Please verify that the data entered is correct') + ': ' + , translate('Event Type') + ': ' + translate(data.eventType) ]; if (data.glucose) { - text.push('Blood glucose: ' + data.glucose); - text.push('Method: ' + data.glucoseType); + text.push(translate('Blood Glucose') + ': ' + data.glucose); + text.push(translate('Measurement Method') + ': ' + translate(data.glucoseType)); } - if (data.carbs) { text.push('Carbs Given: ' + data.carbs); } - if (data.insulin) { text.push('Insulin Given: ' + data.insulin); } - if (data.preBolus) { text.push('Insulin Given: ' + data.insulin); } - if (data.notes) { text.push('Notes: ' + data.notes); } - if (data.enteredBy) { text.push('Entered By: ' + data.enteredBy); } + if (data.carbs) { text.push(translate('Carbs Given') + ': ' + data.carbs); } + if (data.insulin) { text.push(translate('Insulin Given') + ': ' + data.insulin); } + if (data.preBolus) { text.push(translate('Prebolus') + ': ' + data.preBolus + ' ' + translate('min')); } + if (data.notes) { text.push(translate('Notes') + ': ' + data.notes); } + if (data.enteredBy) { text.push(translate('Entered By') + ': ' + data.enteredBy); } - text.push('Event Time: ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); + text.push(translate('Event Time') + ': ' + (data.eventTime ? data.eventTime.toDate().toLocaleString() : new Date().toLocaleString())); return text.join('\n'); } diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 4fcc6b85a4a..6ca484822a3 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -7,6 +7,7 @@ function rawBGsEnabled() { } function getBrowserSettings(storage) { + var translate = Nightscout.language.translate; var json = {}; function scaleBg(bg) { @@ -56,10 +57,10 @@ function getBrowserSettings(storage) { json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins); json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins); - $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high)); - $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top)); - $('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom)); - $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low)); + $('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text(translate('Urgent High Alarm') + appendThresholdValue(app.thresholds.bg_high)); + $('#alarm-high-browser').prop('checked', json.alarmHigh).next().text(translate('High Alarm') + appendThresholdValue(app.thresholds.bg_target_top)); + $('#alarm-low-browser').prop('checked', json.alarmLow).next().text(translate('Low Alarm') + appendThresholdValue(app.thresholds.bg_target_bottom)); + $('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text(translate('Urgent Low Alarm') + appendThresholdValue(app.thresholds.bg_low)); $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn); $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins); $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); @@ -103,7 +104,7 @@ function getBrowserSettings(storage) { //ignore these, they are always on for now } else { var id = 'plugin-' + plugin.name; - var dd = $('
    '); + var dd = $('
    '); showPluginsSettings.append(dd); dd.find('input').prop('checked', json.showPlugins.indexOf(plugin.name) > -1); } From 423538f1a938e35bd8854c3693f21c90c588c3e9 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 22 Jul 2015 17:12:29 +0200 Subject: [PATCH 464/661] fr translation from pierre --- lib/language.js | 269 ++++++++++++++++++++++++++++++++++++++++++++-- static/index.html | 2 +- 2 files changed, 264 insertions(+), 7 deletions(-) diff --git a/lib/language.js b/lib/language.js index 4d39782b63f..eb78ac1f929 100644 --- a/lib/language.js +++ b/lib/language.js @@ -11,825 +11,1082 @@ function init() { // Server 'Listening on port' : { cs: 'Poslouchám na portu' + ,fr: 'Ecoute sur port' } // Client ,'Mo' : { cs: 'Po' ,de: 'Mo' + ,fr: 'Lu' } ,'Tu' : { cs: 'Út' ,de: 'Di' + ,fr: 'Ma' }, ',We' : { cs: 'St' ,de: 'Mi' + ,fr: 'Me' } ,'Th' : { cs: 'Čt' ,de: 'Do' + ,fr: 'Je' } ,'Fr' : { cs: 'Pá' ,de: 'Fr' + ,fr: 'Ve' } ,'Sa' : { cs: 'So' ,de: 'Sa' + ,fr: 'Sa' } ,'Su' : { cs: 'Ne' ,de: 'So' + ,fr: 'Di' } ,'Monday' : { cs: 'Pondělí' ,de: 'Montag' + ,fr: 'Lundi' } ,'Tuesday' : { cs: 'Úterý' ,de: 'Dienstag' + ,fr: 'Mardi' } ,'Wednesday' : { cs: 'Středa' ,de: 'Mittwoch' + ,fr: 'Mercredi' } ,'Thursday' : { cs: 'Čtvrtek' ,de: 'Donnerstag' + ,fr: 'Jeudi' } ,'Friday' : { cs: 'Pátek' ,de: 'Freitag' + ,fr: 'Vendredi' } ,'Saturday' : { cs: 'Sobota' ,de: 'Samstag' + ,fr: 'Samedi' } ,'Sunday' : { cs: 'Neděle' ,de: 'Sonntag' + ,fr: 'Dimanche' } ,'Category' : { cs: 'Kategorie' ,de: 'Kategorie' + ,fr: 'Catégorie' } ,'Subcategory' : { cs: 'Podkategorie' ,de: 'Unterkategorie' + ,fr: 'Sous-catégorie' } ,'Name' : { cs: 'Jméno' ,de: 'Name' + ,fr: 'Nom' } ,'Today' : { cs: 'Dnes' ,de: 'Heute' + ,fr: 'Aujourd\'hui' } ,'Last 2 days' : { cs: 'Poslední 2 dny' ,de: 'letzte 2 Tage' + ,fr: '2 derniers jours' } ,'Last 3 days' : { cs: 'Poslední 3 dny' ,de: 'letzte 3 Tage' + ,fr: '3 derniers jours' } ,'Last week' : { cs: 'Poslední týden' ,de: 'letzte Woche' + ,fr: 'Semaine Dernière' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' ,de: 'letzte 2 Wochen' + ,fr: '2 dernières semaines' } ,'Last month' : { cs: 'Poslední měsíc' ,de: 'letzter Monat' + ,fr: 'Mois dernier' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' ,de: 'letzte 3 Monate' + ,fr: '3 derniers mois' } ,'From' : { cs: 'Od' ,de: 'Von' + ,fr: 'De' } ,'To' : { cs: 'Do' ,de: 'Bis' + ,fr: 'à' } ,'Notes' : { cs: 'Poznámky' ,de: 'Notiz' + ,fr: 'Notes' } ,'Food' : { cs: 'Jídlo' ,de: 'Essen' + ,fr: 'Nourriture' } ,'Insulin' : { cs: 'Inzulín' ,de: 'Insulin' + ,fr: 'Insuline' } ,'Carbs' : { cs: 'Sacharidy' ,de: 'Kohlenhydrate' + ,fr: 'Glucides' } ,'Notes contain' : { cs: 'Poznámky obsahují' ,de: 'Notizen beinhalten' + ,fr: 'Notes contiennent' } ,'Event type contains' : { cs: 'Typ události obsahuje' ,de: 'Ereignis-Typ beinhaltet' + ,fr: 'Type d\'événement contient' } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' ,de: 'Untergrenze des Blutzuckerzielbereichs' + ,fr: 'Limite inférieure glycémie' } ,'top' : { cs: 'horní' ,de: 'oben' + ,fr: 'Supérieur' } ,'Show' : { cs: 'Zobraz' ,de: 'Zeige' + ,fr: 'Montrer' } ,'Display' : { cs: 'Zobraz' ,de: 'Zeige' + ,fr: 'Afficher' } ,'Loading' : { cs: 'Nahrávám' ,de: 'Laden' + ,fr: 'Chargement' } ,'Loading profile' : { cs: 'Nahrávám profil' ,de: 'Lade Profil' + ,fr: 'Chargement du profil' } ,'Loading status' : { cs: 'Nahrávám status' ,de: 'Lade Status' + ,fr: 'Statut du chargement' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' ,de: 'Lade Essensdatenbank' + ,fr: 'Chargement de la base de données alimentaire' } ,'not displayed' : { cs: 'není zobrazeno' ,de: 'nicht angezeigt' + ,fr: 'non affiché' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' ,de: 'Lade CGM-Daten von' + ,fr: 'Chargement données CGM de' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' ,de: 'Lade Behandlungsdaten von' + ,fr: 'Chargement données traitement de' } ,'Processing data of' : { cs: 'Zpracovávám data' ,de: 'Verarbeite Daten von' + ,fr: 'Traitement des données de' } ,'Portion' : { cs: 'Porce' ,de: 'Portion' + ,fr: 'Portion' } ,'Size' : { cs: 'Rozměr' ,de: 'Größe' + ,fr: 'Taille' } ,'(none)' : { cs: '(Prázdný)' ,de: '(nichts)' + ,fr: '(aucun)' } ,'Result is empty' : { cs: 'Prázdný výsledek' ,de: 'Leeres Ergebnis' + ,fr: 'Pas de résultat' } // ported reporting ,'Day to day' : { cs: 'Den po dni' + ,fr: 'jour par jour' } ,'Daily Stats' : { cs: 'Denní statistiky' + ,fr: 'Stats quotidiennes' } ,'Percentile Chart' : { cs: 'Percentil' + ,fr: 'Percentiles' } ,'Distribution' : { cs: 'Rozložení' - } + ,fr: 'Distribution' + } ,'Hourly stats' : { cs: 'Statistika po hodinách' - } + ,fr: 'Statistiques horaires' + } ,'Weekly success' : { cs: 'Statistika po týdnech' + ,fr: 'Résultat hebdomadaire' } ,'No data available' : { cs: 'Žádná dostupná data' + ,fr: 'Pas de données disponibles' } ,'Low' : { cs: 'Nízká' + ,fr: 'Bas' } ,'In Range' : { cs: 'V rozsahu' + ,fr: 'dans la norme' } ,'Period' : { cs: 'Období' + ,fr: 'Période' } ,'High' : { cs: 'Vysoká' + ,fr: 'Haut' } ,'Average' : { cs: 'Průměrná' + ,fr: 'Moyenne' } ,'Low Quartile' : { cs: 'Nízký kvartil' + ,fr: 'Quartile inférieur' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' + ,fr: 'Quartile supérieur' } ,'Quartile' : { cs: 'Kvartil' + ,fr: 'Quartile' } ,'Date' : { cs: 'Datum' + ,fr: 'Date' } ,'Normal' : { cs: 'Normální' + ,fr: 'Normale' } ,'Median' : { cs: 'Medián' + ,fr: 'Médiane' } ,'Readings' : { cs: 'Záznamů' + ,fr: 'Valeurs' } ,'StDev' : { cs: 'St. odchylka' + ,fr: 'Déviation St.' } ,'Daily stats report' : { cs: 'Denní statistiky' + ,fr: 'Rapport quotidien' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' + ,fr: 'Rapport precentiles Glycémie' } ,'Glucose distribution' : { cs: 'Rozložení glykémií' + ,fr: 'Distribution glycémies' } ,'days total' : { cs: 'dní celkem' + ,fr: 'jours totaux' } ,'Overall' : { cs: 'Celkem' + ,fr: 'Dans l\'ensemble' } ,'Range' : { cs: 'Rozsah' + ,fr: 'Intervalle' } ,'% of Readings' : { cs: '% záznamů' } ,'# of Readings' : { cs: 'počet záznamů' + ,fr: 'nbr de valeurs' } ,'Mean' : { cs: 'Střední hodnota' + ,fr: 'Moyenne' } ,'Standard Deviation' : { cs: 'Standardní odchylka' + ,fr: 'Déviation Standard' } ,'Max' : { cs: 'Max' + ,fr: 'Max' } ,'Min' : { cs: 'Min' + ,fr: 'Min' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' + ,fr: 'Estimation HbA1c*' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' + ,fr: 'Réussite hebdomadaire' } ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' - } + ,fr: 'Pas assez de données pour un rapport. Sélectionnez plus de jours.' + } // food editor ,'Using stored API secret hash' : { cs: 'Používám uložený hash API hesla' + ,fr: 'Utilisation du hash API existant' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' + ,fr: 'Pas de secret API existant. Vous devez en entrer un.' } ,'Database loaded' : { cs: 'Databáze načtena' + ,fr: 'Base de données chargée' } ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' + ,fr: 'Erreur, le chargement de la base de données a échoué' } ,'Create new record' : { cs: 'Vytvořit nový záznam' + ,fr: 'Créer nouvel enregistrement' } ,'Save record' : { cs: 'Uložit záznam' + ,fr: 'Sauver enregistrement' } ,'Portions' : { cs: 'Porcí' + ,fr: 'Portions' } ,'Unit' : { cs: 'Jedn' + ,fr: 'Unités' } ,'GI' : { cs: 'GI' + ,fr: 'IG' } ,'Edit record' : { cs: 'Upravit záznam' + ,fr: 'Modifier enregistrement' } ,'Delete record' : { cs: 'Smazat záznam' + ,fr: 'Effacer enregistrement' } ,'Move to the top' : { cs: 'Přesuň na začátek' + ,fr: 'Déplacer au sommet' } ,'Hidden' : { cs: 'Skrytý' + ,fr: 'Caché' } ,'Hide after use' : { cs: 'Skryj po použití' + ,fr: 'Cacher après utilisation' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' + ,fr: 'Votre secret API doit contenir au moins 12 caractères' } ,'Bad API secret' : { cs: 'Chybné API heslo' + ,fr: 'Secret API erroné' } ,'API secret hash stored' : { cs: 'Hash API hesla uložen' + ,fr: 'Hash API secret sauvegardé' } ,'Status' : { cs: 'Status' + ,fr: 'Statut' } ,'Not loaded' : { cs: 'Nenačtený' + ,fr: 'Non chargé' } ,'Food editor' : { cs: 'Editor jídel' + ,fr: 'Editeur aliments' } ,'Your database' : { cs: 'Vaše databáze' + ,fr: 'Votre base de données' } ,'Filter' : { cs: 'Filtr' + ,fr: 'Filtre' } ,'Save' : { cs: 'Ulož' + ,fr: 'Sauver' } ,'Clear' : { cs: 'Vymaž' + ,fr: 'Effacer' } ,'Record' : { cs: 'Záznam' + ,fr: 'Enregistrement' } ,'Quick picks' : { cs: 'Rychlý výběr' + ,fr: 'Sélection rapide' } ,'Show hidden' : { cs: 'Zobraz skryté' + ,fr: 'Montrer cachés' } ,'Your API secret' : { cs: 'Vaše API heslo' + ,fr: 'Votre secret API' } ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' + ,fr: 'Sauver le hash sur cet ordinateur (privé uniquement)' } ,'Treatments' : { cs: 'Ošetření' + ,fr: 'Traitements' } ,'Time' : { cs: 'Čas' + ,fr: 'Heure' } ,'Event Type' : { cs: 'Typ události' + ,fr: 'Type d\'événement' } ,'Blood Glucose' : { cs: 'Glykémie' + ,fr: 'Glycémie' } ,'Entered By' : { cs: 'Zadal' + ,fr: 'Entré par' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' + ,fr: 'Effacer ce traitement?' } ,'Carbs Given' : { cs: 'Sacharidů' + ,fr: 'Glucides donnés' } ,'Inzulin Given' : { cs: 'Inzulínu' + ,fr: 'Insuline donnée' } ,'Event Time' : { cs: 'Čas události' + ,fr: 'Heure de l\'événement' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' + ,fr: 'Merci de vérifier la correction des données entrées' } ,'BG' : { cs: 'Glykémie' + ,fr: 'Glycémie' } ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' + ,fr: 'Utiliser la correction de glycémie dans les calculs' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' + ,fr: 'Glycémie CGM (automatique)' } ,'BG from meter' : { cs: 'Glykémie z glukoměru' + ,fr: 'Glycémie glucomètre' } ,'Manual BG' : { cs: 'Ručně zadaná glykémie' + ,fr: 'Glycémie manuelle' } ,'Quickpick' : { cs: 'Rychlý výběr' + ,fr: 'Sélection rapide' } ,'or' : { cs: 'nebo' + ,fr: 'ou' } ,'Add from database' : { cs: 'Přidat z databáze' + ,fr: 'Ajouter à partir de la base de données' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' + ,fr: 'Utiliser la correction en glucides dans les calculs' } ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' + ,fr: 'Utiliser les COB dans les calculs' } ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' + ,fr: 'Utiliser l\'IOB dans les calculs' } ,'Other correction' : { cs: 'Jiná korekce' + ,fr: 'Autre correction' } ,'Rounding' : { cs: 'Zaokrouhlení' - } + ,fr: 'Arrondi' + } ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' + ,fr: 'Entrer correction insuline dans le traitement' } ,'Insulin needed' : { cs: 'Potřebný inzulín' + ,fr: 'Insuline nécessaire' } ,'Carbs needed' : { cs: 'Potřebné sach' + ,fr: 'Glucides nécessaires' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' + ,fr: 'Glucides nécessaires si insuline totale négative' } ,'Basal rate' : { cs: 'Bazál' + ,fr: 'Taux basal' } ,'Eating' : { cs: 'Jídlo' + ,fr: 'Repas' } ,'60 minutes before' : { cs: '60 min předem' + ,fr: '60 min avant' } ,'45 minutes before' : { cs: '45 min předem' + ,fr: '45 min avant' } ,'30 minutes before' : { cs: '30 min předem' + ,fr: '30 min avant' } ,'20 minutes before' : { cs: '20 min předem' + ,fr: '20 min avant' } ,'15 minutes before' : { cs: '15 min předem' + ,fr: '15 min avant' } ,'Time in minutes' : { cs: 'Čas v minutách' + ,fr: 'Durée en minutes' } ,'15 minutes after' : { cs: '15 min po' + ,fr: '15 min après' } ,'20 minutes after' : { cs: '20 min po' + ,fr: '20 min après' } ,'30 minutes after' : { cs: '30 min po' + ,fr: '30 min après' } ,'45 minutes after' : { cs: '45 min po' + ,fr: '45 min après' } ,'60 minutes after' : { cs: '60 min po' + ,fr: '60 min après' } - ,'Additional Notes, Comments:' : { - cs: 'Dalši poznámky, komentáře:' + ,'Additional Notes, Comments' : { + cs: 'Dalši poznámky, komentáře' + ,fr: 'Notes additionnelles, commentaires' } ,'RETRO MODE' : { cs: 'V MINULOSTI' + ,fr: 'MODE RETROSPECTIF' } ,'Now' : { cs: 'Nyní' + ,fr: 'Maintenant' } ,'Other' : { cs: 'Jiný' + ,fr: 'Autre' } ,'Submit Form' : { cs: 'Odeslat formulář' + ,fr: 'Formulaire de soumission' } ,'Profile editor' : { cs: 'Editor profilu' + ,fr: 'Editeur de profil' } ,'Reporting tool' : { cs: 'Výkazy' + ,fr: 'Outil de rapport' } ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' + ,fr: 'Ajouter aliment de votre base de données' } ,'Reload database' : { cs: 'Znovu nahraj databázi' + ,fr: 'Recharger la base de données' } ,'Add' : { cs: 'Přidej' + ,fr: 'Ajouter' } ,'Unauthorized' : { cs: 'Neautorizováno' + ,fr: 'Non autorisé' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' + ,fr: 'Entrée enregistrement a échoué' } ,'Device authenticated' : { cs: 'Zařízení ověřeno' + ,fr: 'Appareil authentifié' } ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' + ,fr: 'Appareil non authentifié' } ,'Authentication status' : { cs: 'Stav ověření' + ,fr: 'Status de l\'authentification' } ,'Authenticate' : { cs: 'Ověřit' + ,fr: 'Authentifier' } ,'Remove' : { cs: 'Vymazat' + ,fr: 'Retirer' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' + ,fr: 'Votre appareil n\'est pas encore authentifié' } ,'Sensor' : { cs: 'Senzor' + ,fr: 'Senseur' } ,'Finger' : { cs: 'Glukoměr' + ,fr: 'Doigt' } ,'Manual' : { cs: 'Ručně' + ,fr: 'Manuel' } ,'Scale' : { cs: 'Měřítko' + ,fr: 'Echelle' } ,'Linear' : { cs: 'lineární' + ,fr: 'Linéaire' } ,'Logarithmic' : { cs: 'logaritmické' + ,fr: 'Logarithmique' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' + ,fr: 'Silence pendant 30 minutes' } ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' + ,fr: 'Silence pendant 60 minutes' } ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' + ,fr: 'Silence pendant 90 minutes' } ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' + ,fr: 'Silence pendant 120 minutes' } ,'3HR' : { cs: '3hod' + ,fr: '3hr' } ,'6HR' : { cs: '6hod' + ,fr: '6hr' } ,'12HR' : { cs: '12hod' + ,fr: '12hr' } ,'24HR' : { cs: '24hod' + ,fr: '24hr' } ,'Sttings' : { cs: 'Nastavení' + ,fr: 'Paramètres' } ,'Units' : { cs: 'Jednotky' + ,fr: 'Unités' } ,'Date format' : { cs: 'Formát datumu' + ,fr: 'Format Date' } ,'12 hours' : { cs: '12 hodin' + ,fr: '12hr' } ,'24 hours' : { cs: '24 hodin' + ,fr: '24hr' } ,'Log a Treatment' : { cs: 'Záznam ošetření' + ,fr: 'Entrer un traitement' } ,'BG Check' : { cs: 'Kontrola glykémie' + ,fr: 'Contrôle glycémie' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' + ,fr: 'Bolus repas' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' + ,fr: 'Bolus friandise' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' + ,fr: 'Bolus de correction' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' + ,fr: 'Correction glucide' } ,'Note' : { cs: 'Poznámka' + ,fr: 'Note' } ,'Question' : { cs: 'Otázka' + ,fr: 'Question' } ,'Exercise' : { cs: 'Cvičení' + ,fr: 'Exercice' } ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' + ,fr: 'Changement de site pompe' } ,'Sensor Start' : { cs: 'Spuštění sensoru' + ,fr: 'Démarrage senseur' } ,'Sensor Change' : { cs: 'Výměna sensoru' + ,fr: 'Changement senseur' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' + ,fr: 'Démarrage senseur Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' + ,fr: 'Changement senseur Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' + ,fr: 'Changement cartouche d\'insuline' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' + ,fr: 'Wouf! Wouf! Chien d\'alerte diabète' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' + ,fr: 'Valeur de glycémie' } ,'Measurement Method' : { cs: 'Metoda měření' + ,fr: 'Méthode de mesure' } ,'Meter' : { cs: 'Glukoměr' + ,fr: 'Glucomètre' } ,'Insulin Given' : { cs: 'Inzulín' + ,fr: 'Insuline donnée' } ,'Amount in grams' : { cs: 'Množství v gramech' + ,fr: 'Quantité en grammes' } ,'Amount in units' : { cs: 'Množství v jednotkách' + ,fr: 'Quantité en unités' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' + ,fr: 'Voir tous les traitements' } ,'Enable Alarms' : { cs: 'Povolit alarmy' + ,fr: 'Activer les alarmes' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' + ,fr: 'Si activée, un alarme peut sonner.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' + ,fr: 'Alarme haute urgente' } ,'High Alarm' : { cs: 'Vysoká glykémie' + ,fr: 'Alarme haute' } ,'Low Alarm' : { cs: 'Nízká glykémie' + ,fr: 'Alarme basse' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' + ,fr: 'Alarme basse urgente' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' + ,fr: 'Données dépassées' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' + ,fr: 'Données dépassées urgentes' } ,'mins' : { cs: 'min' + ,fr: 'mins' } ,'Night Mode' : { cs: 'Noční mód' + ,fr: 'Mode nocturne' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' + ,fr: 'Si activé, la page sera assombire de 22:00 à 6:00' } ,'Enable' : { cs: 'Povoleno' + ,fr: 'activer' } ,'Settings' : { cs: 'Nastavení' + ,fr: 'Paramètres' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' + ,fr: 'Montrer les données BG brutes' } ,'Never' : { cs: 'Nikdy' + ,fr: 'Jamais' } ,'Always' : { cs: 'Vždy' + ,fr: 'Toujours' } ,'When there is noise' : { cs: 'Při šumu' + ,fr: 'Quand il y a du bruit' } ,'When enabled small white dots will be disaplyed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' + ,fr: 'Si activé, des points blancs représenteront les données brutes' } ,'Custom Title' : { cs: 'Vlastní název stránky' + ,fr: 'Titre sur mesure' } ,'Theme' : { cs: 'Téma' + ,fr: 'Thème' } ,'Default' : { cs: 'Výchozí' + ,fr: 'Par défaut' } ,'Colors' : { cs: 'Barevné' + ,fr: 'Couleurs' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' + ,fr: 'Remise à zéro et utilisation des valeurs par défaut' } ,'Calibrations' : { cs: 'Kalibrace' + ,fr: 'Calibration' } ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' + ,fr: 'Test alarme' } ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' + ,fr: 'Calculateur de bolus' } ,'in the future' : { cs: 'v budoucnosti' + ,fr: 'dans le futur' } ,'time ago' : { cs: 'min zpět' + ,fr: 'temps avant' } ,'hr ago' : { cs: 'hod zpět' + ,fr: 'hr avant' + } ,'hrs ago' : { cs: 'hod zpět' + ,fr: 'hrs avant' } ,'min ago' : { cs: 'min zpět' + ,fr: 'min avant' } ,'mins ago' : { cs: 'min zpět' + ,fr: 'mins avant' } ,'day ago' : { cs: 'den zpět' + ,fr: 'jour avant' } ,'days ago' : { cs: 'dnů zpět' + ,fr: 'jours avant' } ,'long ago' : { cs: 'dlouho zpět' + ,fr: 'il y a très longtemps...' } ,'Clean' : { cs: 'Čistý' + ,fr: 'Propre' } ,'Light' : { cs: 'Lehký' + ,fr: 'Léger' } ,'Medium' : { cs: 'Střední' + ,fr: 'Moyen' } ,'Heavy' : { cs: 'Velký' + ,fr: 'Important' } ,'Treatment type' : { cs: 'Typ ošetření' + ,fr: 'Type de traitement' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' + ,fr: 'BG brut' } ,'Device' : { cs: 'Zařízení' + ,fr: 'Appareil' } ,'Noise' : { cs: 'Šum' + ,fr: 'Bruit' } ,'Calibration' : { cs: 'Kalibrace' + ,fr: 'Calibration' } ,'Show Plugins' : { cs: 'Zobrazuj pluginy' diff --git a/static/index.html b/static/index.html index e6bf3fdabd3..f01f6f4feb7 100644 --- a/static/index.html +++ b/static/index.html @@ -223,7 +223,7 @@ - + :
    From 1aa8a67eca9f30bcbed3bcc59fabe40284ef1a2a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 14:13:29 -0700 Subject: [PATCH 605/661] ; --- lib/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/settings.js b/lib/settings.js index b6b4165f5d9..08e1dedf8b2 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -123,7 +123,7 @@ function init ( ) { if (settings.enable && typeof feature === 'object' && feature.length !== undefined) { enabled = _.find(feature, function eachFeature (f) { - return settings.enable.indexOf(f) > -1 + return settings.enable.indexOf(f) > -1; }) !== undefined; } else { enabled = settings.enable && settings.enable.indexOf(feature) > -1; From 3b5cb23c80db41603ffaed92463f28cdd340d9da Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 18:32:02 -0700 Subject: [PATCH 606/661] added language selection to the settings panel --- lib/client/browser-settings.js | 2 ++ lib/language.js | 30 ++++++++++++++++++++++++++++++ static/index.html | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/lib/client/browser-settings.js b/lib/client/browser-settings.js index bb43ec2555a..f69cf7a160b 100644 --- a/lib/client/browser-settings.js +++ b/lib/client/browser-settings.js @@ -48,6 +48,7 @@ function init (client, plugins, serverSettings, $) { $('#theme-default-browser').prop('checked', true); } + $('#language').val(settings.language); if (settings.timeFormat === 24) { $('#24-browser').prop('checked', true); @@ -110,6 +111,7 @@ function init (client, plugins, serverSettings, $) { customTitle: $('input#customTitle').prop('value'), theme: $('input:radio[name=theme-browser]:checked').val(), timeFormat: $('input:radio[name=timeformat-browser]:checked').val(), + language: $('#language').val(), showPlugins: checkedPluginNames() }); diff --git a/lib/language.js b/lib/language.js index 526385652f7..0c9659a64ea 100644 --- a/lib/language.js +++ b/lib/language.js @@ -19,6 +19,36 @@ function init() { ,hr: 'Slušanje na portu' } // Client + ,'Language' : { + + } + , 'Bulgarian': { + + } + , 'Croatian': { + + } + , 'Czech': { + + } + , 'English': { + + } + , 'French': { + + } + , 'German': { + + } + , 'Portuguese (Brazil)': { + + } + , 'Romanian': { + + } + , 'Spanish': { + + } ,'Mo' : { cs: 'Po' ,de: 'Mo' diff --git a/static/index.html b/static/index.html index 83f11eaecbe..8f6bec8d1de 100644 --- a/static/index.html +++ b/static/index.html @@ -96,6 +96,22 @@
    +
    +
    Language
    +
    + +
    +
    Enable Alarms
    From deab2b927d36daca6ecd6c74845cfde3f682a29f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 15 Aug 2015 18:37:33 -0700 Subject: [PATCH 607/661] make codacy happy --- lib/plugins/treatmentnotify.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/plugins/treatmentnotify.js b/lib/plugins/treatmentnotify.js index 32143e5ec93..167c895b83d 100644 --- a/lib/plugins/treatmentnotify.js +++ b/lib/plugins/treatmentnotify.js @@ -101,7 +101,9 @@ function init() { } function isCurrent(last) { - if (!last) return false; + if (!last) { + return false; + } var now = Date.now(); var lastTime = last.mills; From 6a60df5de437207019ae4d941e7d060673eaf49f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 00:48:41 -0700 Subject: [PATCH 608/661] include stale data in the adjustment used to transition the threshold lines --- lib/client/chart.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index b5966e9e417..c1dce960a3c 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -138,6 +138,19 @@ function init (client, d3, $) { chart.context.append('g') .attr('class', 'y axis'); + function createAdjustedRange() { + var range = chart.brush.extent().slice(); + + var end = range[1].getTime() + client.forecastTime; + if (!chart.inRetroMode()) { + var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; + end += (client.now - lastSGVMills); + } + range[1] = new Date(end); + + return range; + } + chart.inRetroMode = function inRetroMode() { if (!chart.brush || !chart.xScale2) { return false; @@ -169,8 +182,7 @@ function init (client, d3, $) { var contextHeight = chart.contextHeight = chartHeight * .2; // get current brush extent - var currentBrushExtent = chart.brush.extent().slice(); - currentBrushExtent[1] = new Date(currentBrushExtent[1].getTime() + client.forecastTime); + var currentBrushExtent = createAdjustedRange(); // only redraw chart if chart size has changed if ((client.prevChartWidth !== chartWidth) || (client.prevChartHeight !== chartHeight)) { @@ -453,20 +465,9 @@ function init (client, d3, $) { }, DEBOUNCE_MS); - function updateDomain() { - var range = chart.brush.extent().slice(); - - var end = range[1].getTime() + client.forecastTime; - if (!chart.inRetroMode()) { - var lastSGVMills = client.latestSGV ? client.latestSGV.mills : client.now; - end += (client.now - lastSGVMills); - } - range[1] = new Date(end); - chart.xScale.domain(range); - } - chart.scroll = function scroll (nowDate) { - updateDomain(); + chart.xScale.domain(createAdjustedRange()); + // remove all insulin/carb treatment bubbles so that they can be redrawn to correct location d3.selectAll('.path').remove(); From 8524f8c5724e94c4b8346d417a5d60996c5e429f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 01:24:04 -0700 Subject: [PATCH 609/661] hide the upbat pill if there is no uploader batter value --- lib/plugins/upbat.js | 1 + tests/upbat.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/lib/plugins/upbat.js b/lib/plugins/upbat.js index 0276f297e0c..8bf0127528c 100644 --- a/lib/plugins/upbat.js +++ b/lib/plugins/upbat.js @@ -50,6 +50,7 @@ function init() { value: prop && prop.display , labelClass: prop && prop.level && 'icon-battery-' + prop.level , pillClass: prop && prop.status + , hide: !(prop && prop.value && prop.value >= 0) }); }; diff --git a/tests/upbat.test.js b/tests/upbat.test.js index ac7f095b9e1..27f227f7f19 100644 --- a/tests/upbat.test.js +++ b/tests/upbat.test.js @@ -43,6 +43,36 @@ describe('Uploader Battery', function ( ) { }); + it('hide the pill if there is no uploader battery status', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.hide.should.equal(true); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(clientSettings, Date.now(), pluginBase, {}); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + }); + + it('hide the pill if there is uploader battery status is -1', function (done) { + var pluginBase = { + updatePillText: function mockedUpdatePillText (plugin, options) { + options.hide.should.equal(true); + done(); + } + }; + + var sandbox = require('../lib/sandbox')(); + var sbx = sandbox.clientInit(clientSettings, Date.now(), pluginBase, {uploaderBattery: -1}); + var upbat = require('../lib/plugins/upbat')(); + upbat.setProperties(sbx); + upbat.updateVisualisation(sbx); + }); + }); From afd0202b29c82d7186237a7db11d2ba501d8e071 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 11:09:14 -0700 Subject: [PATCH 610/661] 0.8 beta1 bump --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 0d720dfa593..64b94a2a75f 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.8.0-dev", + "version": "0.8.0-beta1", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 59403f1f22c..f21f1952251 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.8.0-dev", + "version": "0.8.0-beta1", "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": "AGPL3", "author": "Nightscout Team", From 13a5a90e19ec28197cc5cc172cf1c31895efbf47 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Aug 2015 23:10:37 +0300 Subject: [PATCH 611/661] Send BWP to Pebble --- lib/pebble.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/pebble.js b/lib/pebble.js index e96fe8af32f..943ca0dcb1a 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -6,6 +6,7 @@ var sandbox = require('./sandbox')(); var units = require('./units')(); var iob = require('./plugins/iob')(); var delta = require('./plugins/delta')(); +var bwp = require('./plugins/boluswizardpreview')(); var DIRECTIONS = { NONE: 0 @@ -84,6 +85,14 @@ function prepareSGVs (req, sbx) { if (iobResult) { bgs[0].iob = iobResult.display; } + + sbx.properties.iob = iobResult; + var bwpResult = bwp.calc(sbx); + console.log(bwpResult); + if (bwpResult) { + bgs[0].bwp = bwpResult.bolusEstimateDisplay; + bgs[0].bwpo = bwpResult.outcomeDisplay; + } } } From 390af0e2106ecb17e5481f326e6f5646ae6e3489 Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sun, 16 Aug 2015 23:14:15 +0300 Subject: [PATCH 612/661] Remove logging --- lib/pebble.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index 943ca0dcb1a..a49710b19da 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -88,7 +88,6 @@ function prepareSGVs (req, sbx) { sbx.properties.iob = iobResult; var bwpResult = bwp.calc(sbx); - console.log(bwpResult); if (bwpResult) { bgs[0].bwp = bwpResult.bolusEstimateDisplay; bgs[0].bwpo = bwpResult.outcomeDisplay; From 76c40c556f7d1421bad6a1858f839046dc43147d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 22:59:49 -0700 Subject: [PATCH 613/661] dynamical resize chart top and height based on bottom of the pills --- lib/client/index.js | 29 ++++++++++++++++++----- static/css/main.css | 58 --------------------------------------------- 2 files changed, 23 insertions(+), 64 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 68466dca7f7..47614614d38 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -73,12 +73,6 @@ client.init = function init(serverSettings, plugins) { client.renderer = require('./renderer')(client, d3, $); client.careportal = require('./careportal')(client, $); - client.dataExtent = function dataExtent ( ) { - return client.data.length > 0 ? - d3.extent(client.data, client.entryToDate) - : d3.extent([new Date(client.now - times.hours(48).msecs), new Date(client.now)]); - }; - var timeAgo = client.utils.timeAgo; var container = $('.container') @@ -89,6 +83,16 @@ client.init = function init(serverSettings, plugins) { , statusPills = $('.status .statusPills') ; + client.dataExtent = function dataExtent ( ) { + return client.data.length > 0 ? + d3.extent(client.data, client.entryToDate) + : d3.extent([new Date(client.now - times.hours(48).msecs), new Date(client.now)]); + }; + + client.bottomOfPills = function bottomOfPills ( ) { + return minorPills.offset().top + minorPills.height(); + }; + function formatTime(time, compact) { var timeFormat = getTimeFormat(false, compact); time = d3.time.format(timeFormat)(time); @@ -266,6 +270,8 @@ client.init = function init(serverSettings, plugins) { function updatePlugins (sgvs, time) { var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip); + var preBottomOfPills = client.bottomOfPills(); + client.sbx = sandbox.clientInit( client.settings , new Date(time).getTime() //make sure we send a timestamp @@ -283,6 +289,17 @@ client.init = function init(serverSettings, plugins) { //only shown plugins get a chance to update visualisations plugins.updateVisualisations(client.sbx); + + if (client.bottomOfPills() != preBottomOfPills) { + chart.update(false); + } + + var chartContainer = $('#chartContainer'); + + console.info('>>>>updatePlugins client.bottomOfPills()', client.bottomOfPills()); + chartContainer.css('top', (client.bottomOfPills() + 10) + 'px'); + chartContainer.find('svg').css('height', 'calc(100vh - ' + (client.bottomOfPills() + 10)+ 'px)'); + } function clearCurrentSGV ( ) { diff --git a/static/css/main.css b/static/css/main.css index f2c469594d6..1c87a86584f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -195,7 +195,6 @@ body { } #chartContainer { - top: 225px; /*(toolbar height + status height)*/ left:0; right:0; bottom:0; @@ -206,14 +205,8 @@ body { position:absolute; } -.has-minor-pills #chartContainer { - top: 245px; -} - #chartContainer svg { - height: calc(100vh - (180px + 45px)); width: 100%; - top: calc(45px + 180px); } #silenceBtn { @@ -392,22 +385,8 @@ body { } #chartContainer { - top: 165px; font-size: 14px; } - - #chartContainer svg { - height: calc(100vh - 165px); - } - - .has-minor-pills #chartContainer { - top: 175px; - } - - #chartContainer svg { - height: calc(100vh - 175px); - } - } @media (max-width: 750px) { @@ -451,14 +430,6 @@ body { line-height: 35px; width: 250px; } - - #chartContainer { - top: 175px; - } - #chartContainer svg { - height: calc(100vh - 165px); - } - } @media (max-width: 400px) { @@ -544,22 +515,6 @@ body { .focus-range li { display: block; } - - #chartContainer { - top: 190px; - } - - #chartContainer svg { - height: calc(100vh - (190px)); - } - - .has-minor-pills #chartContainer { - top: 210px; - } - - .has-minor-pills #chartContainer svg { - height: calc(100vh - (210px)); - } } @media (max-height: 700px) { @@ -590,21 +545,8 @@ body { } #chartContainer { - top: 130px; font-size: 10px; } - - #chartContainer svg { - height: calc(100vh - 130px); - } - - .has-minor-pills #chartContainer { - top: 140px; - } - - .has-minor-pills #chartContainer svg { - height: calc(100vh - 140px); - } } @media (max-height: 480px) and (min-width: 400px) { From f930610719e7bf97add8e2ba01d682529cc11494 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 16 Aug 2015 23:05:56 -0700 Subject: [PATCH 614/661] delete pushover receipts after they are used so alarms can't be acked multiple times --- lib/pushnotify.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pushnotify.js b/lib/pushnotify.js index 5286b9ca0bb..3117f936fa1 100644 --- a/lib/pushnotify.js +++ b/lib/pushnotify.js @@ -58,6 +58,7 @@ function init(env, ctx) { console.info('push ack, response: ', response, ', notify: ', notify); if (notify) { ctx.notifications.ack(notify.level, times.mins(30).msecs, true); + receipts.del(response.receipt); } return !!notify; }; From 791e4f22fad61134f0fd0dd91f1d8339d601e9c2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 17 Aug 2015 00:02:09 -0700 Subject: [PATCH 615/661] Italian translations from @francescaneri --- lib/language.js | 806 ++++++++++++++++++++++++++++++---------------- static/index.html | 1 + 2 files changed, 530 insertions(+), 277 deletions(-) diff --git a/lib/language.js b/lib/language.js index 3ac555045b0..cafe54b7f4f 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2,7 +2,7 @@ function init() { var lang; - + function language() { return language; } @@ -18,41 +18,33 @@ function init() { ,ro: 'Activ pe portul' ,bg: 'Активиране на порта' ,hr: 'Slušanje na portu' + ,it: 'Porta in ascolto' } // Client ,'Language' : { - - } + } , 'Bulgarian': { - - } + } , 'Croatian': { - - } + } , 'Czech': { - - } + } , 'English': { - - } + } , 'French': { - - } + } , 'German': { - - } + } + , 'Italian': { + } , 'Portuguese (Brazil)': { - - } + } , 'Romanian': { - - } + } , 'Spanish': { - - } + } , 'Swedish': { - - } + } ,'Mo' : { cs: 'Po' ,de: 'Mo' @@ -63,6 +55,7 @@ function init() { ,ro: 'Lu' ,bg: 'Пон' ,hr: 'Pon' + ,it: 'Lun' } ,'Tu' : { cs: 'Út' @@ -74,6 +67,7 @@ function init() { ,ro: 'Ma' ,bg: 'Вт' ,hr: 'Ut' + ,it: 'Mar' }, ',We' : { cs: 'St' @@ -85,6 +79,7 @@ function init() { ,ro: 'Mie' ,bg: 'Ср' ,hr: 'Sri' + ,it: 'Mer' } ,'Th' : { cs: 'Čt' @@ -96,6 +91,7 @@ function init() { ,ro: 'Jo' ,bg: 'Четв' ,hr: 'Čet' + ,it: 'Gio' } ,'Fr' : { cs: 'Pá' @@ -107,6 +103,7 @@ function init() { ,ro: 'Vi' ,bg: 'Пет' ,hr: 'Pet' + ,it: 'Ven' } ,'Sa' : { cs: 'So' @@ -118,6 +115,7 @@ function init() { ,ro: 'Sa' ,bg: 'Съб' ,hr: 'Sub' + ,it: 'Sab' } ,'Su' : { cs: 'Ne' @@ -129,6 +127,7 @@ function init() { ,ro: 'Du' ,bg: 'Нед' ,hr: 'Ned' + ,it: 'Dom' } ,'Monday' : { cs: 'Pondělí' @@ -140,6 +139,7 @@ function init() { ,ro: 'Luni' ,bg: 'Понеделник' ,hr: 'Ponedjeljak' + ,it: 'Lunedì' } ,'Tuesday' : { cs: 'Úterý' @@ -151,6 +151,7 @@ function init() { ,bg: 'Вторник' ,hr: 'Utorak' ,sv: 'Tisdag' + ,it: 'Martedì' } ,'Wednesday' : { cs: 'Středa' @@ -162,6 +163,7 @@ function init() { ,ro: 'Miercuri' ,bg: 'Сряда' ,hr: 'Srijeda' + ,it: 'Mercoledì' } ,'Thursday' : { cs: 'Čtvrtek' @@ -173,6 +175,7 @@ function init() { ,ro: 'Joi' ,bg: 'Четвъртък' ,hr: 'Četvrtak' + ,it: 'Giovedì' } ,'Friday' : { cs: 'Pátek' @@ -184,6 +187,7 @@ function init() { ,es: 'Viernes' ,bg: 'Петък' ,hr: 'Petak' + ,it: 'Venerdì' } ,'Saturday' : { cs: 'Sobota' @@ -195,6 +199,7 @@ function init() { ,bg: 'Събота' ,hr: 'Subota' ,sv: 'Lördag' + ,it: 'Sabato' } ,'Sunday' : { cs: 'Neděle' @@ -206,6 +211,7 @@ function init() { ,bg: 'Неделя' ,hr: 'Nedjelja' ,sv: 'Söndag' + ,it: 'Domenica' } ,'Category' : { cs: 'Kategorie' @@ -217,8 +223,9 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' + ,it:'Categoria' } - ,'Subcategory' : { + ,'Subcategory' : { cs: 'Podkategorie' ,de: 'Unterkategorie' ,es: 'Subcategoría' @@ -228,8 +235,9 @@ function init() { ,ro: 'Subcategorie' ,bg: 'Подкатегория' ,hr: 'Podkategorija' + ,it: 'Sottocategoria' } - ,'Name' : { + ,'Name' : { cs: 'Jméno' ,de: 'Name' ,es: 'Nombre' @@ -239,8 +247,9 @@ function init() { ,ro: 'Nume' ,bg: 'Име' ,hr: 'Ime' + ,it: 'Nome' } - ,'Today' : { + ,'Today' : { cs: 'Dnes' ,de: 'Heute' ,es: 'Hoy' @@ -250,8 +259,9 @@ function init() { ,bg: 'Днес' ,hr: 'Danas' ,sv: 'Idag' + ,it: 'Oggi' } - ,'Last 2 days' : { + ,'Last 2 days' : { cs: 'Poslední 2 dny' ,de: 'letzten 2 Tage' ,es: 'Últimos 2 días' @@ -261,8 +271,9 @@ function init() { ,bg: 'Последните 2 дни' ,hr: 'Posljednja 2 dana' ,sv: 'Senaste 2 dagarna' + ,it: 'Ultimi 2 giorni' } - ,'Last 3 days' : { + ,'Last 3 days' : { cs: 'Poslední 3 dny' ,de: 'letzten 3 Tage' ,es: 'Últimos 3 días' @@ -272,8 +283,9 @@ function init() { ,ro: 'Ultimele 3 zile' ,bg: 'Последните 3 дни' ,hr: 'Posljednja 3 dana' + ,it: 'Ultimi 3 giorni' } - ,'Last week' : { + ,'Last week' : { cs: 'Poslední týden' ,de: 'letzte Woche' ,es: 'Semana pasada' @@ -283,8 +295,9 @@ function init() { ,bg: 'Последната седмица' ,hr: 'Protekli tjedan' ,sv: 'Senaste veckan' + ,it: 'Settimana scorsa' } - ,'Last 2 weeks' : { + ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' ,de: 'letzten 2 Wochen' ,es: 'Últimas 2 semanas' @@ -294,8 +307,9 @@ function init() { ,bg: 'Последните 2 седмици' ,hr: 'Protekla 2 tjedna' ,sv: 'Senaste 2 veckorna' + ,it: 'Ultime 2 settimane' } - ,'Last month' : { + ,'Last month' : { cs: 'Poslední měsíc' ,de: 'letzter Monat' ,es: 'Mes pasado' @@ -305,8 +319,9 @@ function init() { ,bg: 'Последният месец' ,hr: 'Protekli mjesec' ,sv: 'Senaste månaden' + ,it: 'Mese scorso' } - ,'Last 3 months' : { + ,'Last 3 months' : { cs: 'Poslední 3 měsíce' ,de: 'letzten 3 Monate' ,es: 'Últimos 3 meses' @@ -316,8 +331,9 @@ function init() { ,bg: 'Последните 3 месеца' ,hr: 'Protekla 3 mjeseca' ,sv: 'Senaste 3 månaderna' + ,it: 'Ultimi 3 mesi' } - ,'From' : { + ,'From' : { cs: 'Od' ,de: 'Von' ,es: 'Desde' @@ -327,8 +343,9 @@ function init() { ,ro: 'De la' ,bg: 'От' ,hr: 'Od' + ,it: 'Da' } - ,'To' : { + ,'To' : { cs: 'Do' ,de: 'Bis' ,es: 'Hasta' @@ -338,8 +355,9 @@ function init() { ,bg: 'До' ,hr: 'Do' ,sv: 'Till' + ,it: 'A' } - ,'Notes' : { + ,'Notes' : { cs: 'Poznámky' ,de: 'Notiz' ,es: 'Notas' @@ -349,8 +367,9 @@ function init() { ,ro: 'Note' ,bg: 'Бележки' ,hr: 'Bilješke' + ,it: 'Note' } - ,'Food' : { + ,'Food' : { cs: 'Jídlo' ,de: 'Essen' ,es: 'Comida' @@ -360,8 +379,9 @@ function init() { ,ro: 'Mâncare' ,bg: 'Храна' ,hr: 'Hrana' + ,it: 'Cibo' } - ,'Insulin' : { + ,'Insulin' : { cs: 'Inzulín' ,de: 'Insulin' ,es: 'Insulina' @@ -371,8 +391,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Inzulin' ,sv: 'Insulin' + ,it: 'Insulina' } - ,'Carbs' : { + ,'Carbs' : { cs: 'Sacharidy' ,de: 'Kohlenhydrate' ,es: 'Hidratos de carbono' @@ -382,8 +403,9 @@ function init() { ,bg: 'Въглехидрати' ,hr: 'Ugljikohidrati' ,sv: 'Kolhydrater' + ,it: 'Carboidrati' } - ,'Notes contain' : { + ,'Notes contain' : { cs: 'Poznámky obsahují' ,de: 'Erläuterungen' ,es: 'Contenido de las notas' @@ -393,8 +415,9 @@ function init() { ,bg: 'бележките съдържат' ,hr: 'Sadržaj bilješki' ,sv: 'Notering innehåller' + ,it: 'Contiene note' } - ,'Event type contains' : { + ,'Event type contains' : { cs: 'Typ události obsahuje' ,de: 'Ereignis-Typ beinhaltet' ,es: 'Contenido del tipo de evento' @@ -404,8 +427,9 @@ function init() { ,bg: 'Типа събитие включва' ,hr: 'Sadržaj vrste događaja' ,sv: 'Händelsen innehåller' + ,it: 'Contiene evento' } - ,'Target bg range bottom' : { + ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' ,de: 'Untergrenze des Blutzuckerzielbereiches' ,es: 'Objetivo inferior de glucemia' @@ -415,8 +439,9 @@ function init() { ,bg: 'Долна граница на КЗ' ,hr: 'Ciljna donja granica GUK-a' ,sv: 'Gräns för nedre blodsockervärde' + ,it: 'Limite inferiore della glicemia' } - ,'top' : { + ,'top' : { cs: 'horní' ,de: 'oben' ,es: 'Superior' @@ -426,8 +451,9 @@ function init() { ,bg: 'горе' ,hr: 'Gornja' ,sv: 'Toppen' + ,it: 'Superiore' } - ,'Show' : { + ,'Show' : { cs: 'Zobraz' ,de: 'Zeigen' ,es: 'Mostrar' @@ -436,8 +462,9 @@ function init() { ,ro: 'Arată' ,bg: 'Покажи' ,hr: 'Prikaži' + ,it: 'Mostra' } - ,'Display' : { + ,'Display' : { cs: 'Zobraz' ,de: 'Darstellen' ,es: 'Visualizar' @@ -447,8 +474,9 @@ function init() { ,bg: 'Покажи' ,hr: 'Prikaži' ,sv: 'Visa' + ,it: 'Schermo' } - ,'Loading' : { + ,'Loading' : { cs: 'Nahrávám' ,de: 'Laden' ,es: 'Cargando' @@ -458,8 +486,9 @@ function init() { ,bg: 'Зареждане' ,hr: 'Učitavanje' ,sv: 'Laddar' + ,it: 'Sto Caricando' } - ,'Loading profile' : { + ,'Loading profile' : { cs: 'Nahrávám profil' ,de: 'Lade Profil' ,es: 'Cargando perfil' @@ -469,8 +498,9 @@ function init() { ,ro: 'Încarc profilul' ,bg: 'Зареждане на профил' ,hr: 'Učitavanje profila' + ,it: 'Sto Caricando il profilo' } - ,'Loading status' : { + ,'Loading status' : { cs: 'Nahrávám status' ,de: 'Lade Status' ,es: 'Cargando estado' @@ -480,8 +510,9 @@ function init() { ,ro: 'Încarc statusul' ,bg: 'Зареждане на статус' ,hr: 'Učitavanje statusa' + ,it: 'Stato di caricamento' } - ,'Loading food database' : { + ,'Loading food database' : { cs: 'Nahrávám databázi jídel' ,de: 'Lade Nahrungsmittel-Datenbank' ,es: 'Cargando base de datos de alimentos' @@ -491,8 +522,9 @@ function init() { ,ro: 'Încarc baza de date de alimente' ,bg: 'Зареждане на данни за храни' ,hr: 'Učitavanje baze podataka o hrani' + ,it: 'Carico dati alimenti' } - ,'not displayed' : { + ,'not displayed' : { cs: 'není zobrazeno' ,de: 'nicht angezeigt' ,es: 'No mostrado' @@ -502,8 +534,9 @@ function init() { ,bg: 'Не се показва' ,hr: 'Ne prikazuje se' ,sv: 'Visas ej' + ,it: 'Non visualizzato' } - ,'Loading CGM data of' : { + ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' ,de: 'Lade CGM-Daten von' ,es: 'Cargando datos de CGM de' @@ -513,8 +546,9 @@ function init() { ,ro: 'Încarc datele CGM ale lui' ,bg: 'Зареждане на CGM данни от' ,hr: 'Učitavanja podataka CGM-a' + ,it: 'Carico dati CGM' } - ,'Loading treatments data of' : { + ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' ,de: 'Lade Behandlungsdaten von' ,es: 'Cargando datos de tratamientos de' @@ -524,8 +558,9 @@ function init() { ,ro: 'Încarc datele despre tratament pentru' ,bg: 'Зареждане на въведените лечения от' ,hr: 'Učitavanje podataka o tretmanu' + ,it: 'Carico trattamenti dei dati di' } - ,'Processing data of' : { + ,'Processing data of' : { cs: 'Zpracovávám data' ,de: 'Verarbeite Daten von' ,es: 'Procesando datos de' @@ -535,8 +570,9 @@ function init() { ,ro: 'Procesez datele lui' ,bg: 'Зареждане на данни от' ,hr: 'Obrada podataka' + ,it: 'Elaborazione dei dati di' } - ,'Portion' : { + ,'Portion' : { cs: 'Porce' ,de: 'Portion' ,es: 'Porción' @@ -546,8 +582,9 @@ function init() { ,bg: 'Порция' ,hr: 'Dio' ,sv: 'Portion' + ,it: 'Porzione' } - ,'Size' : { + ,'Size' : { cs: 'Rozměr' ,de: 'Größe' ,es: 'Tamaño' @@ -556,18 +593,20 @@ function init() { ,ro: 'Mărime' ,bg: 'Големина' ,hr: 'Veličina' + ,it: 'Formato' } - ,'(none)' : { + ,'(none)' : { cs: '(Prázdný)' ,de: '(nichts)' ,es: '(ninguno)' ,fr: '(aucun)' ,pt: '(nenhum)' ,ro: '(fără)' - ,bg: 'няма' + ,bg: '(няма)' ,hr: '(Prazno)' + ,it: '(Nessuno)' } - ,'Result is empty' : { + ,'Result is empty' : { cs: 'Prázdný výsledek' ,de: 'Leeres Ergebnis' ,es: 'Resultado vacío' @@ -576,9 +615,10 @@ function init() { ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' + ,it: 'Risultato vuoto' } // ported reporting - ,'Day to day' : { + ,'Day to day' : { cs: 'Den po dni' ,de: 'Von Tag zu Tag' ,es: 'Día a día' @@ -588,8 +628,9 @@ function init() { ,ro: 'Zi cu zi' ,bg: 'Ден за ден' ,hr: 'Svakodnevno' + ,it: 'Giorno per giorno' } - ,'Daily Stats' : { + ,'Daily Stats' : { cs: 'Denní statistiky' ,de: 'Tägliche Statistik' ,es: 'Estadísticas diarias' @@ -599,8 +640,9 @@ function init() { ,ro: 'Statistici zilnice' ,bg: 'Дневна статистика' ,hr: 'Dnevna statistika' + ,it: 'Statistiche giornaliere' } - ,'Percentile Chart' : { + ,'Percentile Chart' : { cs: 'Percentil' ,de: 'Durchschnittswert' ,es: 'Percentiles' @@ -610,8 +652,9 @@ function init() { ,bg: 'Процентна графика' ,hr: 'Tablica u postotcima' ,sv: 'Procentgraf' + ,it: 'Grafico percentile' } - ,'Distribution' : { + ,'Distribution' : { cs: 'Rozložení' ,de: 'Streuung' ,es: 'Distribución' @@ -621,8 +664,9 @@ function init() { ,bg: 'Разпределение' ,hr: 'Distribucija' ,sv: 'Distribution' - } - ,'Hourly stats' : { + ,it: 'Distribuzione' + } + ,'Hourly stats' : { cs: 'Statistika po hodinách' ,de: 'Stündliche Statistik' ,es: 'Estadísticas por hora' @@ -632,8 +676,9 @@ function init() { ,ro: 'Statistici orare' ,bg: 'Статистика по часове' ,hr: 'Statistika po satu' - } - ,'Weekly success' : { + ,it: 'Statistiche per ore' + } + ,'Weekly success' : { cs: 'Statistika po týdnech' ,de: 'Wöchentlicher Erfolg' ,es: 'Resultados semanales' @@ -643,8 +688,9 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,it: 'Statistiche settimanali' } - ,'No data available' : { + ,'No data available' : { cs: 'Žádná dostupná data' ,de: 'Keine Daten verfügbar' ,es: 'No hay datos disponibles' @@ -654,8 +700,9 @@ function init() { ,bg: 'Няма данни за показване' ,hr: 'Nema raspoloživih podataka' ,sv: 'Data saknas' + ,it: 'Dati non disponibili' } - ,'Low' : { + ,'Low' : { cs: 'Nízká' ,de: 'Tief' ,es: 'Bajo' @@ -665,8 +712,9 @@ function init() { ,ro: 'Prea jos' ,bg: 'Ниска' ,hr: 'Nizak' + ,it: 'Basso' } - ,'In Range' : { + ,'In Range' : { cs: 'V rozsahu' ,de: 'Im Zielbereich' ,es: 'En rango' @@ -676,8 +724,9 @@ function init() { ,ro: 'În interval' ,bg: 'В граници' ,hr: 'U rasponu' + ,it: 'In intervallo' } - ,'Period' : { + ,'Period' : { cs: 'Období' ,de: 'Zeitraum' ,es: 'Periodo' @@ -687,8 +736,9 @@ function init() { ,ro: 'Perioada' ,bg: 'Период' ,hr: 'Period' + ,it: 'Periodo' } - ,'High' : { + ,'High' : { cs: 'Vysoká' ,de: 'Hoch' ,es: 'Alto' @@ -698,8 +748,9 @@ function init() { ,ro: 'Prea sus' ,bg: 'Висока' ,hr: 'Visok' + ,it: 'Alto' } - ,'Average' : { + ,'Average' : { cs: 'Průměrná' ,de: 'Mittelwert' ,es: 'Media' @@ -709,8 +760,9 @@ function init() { ,ro: 'Media' ,bg: 'Средна' ,hr: 'Prosjek' + ,it: 'Media' } - ,'Low Quartile' : { + ,'Low Quartile' : { cs: 'Nízký kvartil' ,de: 'Unteres Quartil' ,es: 'Cuartil inferior' @@ -720,8 +772,9 @@ function init() { ,bg: 'Ниска четвъртинка' ,hr: 'Donji kvartil' ,sv: 'Nedre kvadranten' + ,it: 'Quartile basso' } - ,'Upper Quartile' : { + ,'Upper Quartile' : { cs: 'Vysoký kvartil' ,de: 'Oberes Quartil' ,es: 'Cuartil superior' @@ -731,8 +784,9 @@ function init() { ,bg: 'Висока четвъртинка' ,hr: 'Gornji kvartil' ,sv: 'Övre kvadranten' + ,it: 'Quartile alto' } - ,'Quartile' : { + ,'Quartile' : { cs: 'Kvartil' ,de: 'Quartil' ,es: 'Cuartil' @@ -741,8 +795,9 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,it: 'Quartile' } - ,'Date' : { + ,'Date' : { cs: 'Datum' ,de: 'Datum' ,es: 'Fecha' @@ -752,8 +807,9 @@ function init() { ,ro: 'Data' ,bg: 'Дата' ,hr: 'Datum' + ,it: 'Data' } - ,'Normal' : { + ,'Normal' : { cs: 'Normální' ,de: 'Normal' ,es: 'Normal' @@ -763,8 +819,9 @@ function init() { ,ro: 'Normal' ,bg: 'Нормално' ,hr: 'Normalno' + ,it: 'Normale' } - ,'Median' : { + ,'Median' : { cs: 'Medián' ,de: 'Median' ,es: 'Mediana' @@ -774,8 +831,9 @@ function init() { ,bg: 'Средно' ,hr: 'Srednje' ,sv: 'Median' + ,it: 'Mediana' } - ,'Readings' : { + ,'Readings' : { cs: 'Záznamů' ,de: 'Messwerte' ,es: 'Valores' @@ -785,8 +843,9 @@ function init() { ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' + ,it: 'Valori' } - ,'StDev' : { + ,'StDev' : { cs: 'St. odchylka' ,de: 'Standardabweichung' ,es: 'Desviación estándar' @@ -796,8 +855,9 @@ function init() { ,ro: 'Dev Std' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' + ,it: 'Deviazione standard' } - ,'Daily stats report' : { + ,'Daily stats report' : { cs: 'Denní statistiky' ,de: 'Tagesstatistik Bericht' ,es: 'Informe de estadísticas diarias' @@ -807,8 +867,9 @@ function init() { ,bg: 'Дневна статистика' ,hr: 'Izvješće o dnevnim statistikama' ,sv: 'Dygnsstatistik' + ,it: 'Statistiche giornaliere' } - ,'Glucose Percentile report' : { + ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' ,de: 'Glukose-Prozent Bericht' ,es: 'Informe de percetiles de glucemia' @@ -818,8 +879,9 @@ function init() { ,ro: 'Raport percentile glicemii' ,bg: 'Графика на КЗ' ,hr: 'Izvješće o postotku GUK-a' + ,it: 'Percentuale Glicemie' } - ,'Glucose distribution' : { + ,'Glucose distribution' : { cs: 'Rozložení glykémií' ,de: 'Glukuse Verteilung' ,es: 'Distribución de glucemias' @@ -829,8 +891,9 @@ function init() { ,bg: 'Разпределение на КЗ' ,hr: 'Distribucija GUK-a' ,sv: 'Glukosdistribution' + ,it: 'Distribuzione glicemie' } - ,'days total' : { + ,'days total' : { cs: 'dní celkem' ,de: 'Gesamttage' ,es: 'Total de días' @@ -840,8 +903,9 @@ function init() { ,ro: 'total zile' ,bg: 'общо за деня' ,hr: 'ukupno dana' + ,it: 'Giorni totali' } - ,'Overall' : { + ,'Overall' : { cs: 'Celkem' ,de: 'Insgesamt' ,es: 'General' @@ -851,8 +915,9 @@ function init() { ,ro: 'General' ,bg: 'Общо' ,hr: 'Ukupno' + ,it: 'Generale' } - ,'Range' : { + ,'Range' : { cs: 'Rozsah' ,de: 'Bereich' ,es: 'Intervalo' @@ -862,8 +927,9 @@ function init() { ,ro: 'Interval' ,bg: 'Диапазон' ,hr: 'Raspon' + ,it: 'Intervallo' } - ,'% of Readings' : { + ,'% of Readings' : { cs: '% záznamů' ,de: '% der Messwerte' ,es: '% de valores' @@ -873,8 +939,9 @@ function init() { ,ro: '% de valori' ,bg: '% от измервания' ,hr: '% očitanja' + ,it: '% dei valori' } - ,'# of Readings' : { + ,'# of Readings' : { cs: 'počet záznamů' ,de: '# der Messwerte' ,es: 'N° de valores' @@ -884,8 +951,9 @@ function init() { ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' + ,it: '# di valori' } - ,'Mean' : { + ,'Mean' : { cs: 'Střední hodnota' ,de: 'Durchschnittlich' ,es: 'Media' @@ -895,8 +963,9 @@ function init() { ,ro: 'Medie' ,bg: 'Средна стойност' ,hr: 'Prosjek' + ,it: 'Medio' } - ,'Standard Deviation' : { + ,'Standard Deviation' : { cs: 'Standardní odchylka' ,de: 'Standardabweichung' ,es: 'Desviación estándar' @@ -906,8 +975,9 @@ function init() { ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,sv: 'Standardavvikelse' + ,it: 'Deviazione Standard' } - ,'Max' : { + ,'Max' : { cs: 'Max' ,de: 'Max' ,es: 'Max' @@ -917,8 +987,9 @@ function init() { ,ro: 'Max' ,bg: 'Максимално' ,hr: 'Max' + ,it: 'Max' } - ,'Min' : { + ,'Min' : { cs: 'Min' ,de: 'Min' ,es: 'Min' @@ -928,8 +999,9 @@ function init() { ,ro: 'Min' ,bg: 'Минимално' ,hr: 'Min' + ,it: 'Min' } - ,'A1c estimation*' : { + ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' ,de: 'Prognose HbA1c*' ,es: 'Estimación de HbA1c*' @@ -939,8 +1011,9 @@ function init() { ,bg: 'Очакван HbA1c' ,hr: 'Procjena HbA1c-a' ,sv: 'Beräknat A1c-värde ' + ,it: 'stima A1c' } - ,'Weekly Success' : { + ,'Weekly Success' : { cs: 'Týdenní úspěšnost' ,de: 'Wöchtlicher Erfolg' ,es: 'Resultados semanales' @@ -950,8 +1023,9 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,it: 'Risultati settimanali' } - ,'There is not sufficient data to run this report. Select more days.' : { + ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' ,de: 'Für diesen Bericht sind nicht genug Daten verfügbar. Bitte weitere Tage auswählen' ,es: 'No hay datos suficientes para generar este informe. Seleccione más días.' @@ -961,9 +1035,10 @@ function init() { ,bg: 'Няма достатъчно данни за показване. Изберете повече дни.' ,hr: 'Nema dovoljno podataka za izvođenje izvještaja. Odaberite još dana.' ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' - } + ,it: 'Non ci sono dati sufficienti per eseguire questo rapporto. Selezionare più giorni.' + } // food editor - ,'Using stored API secret hash' : { + ,'Using stored API secret hash' : { cs: 'Používám uložený hash API hesla' ,de: 'Gespeicherte API-Prüfsumme verwenden' ,es: 'Usando el hash del API pre-almacenado' @@ -973,8 +1048,9 @@ function init() { ,bg: 'Използване на запаметена API парола' ,hr: 'Koristi se pohranjeni API tajni hash' ,sv: 'Använd hemlig API-nyckel' + ,it: 'Stai utilizzando API hash segreta' } - ,'No API secret hash stored yet. You need to enter API secret.' : { + ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' ,de: 'Keine API-Prüfsumme gespeichert. Bitte API-Prüfsumme eingeben.' ,es: 'No se ha almacenado ningún hash todavía. Debe introducir su secreto API.' @@ -984,8 +1060,9 @@ function init() { ,bg: 'Няма запаметена API парола. Tрябва да въведете API парола' ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' - } - ,'Database loaded' : { + ,it: 'No API hash segreto ancora memorizzato. È necessario inserire API segreto.' + } + ,'Database loaded' : { cs: 'Databáze načtena' ,de: 'Datenbank geladen' ,es: 'Base de datos cargada' @@ -995,8 +1072,9 @@ function init() { ,bg: 'База с данни заредена' ,hr: 'Baza podataka je učitana' ,sv: 'Databas laddad' + ,it: 'Database caricato' } - ,'Error: Database failed to load' : { + ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' ,de: 'Fehler: Datenbank konnte nicht geladen werden' ,es: 'Error: Carga de base de datos fallida' @@ -1006,8 +1084,9 @@ function init() { ,bg: 'ГРЕШКА. Базата с данни не успя да се зареди' ,hr: 'Greška: Baza podataka nije učitana' ,sv: 'Error: Databas kan ej laddas' + ,it: 'Errore: database non è stato caricato' } - ,'Create new record' : { + ,'Create new record' : { cs: 'Vytvořit nový záznam' ,de: 'Erstelle neuen Datensatz' ,es: 'Crear nuevo registro' @@ -1017,8 +1096,9 @@ function init() { ,bg: 'Създаване на нов запис' ,hr: 'Kreiraj novi zapis' ,sv: 'Skapa ny post' + ,it: 'Crea nuovo registro' } - ,'Save record' : { + ,'Save record' : { cs: 'Uložit záznam' ,de: 'Speichere Datensatz' ,es: 'Guardar registro' @@ -1028,8 +1108,9 @@ function init() { ,bg: 'Запази запис' ,hr: 'Spremi zapis' ,sv: 'Spara post' + ,it: 'Salva Registro' } - ,'Portions' : { + ,'Portions' : { cs: 'Porcí' ,de: 'Portionen' ,es: 'Porciones' @@ -1039,8 +1120,9 @@ function init() { ,bg: 'Порции' ,hr: 'Dijelovi' ,sv: 'Portion' + ,it: 'Porzioni' } - ,'Unit' : { + ,'Unit' : { cs: 'Jedn' ,de: 'Einheit' ,es: 'Unidades' @@ -1050,8 +1132,9 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinica' ,sv: 'Enhet' + ,it: 'Unità' } - ,'GI' : { + ,'GI' : { cs: 'GI' ,de: 'GI' ,es: 'IG' @@ -1061,8 +1144,9 @@ function init() { ,ro: 'CI' ,bg: 'ГИ' ,hr: 'GI' + ,it: 'GI' } - ,'Edit record' : { + ,'Edit record' : { cs: 'Upravit záznam' ,de: 'Bearbeite Datensatz' ,es: 'Editar registro' @@ -1072,8 +1156,9 @@ function init() { ,bg: 'Редактирай запис' ,hr: 'Uredi zapis' ,sv: 'Editera post' + ,it: 'Modifica registro' } - ,'Delete record' : { + ,'Delete record' : { cs: 'Smazat záznam' ,de: 'Lösche Datensatz' ,es: 'Borrar registro' @@ -1083,8 +1168,9 @@ function init() { ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' ,sv: 'Ta bort post' + ,it: 'Cancella registro' } - ,'Move to the top' : { + ,'Move to the top' : { cs: 'Přesuň na začátek' ,de: 'Gehe zum Anfang' ,es: 'Mover arriba' @@ -1094,8 +1180,9 @@ function init() { ,ro: 'Mergi la început' ,bg: 'Преместване в началото' ,hr: 'Premjesti na vrh' + ,it: 'Spostare verso l\'alto' } - ,'Hidden' : { + ,'Hidden' : { cs: 'Skrytý' ,de: 'Versteckt' ,es: 'Oculto' @@ -1105,8 +1192,9 @@ function init() { ,ro: 'Ascuns' ,bg: 'Скрити' ,hr: 'Skriveno' + ,it: 'Nascosto' } - ,'Hide after use' : { + ,'Hide after use' : { cs: 'Skryj po použití' ,de: 'Verberge nach Gebrauch' ,es: 'Ocultar después de utilizar' @@ -1116,8 +1204,9 @@ function init() { ,bg: 'Скрий след употреба' ,hr: 'Sakrij nakon korištenja' ,sv: 'Dölj efter användning' + ,it: 'Nascondi dopo l\'uso' } - ,'Your API secret must be at least 12 characters long' : { + ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' ,de: 'Deine API-Prüfsumme muss mindestens 12 Zeichen lang sein' ,es: 'Su secreo API debe contener al menos 12 caracteres' @@ -1127,8 +1216,9 @@ function init() { ,bg: 'Вашата АPI парола трябва да е дълга поне 12 символа' ,hr: 'Vaš tajni API mora sadržavati barem 12 znakova' ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' + ,it: 'il vostro API secreto deve essere lungo almeno 12 caratteri' } - ,'Bad API secret' : { + ,'Bad API secret' : { cs: 'Chybné API heslo' ,de: 'Fehlerhafte API-Prüfsumme' ,es: 'Secreto API incorrecto' @@ -1138,8 +1228,9 @@ function init() { ,bg: 'Некоректна API парола' ,hr: 'Neispravan tajni API' ,sv: 'Felaktig API-nyckel' + ,it: 'API secreto incorretto' } - ,'API secret hash stored' : { + ,'API secret hash stored' : { cs: 'Hash API hesla uložen' ,de: 'API-Prüfsumme gespeichert' ,es: 'Hash de secreto API guardado' @@ -1149,8 +1240,9 @@ function init() { ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' ,sv: 'Hemlig API-hash lagrad' + ,it: 'Hash API secreto memorizzato' } - ,'Status' : { + ,'Status' : { cs: 'Status' ,de: 'Status' ,es: 'Estado' @@ -1160,8 +1252,9 @@ function init() { ,ro: 'Status' ,bg: 'Статус' ,hr: 'Status' + ,it: 'Stato' } - ,'Not loaded' : { + ,'Not loaded' : { cs: 'Nenačtený' ,de: 'Nicht geladen' ,es: 'No cargado' @@ -1171,8 +1264,9 @@ function init() { ,bg: 'Не е заредено' ,hr: 'Nije učitano' ,sv: 'Ej laddad' + ,it: 'Non caricato' } - ,'Food editor' : { + ,'Food editor' : { cs: 'Editor jídel' ,de: 'Nahrungsmittel Editor' ,es: 'Editor de alimentos' @@ -1182,8 +1276,9 @@ function init() { ,bg: 'Редактор за храна' ,hr: 'Editor hrane' ,sv: 'Födoämneseditor' + ,it: 'Modifica alimenti' } - ,'Your database' : { + ,'Your database' : { cs: 'Vaše databáze' ,de: 'Deine Datenbank' ,es: 'Su base de datos' @@ -1193,8 +1288,9 @@ function init() { ,ro: 'Baza de date' ,bg: 'Твоята база с данни' ,hr: 'Vaša baza podataka' + ,it: 'Vostro database' } - ,'Filter' : { + ,'Filter' : { cs: 'Filtr' ,de: 'Filter' ,es: 'Filtro' @@ -1204,8 +1300,9 @@ function init() { ,ro: 'Filtru' ,bg: 'Филтър' ,hr: 'Filter' + ,it: 'Filtro' } - ,'Save' : { + ,'Save' : { cs: 'Ulož' ,de: 'Speichern' ,es: 'Salvar' @@ -1215,8 +1312,9 @@ function init() { ,bg: 'Запази' ,hr: 'Spremi' ,sv: 'Spara' + ,it: 'Salva' } - ,'Clear' : { + ,'Clear' : { cs: 'Vymaž' ,de: 'Löschen' ,es: 'Limpiar' @@ -1226,8 +1324,9 @@ function init() { ,bg: 'Изчисти' ,hr: 'Očisti' ,sv: 'Rensa' + ,it: 'Pulisci' } - ,'Record' : { + ,'Record' : { cs: 'Záznam' ,de: 'Datensatz' ,es: 'Guardar' @@ -1237,8 +1336,9 @@ function init() { ,ro: 'Înregistrare' ,bg: 'Запиши' ,hr: 'Zapis' + ,it: 'Registro' } - ,'Quick picks' : { + ,'Quick picks' : { cs: 'Rychlý výběr' ,de: 'Schnellauswahl' ,es: 'Selección rápida' @@ -1248,8 +1348,9 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,it: 'Scelta rapida' } - ,'Show hidden' : { + ,'Show hidden' : { cs: 'Zobraz skryté' ,de: 'Zeige verborgen' ,es: 'Mostrar ocultos' @@ -1259,8 +1360,9 @@ function init() { ,bg: 'Покажи скритото' ,hr: 'Prikaži skriveno' ,sv: 'Visa dolda' + ,it: 'Mostra nascosto' } - ,'Your API secret' : { + ,'Your API secret' : { cs: 'Vaše API heslo' ,de: 'Deine API Prüfsumme' ,es: 'Su secreto API' @@ -1270,8 +1372,9 @@ function init() { ,ro: 'Cheia API' ,bg: 'Твоята API парола' ,hr: 'Vaš tajni API' + ,it: 'Il tuo API secreto' } - ,'Store hash on this computer (Use only on private computers)' : { + ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' ,de: 'Speichere Prüfsumme auf diesem Computer (nur auf privaten Computern anwenden)' ,es: 'Guardar hash en este ordenador (Usar solo en ordenadores privados)' @@ -1281,8 +1384,9 @@ function init() { ,bg: 'Запамети данните на този компютър. ( Използвай само на собствен компютър)' ,hr: 'Pohrani hash na ovom računalu (Koristiti samo na osobnom računalu)' ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' + ,it: 'Conservare hash su questo computer (utilizzare solo su computer privati)' } - ,'Treatments' : { + ,'Treatments' : { cs: 'Ošetření' ,de: 'Bearbeitung' ,es: 'Tratamientos' @@ -1292,8 +1396,9 @@ function init() { ,ro: 'Tratamente' ,bg: 'Събития' ,hr: 'Tretmani' + ,it: 'Somministrazione' } - ,'Time' : { + ,'Time' : { cs: 'Čas' ,de: 'Zeit' ,es: 'Hora' @@ -1303,8 +1408,9 @@ function init() { ,ro: 'Ora' ,bg: 'Време' ,hr: 'Vrijeme' + ,it: 'Tempo' } - ,'Event Type' : { + ,'Event Type' : { cs: 'Typ události' ,de: 'Ereignis-Typ' ,es: 'Tipo de evento' @@ -1314,8 +1420,9 @@ function init() { ,ro: 'Tip eveniment' ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' + ,it: 'Tipo di evento' } - ,'Blood Glucose' : { + ,'Blood Glucose' : { cs: 'Glykémie' ,de: 'Blutglukose' ,es: 'Glucemia' @@ -1325,8 +1432,9 @@ function init() { ,ro: 'Glicemie' ,bg: 'Кръвна захар' ,hr: 'GUK' + ,it: 'Glicemia' } - ,'Entered By' : { + ,'Entered By' : { cs: 'Zadal' ,de: 'Eingabe durch' ,es: 'Introducido por' @@ -1336,8 +1444,9 @@ function init() { ,ro: 'Introdus de' ,bg: 'Въведено от' ,hr: 'Unos izvršio' + ,it: 'inserito da' } - ,'Delete this treatment?' : { + ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' ,de: 'Bearbeitung löschen' ,es: '¿Borrar este tratamiento?' @@ -1347,8 +1456,9 @@ function init() { ,bg: 'Изтрий това събитие' ,hr: 'Izbriši ovaj tretman?' ,sv: 'Ta bort händelse?' + ,it: 'Eliminare questa somministrazione?' } - ,'Carbs Given' : { + ,'Carbs Given' : { cs: 'Sacharidů' ,de: 'Kohlenhydratgabe' ,es: 'Hidratos de carbono dados' @@ -1358,8 +1468,9 @@ function init() { ,bg: 'ВХ' ,hr: 'Količina UH' ,sv: 'Antal kolhydrater' + ,it: 'Carboidrati' } - ,'Inzulin Given' : { + ,'Inzulin Given' : { cs: 'Inzulínu' ,de: 'Insulingabe' ,es: 'Insulina dada' @@ -1369,8 +1480,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina inzulina' ,sv: 'Insulin' + ,it: 'Insulina' } - ,'Event Time' : { + ,'Event Time' : { cs: 'Čas události' ,de: 'Ereignis Zeit' ,es: 'Hora del evento' @@ -1380,8 +1492,9 @@ function init() { ,ro: 'Ora evenimentului' ,bg: 'Въвеждане' ,hr: 'Vrijeme događaja' + ,it: 'Ora Evento' } - ,'Please verify that the data entered is correct' : { + ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' ,de: 'Bitte Daten auf Plausibilität prüfen' ,es: 'Por favor, verifique que los datos introducidos son correctos' @@ -1391,8 +1504,9 @@ function init() { ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,it: 'Si prega di verificare che i dati inseriti sono corretti' } - ,'BG' : { + ,'BG' : { cs: 'Glykémie' ,de: 'Blutglukose' ,es: 'Glucemia en sangre' @@ -1402,8 +1516,9 @@ function init() { ,ro: 'Glicemie' ,bg: 'КЗ' ,hr: 'GUK' + ,it: 'Glicemia' } - ,'Use BG correction in calculation' : { + ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' ,de: 'Verwende Blutglukose-Korrektur zur Kalkulation' ,es: 'Usar la corrección de glucemia en los cálculos' @@ -1413,8 +1528,9 @@ function init() { ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för uträkning' + ,it: 'Utilizzare la correzione Glicemia nei calcoli' } - ,'BG from CGM (autoupdated)' : { + ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' ,de: 'Blutglukose vom CGM (Autoupdate)' ,es: 'Glucemia del sensor (Actualizado automáticamente)' @@ -1424,8 +1540,9 @@ function init() { ,ro: 'Glicemie în senzor (automat)' ,bg: 'КЗ от сензора (автоматично)' ,hr: 'GUK sa CGM-a (ažuriran automatski)' + ,it: 'Glicemie da CGM (aggiornamento automatico)' } - ,'BG from meter' : { + ,'BG from meter' : { cs: 'Glykémie z glukoměru' ,de: 'Blutzucker vom Messgerät' ,es: 'Glucemia del glucómetro' @@ -1435,8 +1552,9 @@ function init() { ,ro: 'Glicemie în glucometru' ,bg: 'КЗ от глюкомер' ,hr: 'GUK s glukometra' + ,it: 'Glicemie da glucometro' } - ,'Manual BG' : { + ,'Manual BG' : { cs: 'Ručně zadaná glykémie' ,de: 'Blutzucker händisch' ,es: 'Glucemia manual' @@ -1446,6 +1564,7 @@ function init() { ,bg: 'Ръчно въведена КЗ' ,hr: 'Ručno unesen GUK' ,sv: 'Manuellt BS' + ,it: 'Inserisci Glicemia' } ,'Quickpick' : { cs: 'Rychlý výběr' @@ -1457,6 +1576,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,it: 'Scelta rapida' } ,'or' : { cs: 'nebo' @@ -1468,8 +1588,9 @@ function init() { ,ro: 'sau' ,bg: 'или' ,hr: 'ili' + ,it: 'o' } - ,'Add from database' : { + ,'Add from database' : { cs: 'Přidat z databáze' ,de: 'Ergänzt aus Datenbank' ,es: 'Añadir desde la base de datos' @@ -1479,8 +1600,9 @@ function init() { ,bg: 'Добави към базата с данни' ,hr: 'Dodaj iz baze podataka' ,sv: 'Lägg till från databas' + ,it: 'Aggiungi dal database' } - ,'Use carbs correction in calculation' : { + ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' ,de: 'Verwende Kohlenhydrate-Korrektur zur Kalkulation' ,es: 'Usar la corrección de hidratos de carbono en los cálculos' @@ -1490,8 +1612,9 @@ function init() { ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion i utäkning' + ,it: 'Utilizzare la correzione con carboidrati nel calcolo' } - ,'Use COB correction in calculation' : { + ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' ,de: 'Verwende verzehrte Kohlenhydrate zur Kalkulation' ,es: 'Usar la corrección de COB en los cálculos' @@ -1501,8 +1624,9 @@ function init() { ,bg: 'Въведи корекция за останалите въглехидрати' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' + ,it: 'Utilizzare la correzione COB nel calcolo' } - ,'Use IOB in calculation' : { + ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' ,de: 'Verwende gespritzes Insulin zur Kalkulation' ,es: 'Usar la IOB en los cálculos' @@ -1512,8 +1636,9 @@ function init() { ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för uträkning' + ,it: 'Utilizzare la correzione IOB nel calcolo' } - ,'Other correction' : { + ,'Other correction' : { cs: 'Jiná korekce' ,de: 'Weitere Korrektur' ,es: 'Otra correción' @@ -1523,8 +1648,9 @@ function init() { ,bg: 'Друга корекция' ,hr: 'Druga korekcija' ,sv: 'Övrig korrektion' + ,it: 'Altre correzioni' } - ,'Rounding' : { + ,'Rounding' : { cs: 'Zaokrouhlení' ,de: 'Gerundet' ,es: 'Redondeo' @@ -1534,8 +1660,9 @@ function init() { ,ro: 'Rotunjire' ,bg: 'Закръгляне' ,hr: 'Zaokruživanje' - } - ,'Enter insulin correction in treatment' : { + ,it: 'Arrotondamento' + } + ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' ,de: 'Insulin Korrektur zur Behandlung eingeben' ,es: 'Introducir correción de insulina en tratamiento' @@ -1545,8 +1672,9 @@ function init() { ,bg: 'Въведи корекция с инсулин като лечение' ,hr: 'Unesi korekciju inzulinom u tretman' ,sv: 'Ange insulinkorrektion för händelse' + ,it: 'Inserisci correzione insulina nella somministrazione' } - ,'Insulin needed' : { + ,'Insulin needed' : { cs: 'Potřebný inzulín' ,de: 'Benötigtes Insulin' ,es: 'Insulina necesaria' @@ -1556,8 +1684,9 @@ function init() { ,bg: 'Необходим инсулин' ,hr: 'Potrebno inzulina' ,sv: 'Beräknad insulinmängd' + ,it: 'Insulina necessaria' } - ,'Carbs needed' : { + ,'Carbs needed' : { cs: 'Potřebné sach' ,de: 'Benötigte Kohlenhydrate' ,es: 'Hidratos de carbono necesarios' @@ -1567,8 +1696,9 @@ function init() { ,bg: 'Необходими въглехидрати' ,hr: 'Potrebno UH' ,sv: 'Beräknad kolhydratmängd' + ,it: 'Carboidrati necessari' } - ,'Carbs needed if Insulin total is negative value' : { + ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' ,de: 'Benötigte Kohlenhydrate sofern Gesamtinsulin einen negativen Wert aufweist' ,es: 'Hidratos de carbono necesarios si el total de insulina es un valor negativo' @@ -1578,8 +1708,9 @@ function init() { ,bg: 'Необходими въглехидрати, ако няма инсулин' ,hr: 'Potrebno UH ako je ukupna vrijednost inzulina negativna' ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' + ,it: 'Carboidrati necessari se l\'insulina totale è un valore negativo' } - ,'Basal rate' : { + ,'Basal rate' : { cs: 'Bazál' ,de: 'Basalrate' ,es: 'Tasa basal' @@ -1589,8 +1720,9 @@ function init() { ,bg: 'Базален инсулин' ,hr: 'Bazal' ,sv: 'Basaldos' + ,it: 'Velocità basale' } - ,'60 minutes earlier' : { + ,'60 minutes earlier' : { cs: '60 min předem' ,de: '60 Min. früher' ,es: '60 min antes' @@ -1600,8 +1732,9 @@ function init() { ,ro: 'acum 60 min' ,bg: 'Преди 60 минути' ,hr: 'Prije 60 minuta' + ,it: '60 minuti prima' } - ,'45 minutes earlier' : { + ,'45 minutes earlier' : { cs: '45 min předem' ,de: '45 Min. früher' ,es: '45 min antes' @@ -1611,8 +1744,9 @@ function init() { ,ro: 'acum 45 min' ,bg: 'Преди 45 минути' ,hr: 'Prije 45 minuta' + ,it: '45 minuti prima' } - ,'30 minutes earlier' : { + ,'30 minutes earlier' : { cs: '30 min předem' ,de: '30 Min früher' ,es: '30 min antes' @@ -1622,8 +1756,9 @@ function init() { ,ro: 'acum 30 min' ,bg: 'Преди 30 минути' ,hr: 'Prije 30 minuta' + ,it: '30 minuti prima' } - ,'20 minutes earlier' : { + ,'20 minutes earlier' : { cs: '20 min předem' ,de: '20 Min. früher' ,es: '20 min antes' @@ -1633,8 +1768,9 @@ function init() { ,ro: 'acum 20 min' ,bg: 'Преди 20 минути' ,hr: 'Prije 20 minuta' + ,it: '20 minuti prima' } - ,'15 minutes earlier' : { + ,'15 minutes earlier' : { cs: '15 min předem' ,de: '15 Min. früher' ,es: '15 min antes' @@ -1644,8 +1780,9 @@ function init() { ,ro: 'acu 15 min' ,bg: 'Преди 15 минути' ,hr: 'Prije 15 minuta' + ,it: '15 minuti prima' } - ,'Time in minutes' : { + ,'Time in minutes' : { cs: 'Čas v minutách' ,de: 'Zeit in Minuten' ,es: 'Tiempo en minutos' @@ -1655,8 +1792,9 @@ function init() { ,ro: 'Timp în minute' ,bg: 'Времето в минути' ,hr: 'Vrijeme u minutama' + ,it: 'Tempo in minuti' } - ,'15 minutes later' : { + ,'15 minutes later' : { cs: '15 min po' ,de: '15 Min. später' ,es: '15 min más tarde' @@ -1665,8 +1803,9 @@ function init() { ,bg: 'След 15 минути' ,hr: '15 minuta kasnije' ,sv: '15 min senare' + ,it: '15 minuti più tardi' } - ,'20 minutes later' : { + ,'20 minutes later' : { cs: '20 min po' ,de: '20 Min. später' ,es: '20 min más tarde' @@ -1676,8 +1815,9 @@ function init() { ,bg: 'След 20 минути' ,hr: '20 minuta kasnije' ,sv: '20 min senare' + ,it: '20 minuti più tardi' } - ,'30 minutes later' : { + ,'30 minutes later' : { cs: '30 min po' ,de: '30 Min. später' ,es: '30 min más tarde' @@ -1687,8 +1827,9 @@ function init() { ,bg: 'След 30 минути' ,hr: '30 minuta kasnije' ,sv: '30 min senare' + ,it: '30 minuti più tardi' } - ,'45 minutes later' : { + ,'45 minutes later' : { cs: '45 min po' ,de: '45 Min. später' ,es: '45 min más tarde' @@ -1698,8 +1839,9 @@ function init() { ,bg: 'След 45 минути' ,hr: '45 minuta kasnije' ,sv: '45 min senare' + ,it: '45 minuti più tardi' } - ,'60 minutes later' : { + ,'60 minutes later' : { cs: '60 min po' ,de: '60 Min. später' ,es: '60 min más tarde' @@ -1709,8 +1851,9 @@ function init() { ,bg: 'След 60 минути' ,hr: '60 minuta kasnije' ,sv: '60 min senare' + ,it: '60 minuti più tardi' } - ,'Additional Notes, Comments' : { + ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' ,de: 'Ergänzende Hinweise / Kommentare' ,es: 'Notas adicionales, Comentarios' @@ -1720,8 +1863,9 @@ function init() { ,bg: 'Допълнителни бележки, коментари' ,hr: 'Dodatne bilješke, komentari' ,sv: 'Notering, övrigt' + ,it: 'Note aggiuntive, commenti' } - ,'RETRO MODE' : { + ,'RETRO MODE' : { cs: 'V MINULOSTI' ,de: 'RETRO MODUS' ,es: 'Modo Retrospectivo' @@ -1731,8 +1875,9 @@ function init() { ,ro: 'MOD RETROSPECTIV' ,bg: 'МИНАЛО ВРЕМЕ' ,hr: 'Retrospektivni način' + ,it: 'Modalità retrospettiva' } - ,'Now' : { + ,'Now' : { cs: 'Nyní' ,de: 'Jetzt' ,es: 'Ahora' @@ -1742,8 +1887,9 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' + ,it: 'Adesso' } - ,'Other' : { + ,'Other' : { cs: 'Jiný' ,de: 'Weitere' ,es: 'Otro' @@ -1753,8 +1899,9 @@ function init() { ,ro: 'Altul' ,bg: 'Друго' ,hr: 'Drugo' + ,it: 'Altro' } - ,'Submit Form' : { + ,'Submit Form' : { cs: 'Odeslat formulář' ,de: 'Eingabe senden' ,es: 'Enviar formulario' @@ -1764,8 +1911,9 @@ function init() { ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' ,hr: 'Predaj obrazac' + ,it: 'Inviare il modulo' } - ,'Profile editor' : { + ,'Profile editor' : { cs: 'Editor profilu' ,de: 'Profil-Einstellungen' ,es: 'Editor de perfil' @@ -1775,8 +1923,9 @@ function init() { ,ro: 'Editare profil' ,bg: 'Редактор на профила' ,hr: 'Editor profila' + ,it: 'Editor dei profili' } - ,'Reporting tool' : { + ,'Reporting tool' : { cs: 'Výkazy' ,de: 'Berichte' ,es: 'Herramienta de informes' @@ -1786,8 +1935,9 @@ function init() { ,ro: 'Instrument de rapoarte' ,bg: 'Статистика' ,hr: 'Alat za prijavu' + ,it: 'Strumento di reporting' } - ,'Add food from your database' : { + ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' ,de: 'Ergänzt durch deine Datenbank' ,es: 'Añadir alimento a su base de datos' @@ -1797,8 +1947,9 @@ function init() { ,bg: 'Добави храна от твоята база с данни' ,hr: 'Dodajte hranu iz svoje baze podataka' ,sv: 'Lägg till livsmedel från databas' + ,it: 'Aggiungere cibo al database' } - ,'Reload database' : { + ,'Reload database' : { cs: 'Znovu nahraj databázi' ,de: 'Datenbank nachladen' ,es: 'Recargar base de datos' @@ -1808,8 +1959,9 @@ function init() { ,bg: 'Презареди базата с данни' ,hr: 'Ponovo učitajte bazu podataka' ,sv: 'Ladda om databas' + ,it: 'Ricarica database' } - ,'Add' : { + ,'Add' : { cs: 'Přidej' ,de: 'Hinzufügen' ,es: 'Añadir' @@ -1819,8 +1971,9 @@ function init() { ,bg: 'Добави' ,hr: 'Dodaj' ,sv: 'Lägg till' + ,it: 'Aggiungere' } - ,'Unauthorized' : { + ,'Unauthorized' : { cs: 'Neautorizováno' ,de: 'Unbestätigt' ,es: 'No autorizado' @@ -1830,8 +1983,9 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' + ,it: 'non autorizzato' } - ,'Entering record failed' : { + ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' ,de: 'Eingabe Datensatz fehlerhaft' ,es: 'Entrada de registro fallida' @@ -1841,8 +1995,9 @@ function init() { ,bg: 'Въвеждане на записа не се осъществи' ,hr: 'Neuspjeli unos podataka' ,sv: 'Lägga till post nekas' + ,it: 'Voce del Registro fallita' } - ,'Device authenticated' : { + ,'Device authenticated' : { cs: 'Zařízení ověřeno' ,de: 'Gerät authentifiziert' ,es: 'Dispositivo autenticado' @@ -1852,8 +2007,9 @@ function init() { ,ro: 'Dispozitiv autentificat' ,bg: 'Устройстово е разпознато' ,hr: 'Uređaj autenticiran' + ,it: 'Dispositivo autenticato' } - ,'Device not authenticated' : { + ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' ,de: 'Gerät nicht authentifiziert' ,es: 'Dispositivo no autenticado' @@ -1863,8 +2019,9 @@ function init() { ,ro: 'Dispozitiv neautentificat' ,bg: 'Устройсройството не е разпознато' ,hr: 'Uređaj nije autenticiran' + ,it: 'Dispositivo non autenticato' } - ,'Authentication status' : { + ,'Authentication status' : { cs: 'Stav ověření' ,de: 'Authentifications Status' ,es: 'Estado de autenticación' @@ -1874,8 +2031,9 @@ function init() { ,bg: 'Статус на удостоверяване' ,hr: 'Status autentikacije' ,sv: 'Autentiseringsstatus' + ,it: 'Stato di autenticazione' } - ,'Authenticate' : { + ,'Authenticate' : { cs: 'Ověřit' ,de: 'Authentifizieren' ,es: 'Autenticar' @@ -1885,8 +2043,9 @@ function init() { ,ro: 'Autentificare' ,bg: 'Удостоверяване' ,hr: 'Autenticirati' + ,it: 'Autenticare' } - ,'Remove' : { + ,'Remove' : { cs: 'Vymazat' ,de: 'Entfernen' ,es: 'Eliminar' @@ -1896,6 +2055,7 @@ function init() { ,bg: 'Премахни' ,hr: 'Ukloniti' ,sv: 'Ta bort' + ,it: 'Rimuovere' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' @@ -1907,8 +2067,9 @@ function init() { ,bg: 'Вашето устройство все още не е удостоверено' ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' + ,it: 'Il tuo dispositivo non è ancora stato autenticato' } - ,'Sensor' : { + ,'Sensor' : { cs: 'Senzor' ,de: 'Sensor' ,es: 'Sensor' @@ -1918,8 +2079,9 @@ function init() { ,ro: 'Senzor' ,bg: 'Сензор' ,hr: 'Senzor' + ,it: 'Sensore' } - ,'Finger' : { + ,'Finger' : { cs: 'Glukoměr' ,de: 'Finger' ,es: 'Dedo' @@ -1929,8 +2091,9 @@ function init() { ,ro: 'Deget' ,bg: 'От пръстта' ,hr: 'Prst' + ,it: 'Dito' } - ,'Manual' : { + ,'Manual' : { cs: 'Ručně' ,de: 'Händisch' ,es: 'Manual' @@ -1940,8 +2103,9 @@ function init() { ,ro: 'Manual' ,bg: 'Ръчно' ,hr: 'Ručno' + ,it: 'Manuale' } - ,'Scale' : { + ,'Scale' : { cs: 'Měřítko' ,de: 'Messen' ,es: 'Escala' @@ -1951,8 +2115,9 @@ function init() { ,bg: 'Скала' ,hr: 'Skala' ,sv: 'Skala' + ,it: 'Scala' } - ,'Linear' : { + ,'Linear' : { cs: 'lineární' ,de: 'Linear' ,es: 'Lineal' @@ -1962,8 +2127,9 @@ function init() { ,ro: 'Liniar' ,bg: 'Линеен' ,hr: 'Linearno' + ,it: 'Lineare' } - ,'Logarithmic' : { + ,'Logarithmic' : { cs: 'logaritmické' ,de: 'Logarithmisch' ,es: 'Logarítmica' @@ -1973,8 +2139,9 @@ function init() { ,ro: 'Logaritmic' ,bg: 'Логоритмичен' ,hr: 'Logaritamski' + ,it: 'Logaritmica' } - ,'Silence for 30 minutes' : { + ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' ,de: 'Ausschalten für 30 Minuten' ,es: 'Silenciar durante 30 minutos' @@ -1984,8 +2151,9 @@ function init() { ,bg: 'Заглуши за 30 минути' ,hr: 'Tišina 30 minuta' ,sv: 'Tyst i 30 min' + ,it: 'Silenzio per 30 minuti' } - ,'Silence for 60 minutes' : { + ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' ,de: 'Ausschalten für 60 Minuten' ,es: 'Silenciar durante 60 minutos' @@ -1995,8 +2163,9 @@ function init() { ,bg: 'Заглуши за 60 минути' ,hr: 'Tišina 60 minuta' ,sv: 'Tyst i 60 min' + ,it: 'Silenzio per 60 minuti' } - ,'Silence for 90 minutes' : { + ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' ,de: 'Ausschalten für 90 Minuten' ,es: 'Silenciar durante 90 minutos' @@ -2006,8 +2175,9 @@ function init() { ,bg: 'Заглуши за 90 минути' ,hr: 'Tišina 90 minuta' ,sv: 'Tyst i 90 min' + ,it: 'Silenzio per 90 minuti' } - ,'Silence for 120 minutes' : { + ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' ,de: 'Ausschalten für 120 Minuten' ,es: 'Silenciar durante 120 minutos' @@ -2017,8 +2187,9 @@ function init() { ,bg: 'Заглуши за 120 минути' ,hr: 'Tišina 120 minuta' ,sv: 'Tyst i 120 min' + ,it: 'Silenzio per 120 minuti' } - ,'3HR' : { + ,'3HR' : { cs: '3hod' ,de: '3h' ,es: '3h' @@ -2028,8 +2199,9 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' + ,it: '3h' } - ,'6HR' : { + ,'6HR' : { cs: '6hod' ,de: '6h' ,es: '6h' @@ -2039,8 +2211,9 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' + ,it: '6h' } - ,'12HR' : { + ,'12HR' : { cs: '12hod' ,de: '12h' ,es: '12h' @@ -2050,8 +2223,9 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' + ,it: '12h' } - ,'24HR' : { + ,'24HR' : { cs: '24hod' ,de: '24h' ,es: '24h' @@ -2061,6 +2235,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' + ,it: '24h' } ,'Settings' : { cs: 'Nastavení' @@ -2071,6 +2246,7 @@ function init() { ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' + ,it: 'Impostazioni' } ,'Units' : { cs: 'Jednotky' @@ -2082,8 +2258,9 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinice' ,sv: 'Enheter' + ,it: 'Unità' } - ,'Date format' : { + ,'Date format' : { cs: 'Formát datumu' ,de: 'Datum Format' ,es: 'Formato de fecha' @@ -2093,8 +2270,9 @@ function init() { ,ro: 'Formatul datei' ,bg: 'Формат на датата' ,hr: 'Format datuma' + ,it: 'Formato data' } - ,'12 hours' : { + ,'12 hours' : { cs: '12 hodin' ,de: '12 Stunden' ,es: '12 horas' @@ -2104,8 +2282,9 @@ function init() { ,ro: '12 ore' ,bg: '12 часа' ,hr: '12 sati' + ,it: '12 ore' } - ,'24 hours' : { + ,'24 hours' : { cs: '24 hodin' ,de: '24 Stunden' ,es: '24 horas' @@ -2115,8 +2294,9 @@ function init() { ,ro: '24 ore' ,bg: '24 часа' ,hr: '24 sata' + ,it: '24 ore' } - ,'Log a Treatment' : { + ,'Log a Treatment' : { cs: 'Záznam ošetření' ,de: 'Dateneingabe' ,es: 'Apuntar un tratamiento' @@ -2126,8 +2306,9 @@ function init() { ,bg: 'Въвеждане на събитие' ,hr: 'Evidencija tretmana' ,sv: 'Ange händelse' + ,it: 'Somministrazione' } - ,'BG Check' : { + ,'BG Check' : { cs: 'Kontrola glykémie' ,de: 'Blutglukose-Prüfung' ,es: 'Control de glucemia' @@ -2137,8 +2318,9 @@ function init() { ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' + ,it: 'Controllo Glicemia' } - ,'Meal Bolus' : { + ,'Meal Bolus' : { cs: 'Bolus na jídlo' ,de: 'Mahlzeiten Bolus' ,es: 'Bolo de comida' @@ -2148,8 +2330,9 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' + ,it: 'bolo pasto' } - ,'Snack Bolus' : { + ,'Snack Bolus' : { cs: 'Bolus na svačinu' ,de: 'Snack Bolus' ,es: 'Bolo de aperitivo' @@ -2159,8 +2342,9 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' + ,it: 'Bolo merenda' } - ,'Correction Bolus' : { + ,'Correction Bolus' : { cs: 'Bolus na glykémii' ,de: 'Korrektur Bolus' ,es: 'Bolo corrector' @@ -2170,8 +2354,9 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' + ,it: 'Correzione bolo' } - ,'Carb Correction' : { + ,'Carb Correction' : { cs: 'Přídavek sacharidů' ,de: 'Kohlenhydrate Korrektur' ,es: 'Hidratos de carbono de corrección' @@ -2181,8 +2366,9 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' + ,it: 'Correzione carboidrati' } - ,'Note' : { + ,'Note' : { cs: 'Poznámka' ,de: 'Bemerkungen' ,es: 'Nota' @@ -2192,8 +2378,9 @@ function init() { ,bg: 'Бележка' ,hr: 'Bilješka' ,sv: 'Notering' + ,it: 'Nota' } - ,'Question' : { + ,'Question' : { cs: 'Otázka' ,de: 'Frage' ,es: 'Pregunta' @@ -2203,8 +2390,9 @@ function init() { ,ro: 'Întrebare' ,bg: 'Въпрос' ,hr: 'Pitanje' + ,it: 'Domanda' } - ,'Exercise' : { + ,'Exercise' : { cs: 'Cvičení' ,de: 'Bewegung' ,es: 'Ejercicio' @@ -2214,8 +2402,9 @@ function init() { ,bg: 'Спорт' ,hr: 'Aktivnost' ,sv: 'Aktivitet' + ,it: 'Esercizio' } - ,'Pump Site Change' : { + ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' ,de: 'Pumpen-Katheter Wechsel' ,es: 'Cambio de catéter' @@ -2225,8 +2414,9 @@ function init() { ,bg: 'Смяна на сет' ,hr: 'Promjena seta' ,sv: 'Pump/nålbyte' + ,it: 'Cambio Ago' } - ,'Sensor Start' : { + ,'Sensor Start' : { cs: 'Spuštění sensoru' ,de: 'Sensor Start' ,es: 'Inicio de sensor' @@ -2236,8 +2426,9 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' + ,it: 'Avvio sensore' } - ,'Sensor Change' : { + ,'Sensor Change' : { cs: 'Výměna sensoru' ,de: 'Sensor Wechsel' ,es: 'Cambio de sensor' @@ -2247,8 +2438,9 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' + ,it: 'Cambio sensore' } - ,'Dexcom Sensor Start' : { + ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' ,de: 'Dexcom Sensor Start' ,es: 'Inicio de sensor Dexcom' @@ -2258,8 +2450,9 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' + ,it: 'Avvio sensore Dexcom' } - ,'Dexcom Sensor Change' : { + ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' ,de: 'Dexcom Sensor Wechsel' ,es: 'Cambio de sensor Dexcom' @@ -2269,8 +2462,9 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' + ,it: 'Cambio sensore Dexcom' } - ,'Insulin Cartridge Change' : { + ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' ,de: 'Insulin Ampullen Wechsel' ,es: 'Cambio de reservorio de insulina' @@ -2280,8 +2474,9 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' + ,it: 'Cambio cartuccia insulina' } - ,'D.A.D. Alert' : { + ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' ,de: 'Diabetes-Hund Alarm' ,es: 'Alerta de perro de alerta diabética' @@ -2291,8 +2486,9 @@ function init() { ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' ,sv: 'Voff voff! (Diabeteshundalarm!)' + ,it: 'Allarme D.A.D.' } - ,'Glucose Reading' : { + ,'Glucose Reading' : { cs: 'Hodnota glykémie' ,de: 'Glukose Messwert' ,es: 'Valor de glucemia' @@ -2302,8 +2498,9 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' + ,it: 'Lettura glicemie' } - ,'Measurement Method' : { + ,'Measurement Method' : { cs: 'Metoda měření' ,de: 'Messmethode' ,es: 'Método de medida' @@ -2313,8 +2510,9 @@ function init() { ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' ,sv: 'Mätmetod' + ,it: 'Metodo di misurazione' } - ,'Meter' : { + ,'Meter' : { cs: 'Glukoměr' ,de: 'BZ-Messgerät' ,fr: 'Glucomètre' @@ -2324,8 +2522,9 @@ function init() { ,es: 'Glucómetro' ,bg: 'Глюкомер' ,hr: 'Glukometar' + ,it: 'Glucometro' } - ,'Insulin Given' : { + ,'Insulin Given' : { cs: 'Inzulín' ,de: 'Insulingabe' ,es: 'Insulina' @@ -2335,8 +2534,9 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,sv: 'Insulindos' + ,it: 'Insulina' } - ,'Amount in grams' : { + ,'Amount in grams' : { cs: 'Množství v gramech' ,de: 'Menge in Gramm' ,es: 'Cantidad en gramos' @@ -2346,8 +2546,9 @@ function init() { ,ro: 'Cantitate în grame' ,bg: ' К-во в грамове' ,hr: 'Količina u gramima' + ,it: 'Quantità in grammi' } - ,'Amount in units' : { + ,'Amount in units' : { cs: 'Množství v jednotkách' ,de: 'Anzahl in Einheiten' ,es: 'Cantidad en unidades' @@ -2357,8 +2558,9 @@ function init() { ,bg: 'К-во в единици' ,hr: 'Količina u jedinicama' ,sv: 'Antal enheter' + ,it: 'Quantità in unità' } - ,'View all treatments' : { + ,'View all treatments' : { cs: 'Zobraz všechny ošetření' ,de: 'Zeige alle Eingaben' ,es: 'Visualizar todos los tratamientos' @@ -2368,8 +2570,9 @@ function init() { ,ro: 'Vezi toate evenimentele' ,bg: 'Преглед на всички събития' ,hr: 'Prikaži sve tretmane' + ,it: 'Visualizza tutti le somministrazioni' } - ,'Enable Alarms' : { + ,'Enable Alarms' : { cs: 'Povolit alarmy' ,de: 'Alarm einschalten' ,es: 'Activar las alarmas' @@ -2378,8 +2581,9 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' + ,it: 'Attiva Allarme' } - ,'When enabled an alarm may sound.' : { + ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' ,de: 'Sofern eingeschaltet ertönt ein Alarm' ,es: 'Cuando estén activas, una alarma podrá sonar' @@ -2388,8 +2592,9 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' + ,it: 'Quando si attiva un allarme acustico.' } - ,'Urgent High Alarm' : { + ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' ,de: 'Achtung Hoch Alarm' ,es: 'Alarma de glucemia alta urgente' @@ -2398,8 +2603,9 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' + ,it: 'Allarme Urgente: Glicemia Molto Alta' } - ,'High Alarm' : { + ,'High Alarm' : { cs: 'Vysoká glykémie' ,de: 'Hoch Alarm' ,es: 'Alarma de glucemia alta' @@ -2408,8 +2614,9 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' + ,it: 'Allarme: Glicemia Alta' } - ,'Low Alarm' : { + ,'Low Alarm' : { cs: 'Nízká glykémie' ,de: 'Tief Alarm' ,es: 'Alarma de glucemia baja' @@ -2418,8 +2625,9 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' + ,it: 'Allarme Glicemia bassa' } - ,'Urgent Low Alarm' : { + ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' ,de: 'Achtung Tief Alarm' ,es: 'Alarma de glucemia baja urgente' @@ -2428,8 +2636,9 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' + ,it: 'Allarme Urgente. Glicemia Molto Bassa' } - ,'Stale Data: Warn' : { + ,'Stale Data: Warn' : { cs: 'Zastaralá data' ,de: 'Warnung: Daten nicht mehr gültig' ,es: 'Datos obsoletos: aviso' @@ -2438,8 +2647,9 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' + ,it: 'Dati obsoleti: Notifica' } - ,'Stale Data: Urgent' : { + ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' ,de: 'Achtung: Daten nicht mehr gültig' ,es: 'Datos obsoletos: Urgente' @@ -2449,8 +2659,9 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' + ,it: 'Dati obsoleti: Urgente' } - ,'mins' : { + ,'mins' : { cs: 'min' ,de: 'min' ,es: 'min' @@ -2460,8 +2671,10 @@ function init() { ,ro: 'min' ,bg: 'мин' ,hr: 'min' + ,it: 'min' + } - ,'Night Mode' : { + ,'Night Mode' : { cs: 'Noční mód' ,de: 'Nacht Modus' ,es: 'Modo nocturno' @@ -2471,8 +2684,9 @@ function init() { ,ro: 'Mod nocturn' ,bg: 'Нощен режим' ,hr: 'Noćni način' + ,it: 'Modalità notte' } - ,'When enabled the page will be dimmed from 10pm - 6am.' : { + ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' ,de: 'Wenn aktiviert wird die Anzeige von 22 Uhr - 6 Uhr gedimmt' ,es: 'Cuando esté activo, el brillo de la página bajará de 10pm a 6am.' @@ -2481,8 +2695,9 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' + ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' } - ,'Enable' : { + ,'Enable' : { cs: 'Povoleno' ,de: 'Eingeschaltet' ,es: 'Activar' @@ -2492,6 +2707,7 @@ function init() { ,ro: 'Activează' ,bg: 'Активно' ,hr: 'Aktiviraj' + ,it: 'Permettere' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' @@ -2503,8 +2719,9 @@ function init() { ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' ,sv: 'Visa RAW-data' + ,it: 'Mostra dati Raw BG' } - ,'Never' : { + ,'Never' : { cs: 'Nikdy' ,de: 'Nie' ,es: 'Nunca' @@ -2514,8 +2731,9 @@ function init() { ,ro: 'Niciodată' ,bg: 'Никога' ,hr: 'Nikad' + ,it: 'Mai' } - ,'Always' : { + ,'Always' : { cs: 'Vždy' ,de: 'Immer' ,es: 'Siempre' @@ -2525,8 +2743,9 @@ function init() { ,ro: 'Întotdeauna' ,bg: 'Винаги' ,hr: 'Uvijek' + ,it: 'Sempre' } - ,'When there is noise' : { + ,'When there is noise' : { cs: 'Při šumu' ,de: 'Sofern Störgeräusch vorhanden' ,es: 'Cuando hay ruido' @@ -2536,8 +2755,9 @@ function init() { ,ro: 'Atunci când este diferență' ,bg: 'Когато има шум' ,hr: 'Kad postoji šum' + ,it: 'Quando vi è rumore' } - ,'When enabled small white dots will be disaplyed for raw BG data' : { + ,'When enabled small white dots will be displayed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' ,de: 'Bei Aktivierung erscheinen kleine weiße Punkte für Roh-Blutglukose Daten' ,es: 'Cuando esté activo, pequeños puntos blancos mostrarán los datos en crudo' @@ -2546,8 +2766,9 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } - ,'Custom Title' : { + ,'Custom Title' : { cs: 'Vlastní název stránky' ,de: 'Benutzerdefiniert' ,es: 'Título personalizado' @@ -2557,8 +2778,9 @@ function init() { ,ro: 'Titlu particularizat' ,bg: 'Име на страницата' ,hr: 'Vlastiti naziv' + ,it: 'Titolo personalizzato' } - ,'Theme' : { + ,'Theme' : { cs: 'Téma' ,de: 'Thema' ,es: 'Tema' @@ -2568,8 +2790,9 @@ function init() { ,bg: 'Тема' ,hr: 'Tema' ,sv: 'Tema' + ,it: 'Tema' } - ,'Default' : { + ,'Default' : { cs: 'Výchozí' ,de: 'Voreingestellt' ,es: 'Por defecto' @@ -2579,8 +2802,9 @@ function init() { ,bg: 'Черно-бяла' ,hr: 'Default' ,sv: 'Standard' + ,it: 'Predefinito' } - ,'Colors' : { + ,'Colors' : { cs: 'Barevné' ,de: 'Farben' ,es: 'Colores' @@ -2589,8 +2813,9 @@ function init() { ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' + ,it: 'Colori' } - ,'Reset, and use defaults' : { + ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' ,de: 'Zurücksetzen und Voreinstellungen verwenden' ,es: 'Inicializar y utilizar los valores por defecto' @@ -2600,8 +2825,9 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' + ,it: 'Resetta e utilizza le impostazioni predefinite' } - ,'Calibrations' : { + ,'Calibrations' : { cs: 'Kalibrace' ,de: 'Kalibrierung' ,es: 'Calibraciones' @@ -2610,8 +2836,9 @@ function init() { ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' + ,it: 'Calibrazioni' } - ,'Alarm Test / Smartphone Enable' : { + ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' ,de: 'Alarm Test / Smartphone aktivieren' ,es: 'Test de Alarma / Activar teléfono' @@ -2620,8 +2847,9 @@ function init() { ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' + ,it: 'Test Allarme / Abilita Smartphone' } - ,'Bolus Wizard' : { + ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' ,de: 'Bolus Kalkulator' ,es: 'Bolus Wizard' @@ -2631,8 +2859,9 @@ function init() { ,ro: 'Calculator sugestie bolus' ,bg: 'Съветник при изчисление на болуса' ,hr: 'Bolus wizard' + ,it: 'Bolo guidato' } - ,'in the future' : { + ,'in the future' : { cs: 'v budoucnosti' ,de: 'in der Zuknft' ,es: 'en el futuro' @@ -2642,8 +2871,9 @@ function init() { ,ro: 'în viitor' ,bg: 'в бъдещето' ,hr: 'U budućnosti' + ,it: 'nel futuro' } - ,'time ago' : { + ,'time ago' : { cs: 'min zpět' ,de: 'Aktualisiert' ,es: 'tiempo atrás' @@ -2653,8 +2883,9 @@ function init() { ,ro: 'în trecut' ,bg: 'преди време' ,hr: 'prije' + ,it: 'tempo fa' } - ,'hr ago' : { + ,'hr ago' : { cs: 'hod zpět' ,de: 'Std. vorher' ,es: 'hr atrás' @@ -2663,8 +2894,9 @@ function init() { ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' + ,it: 'ora fa' } - ,'hrs ago' : { + ,'hrs ago' : { cs: 'hod zpět' ,de: 'Std. vorher' ,es: 'hr atrás' @@ -2674,8 +2906,9 @@ function init() { ,ro: 'h în trecut' ,bg: 'часа по-рано' ,hr: 'sati unazad' + ,it: 'ore fa' } - ,'min ago' : { + ,'min ago' : { cs: 'min zpět' ,de: 'Min. vorher' ,es: 'min atrás' @@ -2685,8 +2918,10 @@ function init() { ,ro: 'minut în trecut' ,bg: 'минута по-рано' ,hr: 'minuta unazad' + ,it: 'minuto fa' + } - ,'mins ago' : { + ,'mins ago' : { cs: 'min zpět' ,de: 'Min. vorher' ,es: 'min atrás' @@ -2696,8 +2931,9 @@ function init() { ,ro: 'minute în trecut' ,bg: 'минути по-рано' ,hr: 'minuta unazad' + ,it: 'minuti fa' } - ,'day ago' : { + ,'day ago' : { cs: 'den zpět' ,de: 'Tag vorher' ,es: 'día atrás' @@ -2707,8 +2943,9 @@ function init() { ,ro: 'zi în trecut' ,bg: 'ден по-рано' ,hr: 'dan unazad' + ,it: 'Giorno fa' } - ,'days ago' : { + ,'days ago' : { cs: 'dnů zpět' ,de: 'Tage vorher' ,es: 'días atrás' @@ -2718,8 +2955,9 @@ function init() { ,ro: 'zile în trecut' ,bg: 'дни по-рано' ,hr: 'dana unazad' + ,it: 'giorni fa' } - ,'long ago' : { + ,'long ago' : { cs: 'dlouho zpět' ,de: 'Lange her' ,es: 'Hace mucho tiempo' @@ -2729,8 +2967,9 @@ function init() { ,ro: 'timp în trecut' ,bg: 'преди много време' ,hr: 'prije dosta vremena' + ,it: 'Molto tempo fa' } - ,'Clean' : { + ,'Clean' : { cs: 'Čistý' ,de: 'Komplett' ,es: 'Limpio' @@ -2740,8 +2979,9 @@ function init() { ,ro: 'Curat' ,bg: 'Чист' ,hr: 'Čisto' + ,it: 'Pulito' } - ,'Light' : { + ,'Light' : { cs: 'Lehký' ,de: 'Leicht' ,es: 'Ligero' @@ -2751,8 +2991,9 @@ function init() { ,ro: 'Ușor' ,bg: 'Лек' ,hr: 'Lagano' + ,it: 'Leggero' } - ,'Medium' : { + ,'Medium' : { cs: 'Střední' ,de: 'Mittel' ,es: 'Medio' @@ -2762,8 +3003,9 @@ function init() { ,ro: 'Mediu' ,bg: 'Среден' ,hr: 'Srednje' + ,it: 'Medio' } - ,'Heavy' : { + ,'Heavy' : { cs: 'Velký' ,de: 'Schwer' ,es: 'Fuerte' @@ -2773,8 +3015,9 @@ function init() { ,ro: 'Puternic' ,bg: 'Висок' ,hr: 'Teško' + ,it: 'Pesante' } - ,'Treatment type' : { + ,'Treatment type' : { cs: 'Typ ošetření' ,de: 'Eingabe Typ' ,es: 'Tipo de tratamiento' @@ -2784,8 +3027,9 @@ function init() { ,ro: 'Tip tratament' ,bg: 'Вид събитие' ,hr: 'Vrsta tretmana' + ,it: 'Somministrazione' } - ,'Raw BG' : { + ,'Raw BG' : { cs: 'Glykémie z RAW dat' ,de: 'Roh Blutglukose' ,es: 'Glucemia en crudo' @@ -2795,8 +3039,9 @@ function init() { ,ro: 'Citire brută a glicemiei' ,bg: 'Непреработена КЗ' ,hr: 'Sirovi podaci o GUK-u' + ,it: 'Raw BG' } - ,'Device' : { + ,'Device' : { cs: 'Zařízení' ,de: 'Gerät' ,es: 'Dispositivo' @@ -2806,8 +3051,9 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,it: 'Dispositivo' } - ,'Noise' : { + ,'Noise' : { cs: 'Šum' ,de: 'Störgeräusch' ,es: 'Ruido' @@ -2817,8 +3063,9 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,it: 'Rumore' } - ,'Calibration' : { + ,'Calibration' : { cs: 'Kalibrace' ,de: 'Kalibrierung' ,es: 'Calibración' @@ -2828,8 +3075,9 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,it: 'Calibratura' } - ,'Show Plugins' : { + ,'Show Plugins' : { cs: 'Zobrazuj pluginy' ,de: 'Zeige Plugins' ,es: 'Mostrar Plugins' @@ -2838,8 +3086,9 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' + ,it: 'Mostra Plugin' } - ,'About' : { + ,'About' : { cs: 'O aplikaci' ,de: 'Über' ,es: 'Sobre' @@ -2849,8 +3098,9 @@ function init() { ,bg: 'Относно' ,hr: 'O aplikaciji' ,sv: 'Om' + ,it: 'Informazioni' } - ,'Value in' : { + ,'Value in' : { cs: 'Hodnota v' ,de: 'Wert in' ,es: 'Valor en' @@ -2860,8 +3110,9 @@ function init() { ,bg: 'Стойност в' ,hr: 'Vrijednost u' ,sv: 'Värde om' + ,it: 'Valore in' } - ,'Carb Time' : { + ,'Carb Time' : { cs: 'Čas jídla' ,de: 'Kohlenhydrate Zeit' ,es: 'Momento de la ingesta' @@ -2869,35 +3120,36 @@ function init() { ,bg: 'ВХ действа след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' + ,it: 'Tempo' } - - }; - - language.translate = function translate(text) { + + }; + + language.translate = function translate(text) { if (translations[text] && translations[text][lang]) { return translations[text][lang]; - } + } return text; }; - + language.DOMtranslate = function DOMtranslate($) { // do translation of static text on load $('.translate').each(function () { $(this).text(language.translate($(this).text())); - }); + }); $('.titletranslate, .tip').each(function () { $(this).attr('title',language.translate($(this).attr('title'))); $(this).attr('original-title',language.translate($(this).attr('original-title'))); $(this).attr('placeholder',language.translate($(this).attr('placeholder'))); - }); + }); }; - + language.set = function set(newlang) { lang = newlang; return language(); }; - + return language(); } -module.exports = init; +module.exports = init; \ No newline at end of file diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..9956e8a4eb8 100644 --- a/static/index.html +++ b/static/index.html @@ -106,6 +106,7 @@ + From bb850aea385a55d80cd5531d12dea90f60461bbb Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 13:47:31 +0200 Subject: [PATCH 616/661] update2 it-laguage.js Hi Jason, adapted words. Missing translation of announcement. (menu food). no charge translation of amount in gramns and the explanation of shows raw data BG. Thanks Matteo --- lib/language.js | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/language.js b/lib/language.js index cafe54b7f4f..1ce1888b379 100644 --- a/lib/language.js +++ b/lib/language.js @@ -223,7 +223,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' - ,it:'Categoria' + ,it: 'Categoria' } ,'Subcategory' : { cs: 'Podkategorie' @@ -1887,7 +1887,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' - ,it: 'Adesso' + ,it: 'Ora' } ,'Other' : { cs: 'Jiný' @@ -1983,7 +1983,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' - ,it: 'non autorizzato' + ,it: 'Non autorizzato' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -2199,7 +2199,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3h' + ,it: '3ORE } ,'6HR' : { cs: '6hod' @@ -2211,7 +2211,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' - ,it: '6h' + ,it: '6ORE' } ,'12HR' : { cs: '12hod' @@ -2223,7 +2223,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' - ,it: '12h' + ,it: '12ORE' } ,'24HR' : { cs: '24hod' @@ -2235,7 +2235,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' - ,it: '24h' + ,it: '24ORE' } ,'Settings' : { cs: 'Nastavení' @@ -2330,7 +2330,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' - ,it: 'bolo pasto' + ,it: 'Bolo Pasto' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2342,7 +2342,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' - ,it: 'Bolo merenda' + ,it: 'Bolo Merenda' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2354,7 +2354,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' - ,it: 'Correzione bolo' + ,it: 'Bolo Correttivo' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2366,7 +2366,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' - ,it: 'Correzione carboidrati' + ,it: 'Carboidrati Correttivi' } ,'Note' : { cs: 'Poznámka' @@ -2426,7 +2426,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' - ,it: 'Avvio sensore' + ,it: 'Avvio Sensore' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2438,7 +2438,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' - ,it: 'Cambio sensore' + ,it: 'Cambio Sensore' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2450,7 +2450,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' - ,it: 'Avvio sensore Dexcom' + ,it: 'Avvio Sensore Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2462,7 +2462,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' - ,it: 'Cambio sensore Dexcom' + ,it: 'Cambio Sensore Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2474,7 +2474,7 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' - ,it: 'Cambio cartuccia insulina' + ,it: 'Cambio Cartuccia Insulina' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2498,7 +2498,7 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' - ,it: 'Lettura glicemie' + ,it: 'Lettura Glicemie' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2581,7 +2581,7 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' - ,it: 'Attiva Allarme' + ,it: 'Attiva Allarmi' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2592,7 +2592,7 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' - ,it: 'Quando si attiva un allarme acustico.' + ,it: 'Attiverai gli allarmi acustici.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2603,7 +2603,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Allarme Urgente: Glicemia Molto Alta' + ,it: 'Urgente:Glicemia Molto Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2614,7 +2614,7 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' - ,it: 'Allarme: Glicemia Alta' + ,it: 'Glicemia Alta' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2625,7 +2625,7 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' - ,it: 'Allarme Glicemia bassa' + ,it: 'Glicemia Bassa' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2636,7 +2636,7 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' - ,it: 'Allarme Urgente. Glicemia Molto Bassa' + ,it: 'Urgente:Glicemia Bassa' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2647,7 +2647,7 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' - ,it: 'Dati obsoleti: Notifica' + ,it: 'Dati non aggiornati' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2659,7 +2659,7 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' - ,it: 'Dati obsoleti: Urgente' + ,it: 'Dati non aggiornati' } ,'mins' : { cs: 'min' @@ -2695,7 +2695,7 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' - ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' + ,it: 'Attivandola, la pagina sarà oscurata dalle 22:00 alle 06:00.' } ,'Enable' : { cs: 'Povoleno' @@ -2766,7 +2766,7 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' - ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi cioè i dati grezzi' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2825,7 +2825,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' - ,it: 'Resetta e utilizza le impostazioni predefinite' + ,it: 'Resetta le impostazioni' } ,'Calibrations' : { cs: 'Kalibrace' @@ -3086,7 +3086,7 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' - ,it: 'Mostra Plugin' + ,it: 'Mostra Plugins' } ,'About' : { cs: 'O aplikaci' @@ -3152,4 +3152,4 @@ function init() { return language(); } -module.exports = init; \ No newline at end of file +module.exports = init; From 0d87ca3f13a0a5b06af0b69486c61fc2f045908b Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 20:35:07 +0200 Subject: [PATCH 617/661] update2 it-laguage.js Jason , correct . --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index 1ce1888b379..b28f38a40d8 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2199,7 +2199,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3ORE + ,it: '3ORE' } ,'6HR' : { cs: '6hod' From e269db8a68ed58f72923d4e1ed4ec120ed1587b0 Mon Sep 17 00:00:00 2001 From: sebastianlorant Date: Mon, 17 Aug 2015 20:47:54 +0200 Subject: [PATCH 618/661] Edited by Johan Lorant (swedish) --- lib/language.js | 125 +++++++++++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 50 deletions(-) diff --git a/lib/language.js b/lib/language.js index b28f38a40d8..e11dbcb504c 100644 --- a/lib/language.js +++ b/lib/language.js @@ -223,7 +223,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' - ,it: 'Categoria' + ,it:'Categoria' } ,'Subcategory' : { cs: 'Podkategorie' @@ -460,6 +460,7 @@ function init() { ,fr: 'Montrer' ,pt: 'Mostrar' ,ro: 'Arată' + ,sv: 'Visa' ,bg: 'Покажи' ,hr: 'Prikaži' ,it: 'Mostra' @@ -591,6 +592,7 @@ function init() { ,fr: 'Taille' ,pt: 'Tamanho' ,ro: 'Mărime' + ,sv: 'Storlek' ,bg: 'Големина' ,hr: 'Veličina' ,it: 'Formato' @@ -601,6 +603,7 @@ function init() { ,es: '(ninguno)' ,fr: '(aucun)' ,pt: '(nenhum)' + ,sv: '(tom)' ,ro: '(fără)' ,bg: '(няма)' ,hr: '(Prazno)' @@ -612,6 +615,7 @@ function init() { ,es: 'Resultado vacío' ,fr: 'Pas de résultat' ,pt: 'Resultado vazio' + ,sv: 'Resultat saknas' ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' @@ -795,6 +799,7 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,sv: 'Kvadrant' ,it: 'Quartile' } ,'Date' : { @@ -839,7 +844,7 @@ function init() { ,es: 'Valores' ,fr: 'Valeurs' ,pt: 'Valores' - ,sv: 'Avläsning' + ,sv: 'Avläsningar' ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' @@ -852,7 +857,7 @@ function init() { ,fr: 'Déviation St.' ,pt: 'DesvPadr' ,sv: 'StdDev' - ,ro: 'Dev Std' + ,ro: 'Standarddeviation' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,it: 'Deviazione standard' @@ -947,7 +952,7 @@ function init() { ,es: 'N° de valores' ,fr: 'nbr de valeurs' ,pt: 'N° de valores' - ,sv: 'av avläsningar' + ,sv: '# av avläsningar' ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' @@ -1167,7 +1172,7 @@ function init() { ,ro: 'Șterge înregistrarea' ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' - ,sv: 'Ta bort post' + ,sv: 'Radera post' ,it: 'Cancella registro' } ,'Move to the top' : { @@ -1239,7 +1244,7 @@ function init() { ,ro: 'Cheie API înregistrată' ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' - ,sv: 'Hemlig API-hash lagrad' + ,sv: 'Lagrad hemlig API-hash' ,it: 'Hash API secreto memorizzato' } ,'Status' : { @@ -1503,7 +1508,7 @@ function init() { ,ro: 'Verificați conexiunea datelor introduse' ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' - ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,sv: 'Vänligen verifiera att inlagd data är korrekt' ,it: 'Si prega di verificare che i dati inseriti sono corretti' } ,'BG' : { @@ -1527,7 +1532,7 @@ function init() { ,ro: 'Folosește corecția de glicemie în calcule' ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' - ,sv: 'Använd BS-korrektion för uträkning' + ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' } ,'BG from CGM (autoupdated)' : { @@ -1611,7 +1616,7 @@ function init() { ,ro: 'Folosește corecția de carbohidrați în calcule' ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' - ,sv: 'Använd kolhydratkorrektion i utäkning' + ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' } ,'Use COB correction in calculation' : { @@ -1635,7 +1640,7 @@ function init() { ,ro: 'Folosește IOB în calcule' ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' - ,sv: 'Använd aktivt insulin för uträkning' + ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' } ,'Other correction' : { @@ -1887,7 +1892,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' - ,it: 'Ora' + ,it: 'Adesso' } ,'Other' : { cs: 'Jiný' @@ -1983,7 +1988,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' - ,it: 'Non autorizzato' + ,it: 'non autorizzato' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -2199,7 +2204,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' - ,it: '3ORE' + ,it: '3h' } ,'6HR' : { cs: '6hod' @@ -2211,7 +2216,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' - ,it: '6ORE' + ,it: '6h' } ,'12HR' : { cs: '12hod' @@ -2223,7 +2228,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' - ,it: '12ORE' + ,it: '12h' } ,'24HR' : { cs: '24hod' @@ -2235,7 +2240,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' - ,it: '24ORE' + ,it: '24h' } ,'Settings' : { cs: 'Nastavení' @@ -2243,6 +2248,7 @@ function init() { ,es: 'Ajustes' ,fr: 'Paramètres' ,pt: 'Definições' + ,sv: 'Inställningar' ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' @@ -2314,7 +2320,7 @@ function init() { ,es: 'Control de glucemia' ,fr: 'Contrôle glycémie' ,pt: 'Medida de glicemia' - ,sv: 'BS-kontroll' + ,sv: 'Blodsockerkontroll' ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' @@ -2330,7 +2336,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' - ,it: 'Bolo Pasto' + ,it: 'bolo pasto' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2342,7 +2348,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' - ,it: 'Bolo Merenda' + ,it: 'Bolo merenda' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2354,7 +2360,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' - ,it: 'Bolo Correttivo' + ,it: 'Correzione bolo' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2366,7 +2372,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' - ,it: 'Carboidrati Correttivi' + ,it: 'Correzione carboidrati' } ,'Note' : { cs: 'Poznámka' @@ -2375,9 +2381,9 @@ function init() { ,fr: 'Note' ,pt: 'Nota' ,ro: 'Notă' + ,sv: 'Notering' ,bg: 'Бележка' ,hr: 'Bilješka' - ,sv: 'Notering' ,it: 'Nota' } ,'Question' : { @@ -2386,8 +2392,8 @@ function init() { ,es: 'Pregunta' ,fr: 'Question' ,pt: 'Pergunta' - ,sv: 'Fråga' ,ro: 'Întrebare' + ,sv: 'Fråga' ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' @@ -2398,10 +2404,10 @@ function init() { ,es: 'Ejercicio' ,fr: 'Exercice' ,pt: 'Exercício' + ,sv: 'Aktivitet' ,ro: 'Activitate fizică' ,bg: 'Спорт' ,hr: 'Aktivnost' - ,sv: 'Aktivitet' ,it: 'Esercizio' } ,'Pump Site Change' : { @@ -2410,10 +2416,10 @@ function init() { ,es: 'Cambio de catéter' ,fr: 'Changement de site pompe' ,pt: 'Troca de catéter' + ,sv: 'Pump/nålbyte' ,ro: 'Schimbare loc pompă' ,bg: 'Смяна на сет' ,hr: 'Promjena seta' - ,sv: 'Pump/nålbyte' ,it: 'Cambio Ago' } ,'Sensor Start' : { @@ -2426,7 +2432,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' - ,it: 'Avvio Sensore' + ,it: 'Avvio sensore' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2438,7 +2444,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' - ,it: 'Cambio Sensore' + ,it: 'Cambio sensore' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2450,7 +2456,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' - ,it: 'Avvio Sensore Dexcom' + ,it: 'Avvio sensore Dexcom' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2462,7 +2468,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' - ,it: 'Cambio Sensore Dexcom' + ,it: 'Cambio sensore Dexcom' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2470,11 +2476,11 @@ function init() { ,es: 'Cambio de reservorio de insulina' ,fr: 'Changement cartouche d\'insuline' ,pt: 'Troca de reservatório de insulina' + ,sv: 'Insulinreservoarbyte' ,ro: 'Schimbare cartuș insulină' ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' - ,sv: 'Insulinreservoarbyte' - ,it: 'Cambio Cartuccia Insulina' + ,it: 'Cambio cartuccia insulina' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2482,10 +2488,10 @@ function init() { ,es: 'Alerta de perro de alerta diabética' ,fr: 'Wouf! Wouf! Chien d\'alerte diabète' ,pt: 'Alerta de cão sentinela de diabetes' + ,sv: 'Diabeteshundlarm (Duktig vovve!)' ,ro: 'Alertă câine de serviciu' ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' - ,sv: 'Voff voff! (Diabeteshundalarm!)' ,it: 'Allarme D.A.D.' } ,'Glucose Reading' : { @@ -2494,11 +2500,11 @@ function init() { ,es: 'Valor de glucemia' ,fr: 'Valeur de glycémie' ,pt: 'Valor de glicemia' - ,sv: 'Blodglukosavläsning' + ,sv: 'Glukosvärde' ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' - ,it: 'Lettura Glicemie' + ,it: 'Lettura glicemie' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2506,10 +2512,10 @@ function init() { ,es: 'Método de medida' ,fr: 'Méthode de mesure' ,pt: 'Método de medida' + ,sv: 'Mätmetod' ,ro: 'Metodă măsurare' ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' - ,sv: 'Mätmetod' ,it: 'Metodo di misurazione' } ,'Meter' : { @@ -2530,10 +2536,10 @@ function init() { ,es: 'Insulina' ,fr: 'Insuline donnée' ,pt: 'Insulina' + ,sv: 'Insulindos' ,ro: 'Insulină administrată' ,bg: 'Инсулин' ,hr: 'Količina iznulina' - ,sv: 'Insulindos' ,it: 'Insulina' } ,'Amount in grams' : { @@ -2578,10 +2584,11 @@ function init() { ,es: 'Activar las alarmas' ,fr: 'Activer les alarmes' ,pt: 'Ativar alarmes' + ,sv: 'Aktivera larm' ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' - ,it: 'Attiva Allarmi' + ,it: 'Attiva Allarme' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2590,9 +2597,10 @@ function init() { ,fr: 'Si activée, un alarme peut sonner.' ,pt: 'Quando ativado, um alarme poderá soar' ,ro: 'Când este activ, poate suna o alarmă.' + ,sv: 'När markerad är ljudlarm aktivt' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' - ,it: 'Attiverai gli allarmi acustici.' + ,it: 'Quando si attiva un allarme acustico.' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2600,10 +2608,11 @@ function init() { ,es: 'Alarma de glucemia alta urgente' ,fr: 'Alarme haute urgente' ,pt: 'Alarme de alto urgente' + ,sv: 'Brådskande högt larmvärde' ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Urgente:Glicemia Molto Alta' + ,it: 'Allarme Urgente: Glicemia Molto Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2611,10 +2620,11 @@ function init() { ,es: 'Alarma de glucemia alta' ,fr: 'Alarme haute' ,pt: 'Alarme de alto' + ,sv: 'Högt larmvärde' ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' - ,it: 'Glicemia Alta' + ,it: 'Allarme: Glicemia Alta' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2622,10 +2632,11 @@ function init() { ,es: 'Alarma de glucemia baja' ,fr: 'Alarme basse' ,pt: 'Alarme de baixo' + ,sv: 'Lågt larmvärde' ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' - ,it: 'Glicemia Bassa' + ,it: 'Allarme Glicemia bassa' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2633,10 +2644,11 @@ function init() { ,es: 'Alarma de glucemia baja urgente' ,fr: 'Alarme basse urgente' ,pt: 'Alarme de baixo urgente' + ,sv: 'Brådskande lågt larmvärde' ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' - ,it: 'Urgente:Glicemia Bassa' + ,it: 'Allarme Urgente. Glicemia Molto Bassa' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2644,10 +2656,11 @@ function init() { ,es: 'Datos obsoletos: aviso' ,fr: 'Données dépassées: avis' ,pt: 'Dados antigos: aviso' + ,sv: 'Förfluten data: Varning!' ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' - ,it: 'Dati non aggiornati' + ,it: 'Dati obsoleti: Notifica' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2655,11 +2668,11 @@ function init() { ,es: 'Datos obsoletos: Urgente' ,fr: 'Données dépassées urgentes' ,pt: 'Dados antigos: Urgente' - ,sv: 'Brådskande varning, Inaktuell data' + ,sv: 'Brådskande varning, inaktuell data' ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' - ,it: 'Dati non aggiornati' + ,it: 'Dati obsoleti: Urgente' } ,'mins' : { cs: 'min' @@ -2692,10 +2705,11 @@ function init() { ,es: 'Cuando esté activo, el brillo de la página bajará de 10pm a 6am.' ,fr: 'Si activé, la page sera assombire de 22:00 à 6:00' ,pt: 'Se ativado, a página será escurecida de 22h a 6h' + ,sv: 'När aktiverad dimmas sidan mellan 22:00 - 06:00' ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' - ,it: 'Attivandola, la pagina sarà oscurata dalle 22:00 alle 06:00.' + ,it: 'Attivandola, la pagina sarà oscurato da 10:00-06:00.' } ,'Enable' : { cs: 'Povoleno' @@ -2715,10 +2729,10 @@ function init() { ,es: 'Mostrat datos en glucemia en crudo' ,fr: 'Montrer les données BG brutes' ,pt: 'Mostrar dados de glicemia não processados' + ,sv: 'Visa RAW-data' ,ro: 'Afișează date primare glicemie' ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' - ,sv: 'Visa RAW-data' ,it: 'Mostra dati Raw BG' } ,'Never' : { @@ -2763,10 +2777,11 @@ function init() { ,es: 'Cuando esté activo, pequeños puntos blancos mostrarán los datos en crudo' ,fr: 'Si activé, des points blancs représenteront les données brutes' ,pt: 'Se ativado, pontinhos brancos representarão os dados de glicemia não processados' + ,sv: 'När aktiverad visar de vita punkterna RAW-blodglukosevärden' ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' - ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi cioè i dati grezzi' + ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2798,6 +2813,7 @@ function init() { ,es: 'Por defecto' ,fr: 'Par défaut' ,pt: 'Padrão' + ,sv: 'Standard' ,ro: 'Implicită' ,bg: 'Черно-бяла' ,hr: 'Default' @@ -2810,6 +2826,7 @@ function init() { ,es: 'Colores' ,fr: 'Couleurs' ,pt: 'Cores' + ,sv: 'Färg' ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' @@ -2825,7 +2842,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' - ,it: 'Resetta le impostazioni' + ,it: 'Resetta e utilizza le impostazioni predefinite' } ,'Calibrations' : { cs: 'Kalibrace' @@ -2833,6 +2850,7 @@ function init() { ,es: 'Calibraciones' ,fr: 'Calibration' ,pt: 'Calibraçôes' + ,sv: 'Kalibreringar' ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' @@ -2844,6 +2862,7 @@ function init() { ,es: 'Test de Alarma / Activar teléfono' ,fr: 'Test alarme / Activer Smartphone' ,pt: 'Testar Alarme / Ativar Smartphone' + ,sv: 'Testa alarm / Aktivera Smatphone' ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' @@ -2879,6 +2898,7 @@ function init() { ,es: 'tiempo atrás' ,fr: 'temps avant' ,pt: 'tempo atrás' + ,sv: 'förfluten tid' ,sv: 'tid sedan' ,ro: 'în trecut' ,bg: 'преди време' @@ -2891,6 +2911,7 @@ function init() { ,es: 'hr atrás' ,fr: 'hr avant' ,pt: 'h atrás' + ,sv: 'timmar sedan' ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' @@ -3051,6 +3072,7 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,sv: 'Enhet' ,it: 'Dispositivo' } ,'Noise' : { @@ -3063,6 +3085,7 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,sv: 'Brus' ,it: 'Rumore' } ,'Calibration' : { @@ -3075,6 +3098,7 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,sv: 'Kalibrering' ,it: 'Calibratura' } ,'Show Plugins' : { @@ -3086,7 +3110,8 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' - ,it: 'Mostra Plugins' + ,sv: 'Visa tillägg' + ,it: 'Mostra Plugin' } ,'About' : { cs: 'O aplikaci' From 0deac611f39ee13040498cf9abebf7202e21fba5 Mon Sep 17 00:00:00 2001 From: Matteo Neri Date: Mon, 17 Aug 2015 21:51:17 +0200 Subject: [PATCH 619/661] update3 it-laguage.js Jason: last adjustment : 1 line. thanks --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index b28f38a40d8..64628cc922e 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2603,7 +2603,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' - ,it: 'Urgente:Glicemia Molto Alta' + ,it: 'Urgente:Glicemia Alta' } ,'High Alarm' : { cs: 'Vysoká glykémie' From 59f7c258bdede7bd84d70d24c258b5e7d5d0729b Mon Sep 17 00:00:00 2001 From: xpucuto Date: Tue, 18 Aug 2015 15:10:36 +0300 Subject: [PATCH 620/661] Bulgarian language edit - 18.08.2015 I think i made it right this time :) --- lib/language.js | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/language.js b/lib/language.js index 00506aa4556..b2de6c8ad62 100644 --- a/lib/language.js +++ b/lib/language.js @@ -521,7 +521,7 @@ function init() { ,pt: 'Carregando dados de alimentos' ,sv: 'Laddar födoämnesdatabas' ,ro: 'Încarc baza de date de alimente' - ,bg: 'Зареждане на данни за храни' + ,bg: 'Зареждане на данни с храни' ,hr: 'Učitavanje baze podataka o hrani' ,it: 'Carico dati alimenti' } @@ -990,7 +990,7 @@ function init() { ,pt: 'Max' ,sv: 'Max' ,ro: 'Max' - ,bg: 'Максимално' + ,bg: 'Макс.' ,hr: 'Max' ,it: 'Max' } @@ -1002,7 +1002,7 @@ function init() { ,pt: 'Min' ,sv: 'Min' ,ro: 'Min' - ,bg: 'Минимално' + ,bg: 'Мин.' ,hr: 'Min' ,it: 'Min' } @@ -1530,7 +1530,7 @@ function init() { ,fr: 'Utiliser la correction de glycémie dans les calculs' ,pt: 'Usar correção de glicemia nos cálculos' ,ro: 'Folosește corecția de glicemie în calcule' - ,bg: 'Въведи корекция за КЗ ' + ,bg: 'Използвай корекцията за КЗ в изчислението' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för beräkning' ,it: 'Utilizzare la correzione Glicemia nei calcoli' @@ -1614,7 +1614,7 @@ function init() { ,fr: 'Utiliser la correction en glucides dans les calculs' ,pt: 'Usar correção com carboidratos no cálculo' ,ro: 'Folosește corecția de carbohidrați în calcule' - ,bg: 'Въведи корекция за въглехидратите' + ,bg: 'Включи корекцията чрез ВХ в изчислението' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion för beräkning' ,it: 'Utilizzare la correzione con carboidrati nel calcolo' @@ -1626,7 +1626,7 @@ function init() { ,fr: 'Utiliser les COB dans les calculs' ,pt: 'Usar COB no cálculo' ,ro: 'Folosește COB în calcule' - ,bg: 'Въведи корекция за останалите въглехидрати' + ,bg: 'Включи активните ВХ в изчислението' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' ,it: 'Utilizzare la correzione COB nel calcolo' @@ -1638,7 +1638,7 @@ function init() { ,fr: 'Utiliser l\'IOB dans les calculs' ,pt: 'Usar IOB no cálculo' ,ro: 'Folosește IOB în calcule' - ,bg: 'Използвай активния инсулин' + ,bg: 'Включи активния инсулин в изчислението' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för beräkning' ,it: 'Utilizzare la correzione IOB nel calcolo' @@ -1986,7 +1986,7 @@ function init() { ,pt: 'Não autorizado' ,sv: 'Ej behörig' ,ro: 'Neautorizat' - ,bg: 'Нямаш достъп' + ,bg: 'Неразрешен достъп' ,hr: 'Neautorizirano' ,it: 'non autorizzato' } @@ -2369,7 +2369,7 @@ function init() { ,fr: 'Correction glucide' ,pt: 'Carboidrato de correção' ,ro: 'Corecție de carbohidrați' - ,bg: 'Корекция за въглехидратите' + ,bg: 'Корекция чрез въглехидрати' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' ,it: 'Correzione carboidrati' @@ -2397,6 +2397,9 @@ function init() { ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' + } + ,'Announcement' : { + bg: 'Известяване' } ,'Exercise' : { cs: 'Cvičení' @@ -2550,7 +2553,7 @@ function init() { ,pt: 'Quantidade em gramas' ,sv: 'Antal gram' ,ro: 'Cantitate în grame' - ,bg: ' К-во в грамове' + ,bg: 'К-во в грамове' ,hr: 'Količina u gramima' ,it: 'Quantità in grammi' } @@ -2719,7 +2722,7 @@ function init() { ,pt: 'Ativar' ,sv: 'Aktivera' ,ro: 'Activează' - ,bg: 'Активно' + ,bg: 'Активен' ,hr: 'Aktiviraj' ,it: 'Permettere' } @@ -2779,7 +2782,7 @@ function init() { ,pt: 'Se ativado, pontinhos brancos representarão os dados de glicemia não processados' ,sv: 'När aktiverad visar de vita punkterna RAW-blodglukosevärden' ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' - ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' + ,bg: 'Когато е активно, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' ,it: 'Quando lo abiliti, visualizzerai piccoli puntini bianchi (raw BG data)' } @@ -2935,7 +2938,7 @@ function init() { ,pt: 'min atrás' ,sv: 'minut sedan' ,ro: 'minut în trecut' - ,bg: 'минута по-рано' + ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuto fa' @@ -2948,7 +2951,7 @@ function init() { ,pt: 'min atrás' ,sv: 'minuter sedan' ,ro: 'minute în trecut' - ,bg: 'минути по-рано' + ,bg: 'мин. по-рано' ,hr: 'minuta unazad' ,it: 'minuti fa' } @@ -3032,7 +3035,7 @@ function init() { ,pt: 'Pesado' ,sv: 'Rikligt' ,ro: 'Puternic' - ,bg: 'Висок' + ,bg: 'Силен' ,hr: 'Teško' ,it: 'Pesante' } @@ -3137,7 +3140,7 @@ function init() { ,de: 'Kohlenhydrate Zeit' ,es: 'Momento de la ingesta' ,fr: 'Moment de Glucide' - ,bg: 'ВХ действа след' + ,bg: 'Ядене след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' ,it: 'Tempo' From 902f41eb364e8fd155e24a0d35b8025bcc7a5938 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 18 Aug 2015 22:15:05 -0700 Subject: [PATCH 621/661] fix css indents --- static/css/main.css | 756 ++++++++++++++++++++++---------------------- 1 file changed, 378 insertions(+), 378 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 1c87a86584f..40aa2177fcf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -4,26 +4,26 @@ html, body { - height: 100%; - margin: 0; - padding: 0; + height: 100%; + margin: 0; + padding: 0; } body { - font-family: 'Open Sans', Helvetica, Arial, sans-serif; - background: #000; - color: #bdbdbd; + font-family: 'Open Sans', Helvetica, Arial, sans-serif; + background: #000; + color: #bdbdbd; } .container { - bottom: 0; - display: block; - height: 100%; - left: 0; - margin: 0; - padding: 0; - top: 45px; - width: 100%; - z-index: 2; + bottom: 0; + display: block; + height: 100%; + left: 0; + margin: 0; + padding: 0; + top: 45px; + width: 100%; + z-index: 2; } #container.announcing .customTitle { @@ -39,10 +39,10 @@ body { } .primary { - font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; - vertical-align: middle; - clear: both; + font-family: 'Ubuntu', Helvetica, Arial, sans-serif; + height: 180px; + vertical-align: middle; + clear: both; } .has-minor-pills .primary { @@ -50,10 +50,10 @@ body { } .bgStatus { - float: right; - text-align: center; - white-space: nowrap; - padding-right: 20px; + float: right; + text-align: center; + white-space: nowrap; + padding-right: 20px; } .urgent .bgButton { @@ -69,10 +69,10 @@ body { } .bgStatus .currentBG { - font-size: 120px; - line-height: 100px; - text-decoration: line-through; - vertical-align: middle; + font-size: 120px; + line-height: 100px; + text-decoration: line-through; + vertical-align: middle; } .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { @@ -89,50 +89,50 @@ body { } .bgStatus .majorPills { - font-size: 30px; + font-size: 30px; } .minorPills { - font-size: 22px; - margin-top: 10px; - z-index: 500; + font-size: 22px; + margin-top: 10px; + z-index: 500; } .majorPills > span:not(:first-child) { - margin-left: 5px; + margin-left: 5px; } .minorPills > span:not(:first-child) { - margin-left: 5px; + margin-left: 5px; } .pill { - white-space: nowrap; - border-radius: 5px; - border: 2px solid #808080; + white-space: nowrap; + border-radius: 5px; + border: 2px solid #808080; } .pill em, .pill label { - padding-left: 2px; - padding-right: 2px; + padding-left: 2px; + padding-right: 2px; } .pill em { - font-style: normal; - font-weight: bold; + font-style: normal; + font-weight: bold; } .pill label { - color: #000; - background: #808080; + color: #000; + background: #808080; } .pill.warn em { - color: yellow !important; + color: yellow !important; } .pill.urgent em { - color: red !important; + color: red !important; } .alarming-timeago #lastEntry.pill.warn { @@ -150,7 +150,7 @@ body { } .bgStatus.current .currentBG { - text-decoration: none; + text-decoration: none; } .alarming-timeago .bgStatus.current .currentBG { @@ -158,65 +158,65 @@ body { } .status { - color: #808080; - font-size: 100px; - line-height: 100px; + color: #808080; + font-size: 100px; + line-height: 100px; } .statusBox { - text-align: center; - width: 250px; + text-align: center; + width: 250px; } .statusPills { - font-size: 20px; - line-height: 30px; + font-size: 20px; + line-height: 30px; } .loading .statusPills { - display: none; + display: none; } .statusPills .pill { - background: #808080; - border-color: #808080; + background: #808080; + border-color: #808080; } .statusPills .pill em { - color: #bdbdbd; - background: #000; - border-radius: 4px 0 0 4px; + color: #bdbdbd; + background: #000; + border-radius: 4px 0 0 4px; } .statusPills .pill label { - background: #808080; + background: #808080; } .pill.upbat label { - padding: 0 !important; + padding: 0 !important; } #chartContainer { - left:0; - right:0; - bottom:0; - height:auto; - font-size: 20px; - background: #111; - display: block; - position:absolute; + left:0; + right:0; + bottom:0; + height:auto; + font-size: 20px; + background: #111; + display: block; + position:absolute; } #chartContainer svg { - width: 100%; + width: 100%; } #silenceBtn { - z-index: 99; - border-radius: 5px; - border: 2px solid #bdbdbd; + z-index: 99; + border-radius: 5px; + border: 2px solid #bdbdbd; } #silenceBtn a { - font-size: 40px + font-size: 40px } .bgButton { @@ -227,99 +227,99 @@ body { } .loading .pill.rawbg { - display: none; + display: none; } .pill.rawbg { - display: inline-block; - border-radius: 2px; - border: 1px solid #808080; + display: inline-block; + border-radius: 2px; + border: 1px solid #808080; } .pill.rawbg em { - color: white; - background-color: black; - display: block; - font-size: 20px; + color: white; + background-color: black; + display: block; + font-size: 20px; } .pill.rawbg label { - display: block; - font-size: 14px; + display: block; + font-size: 14px; } .alarming .bgButton { - border-color: #bdbdbd; - color: #000; - box-shadow: 2px 2px 0 #ddd; + border-color: #bdbdbd; + color: #000; + box-shadow: 2px 2px 0 #ddd; } .alarming.urgent .bgButton { - background-color: red; + background-color: red; } .alarming.warning .bgButton { - background-color: yellow; + background-color: yellow; } .alarming .bgButton:active { - border: 2px solid #bdbdbd; - box-shadow: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; + border: 2px solid #bdbdbd; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; } .button { - text-align: center; - background: #ababab; - margin: 10px auto; + text-align: center; + background: #ababab; + margin: 10px auto; } .tooltip { - top: 0; - left: 0; - color: #444; - position: absolute; - text-align: left; - padding: 4px; - font-size: 14px; - line-height: 15px; - background: white; - border: 0; - border-radius: 8px; + top: 0; + left: 0; + color: #444; + position: absolute; + text-align: left; + padding: 4px; + font-size: 14px; + line-height: 15px; + background: white; + border: 0; + border-radius: 8px; } .alarms { - display: none; + display: none; } .loading .focus-range { - display: none; + display: none; } .focus-range { - list-style: none; - margin: 4px; - padding: 0; - color: #808080; - width: 250px; - text-align: center; + list-style: none; + margin: 4px; + padding: 0; + color: #808080; + width: 250px; + text-align: center; } .focus-range li { - display: inline-block; - font-size: 18px; - white-space: nowrap; - border-radius: 5px; - border: 2px solid #000; - cursor: pointer; + display: inline-block; + font-size: 18px; + white-space: nowrap; + border-radius: 5px; + border: 2px solid #000; + cursor: pointer; } .focus-range .selected { - border-color: #808080; - color: #000; - background: #808080; + border-color: #808080; + color: #000; + background: #808080; } .pill.hidden { @@ -327,322 +327,322 @@ body { } @media (max-width: 800px) { - .bgStatus { - width: 300px; - } + .bgStatus { + width: 300px; + } - .bgButton { - width: auto; - } + .bgButton { + width: auto; + } - .bgStatus .currentBG { - font-size: 80px; - line-height: 80px; - } + .bgStatus .currentBG { + font-size: 80px; + line-height: 80px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 70px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 70px; + } - .bgStatus .pill.direction { - font-size: 70px; - line-height: 70px; - } + .bgStatus .pill.direction { + font-size: 70px; + line-height: 70px; + } - .bgStatus .majorPills { - font-size: 20px; - } + .bgStatus .majorPills { + font-size: 20px; + } - .bgStatus .minorPills { - font-size: 16px; - } + .bgStatus .minorPills { + font-size: 16px; + } - .status { - font-size: 70px; - line-height: 60px; - padding-top: 8px; - } + .status { + font-size: 70px; + line-height: 60px; + padding-top: 8px; + } - .statusPills { - font-size: 15px; - line-height: 40px; - } + .statusPills { + font-size: 15px; + line-height: 40px; + } - .pill.upbat label { - font-size: 15px !important; - } + .pill.upbat label { + font-size: 15px !important; + } - .focus-range { - margin: 0; - } + .focus-range { + margin: 0; + } - .focus-range li { - font-size: 14px; - } + .focus-range li { + font-size: 14px; + } - #silenceBtn a { - font-size: 30px; - } + #silenceBtn a { + font-size: 30px; + } - #chartContainer { - font-size: 14px; - } + #chartContainer { + font-size: 14px; + } } @media (max-width: 750px) { - .bgStatus { - width: 240px; - padding: 0; - } + .bgStatus { + width: 240px; + padding: 0; + } - .pill.rawbg em { - font-size: 14px; - } + .pill.rawbg em { + font-size: 14px; + } - .pill.rawbg label { - font-size: 12px; - } + .pill.rawbg label { + font-size: 12px; + } - .bgStatus .currentBG { - font-size: 70px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 70px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 50px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 50px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .minorPills { - font-size: 12px; - } + .bgStatus .minorPills { + font-size: 12px; + } - .status { - font-size: 50px; - line-height: 35px; - width: 250px; - } + .status { + font-size: 50px; + line-height: 35px; + width: 250px; + } } @media (max-width: 400px) { - .primary { - text-align: center; - margin-bottom: 0; - height: 152px; - } - - .bgStatus { - float: none; - padding: 0; - text-align: center; - width: 250px; - } - - - .bgButton { - margin: 5px; - width: auto; - } - - .bgStatus .currentBG { - font-size: 70px; - line-height: 70px; - } - - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 60px; - padding-left: 20px; - } - - .bgStatus .pill.direction { - font-size: 60px; - line-height: 60px; - } - - .bgStatus .majorPills { - font-size: 15px; - } - - .bgStatus .minorPills { - font-size: 12px; - } - - #silenceBtn a { - font-size: 20px; - } - - .status { - padding-top: 0; - font-size: 20px !important; - } - - .statusBox { - width: auto; - } - - #currentTime { - display: inline; - vertical-align: middle; - } - - .statusPills { - display: inline; - margin-left: auto; - font-size: 15px; - } - - .pill.upbat label { - font-size: 15px !important; - } - - .focus-range { - position: absolute; - top: 80px; - left: auto; - right: 10px; - margin: 0; - width: auto; - } - - .focus-range li { - display: block; - } + .primary { + text-align: center; + margin-bottom: 0; + height: 152px; + } + + .bgStatus { + float: none; + padding: 0; + text-align: center; + width: 250px; + } + + + .bgButton { + margin: 5px; + width: auto; + } + + .bgStatus .currentBG { + font-size: 70px; + line-height: 70px; + } + + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 60px; + padding-left: 20px; + } + + .bgStatus .pill.direction { + font-size: 60px; + line-height: 60px; + } + + .bgStatus .majorPills { + font-size: 15px; + } + + .bgStatus .minorPills { + font-size: 12px; + } + + #silenceBtn a { + font-size: 20px; + } + + .status { + padding-top: 0; + font-size: 20px !important; + } + + .statusBox { + width: auto; + } + + #currentTime { + display: inline; + vertical-align: middle; + } + + .statusPills { + display: inline; + margin-left: auto; + font-size: 15px; + } + + .pill.upbat label { + font-size: 15px !important; + } + + .focus-range { + position: absolute; + top: 80px; + left: auto; + right: 10px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } } @media (max-height: 700px) { - #chartContainer { - font-size: 14px; - } + #chartContainer { + font-size: 14px; + } } @media (max-height: 600px) { - #chartContainer { - font-size: 12px; - } + #chartContainer { + font-size: 12px; + } } @media (max-height: 480px) { - #toolbar { - float: right; - height: auto !important; - } + #toolbar { + float: right; + height: auto !important; + } - #toolbar .customTitle { - display: none; - } + #toolbar .customTitle { + display: none; + } - .container { - top: 15px; - padding-bottom: 20px; - } + .container { + top: 15px; + padding-bottom: 20px; + } - #chartContainer { - font-size: 10px; - } + #chartContainer { + font-size: 10px; + } } @media (max-height: 480px) and (min-width: 400px) { - .bgStatus .currentBG { - font-size: 70px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 70px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 70px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 70px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .minorPills { - font-size: 12px; - } + .bgStatus .minorPills { + font-size: 12px; + } - .status { - font-size: 50px; - line-height: 40px; - padding-top: 5px; - } + .status { + font-size: 50px; + line-height: 40px; + padding-top: 5px; + } - .statusPills { - font-size: 15px; - } + .statusPills { + font-size: 15px; + } - .pill.upbat label { - font-size: 15px !important; - } + .pill.upbat label { + font-size: 15px !important; + } } @media (max-height: 480px) and (max-width: 400px) { - .bgStatus { - text-align: center; - width: 220px; - left: 0; - position: absolute; - } + .bgStatus { + text-align: center; + width: 220px; + left: 0; + position: absolute; + } - .bgStatus .currentBG { - font-size: 60px; - line-height: 60px; - } + .bgStatus .currentBG { + font-size: 60px; + line-height: 60px; + } - .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { - font-size: 60px; - } + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { + font-size: 60px; + } - .bgStatus .pill.direction { - font-size: 50px; - line-height: 50px; - } + .bgStatus .pill.direction { + font-size: 50px; + line-height: 50px; + } - .bgStatus .majorPills { - font-size: 15px; - } + .bgStatus .majorPills { + font-size: 15px; + } - .bgStatus .majorPills { - font-size: 12px; - } + .bgStatus .majorPills { + font-size: 12px; + } - #silenceBtn { - right: -25px; - } + #silenceBtn { + right: -25px; + } .focus-range { - position: absolute; - top: 40px; - left: auto; - right: 35px; - margin: 0; - width: auto; - } - - .focus-range li { - display: block; - } - - .status { - position: absolute; - top: 90px; - } - - .statusBox { - width: 220px; - } + position: absolute; + top: 40px; + left: auto; + right: 35px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } + + .status { + position: absolute; + top: 90px; + } + + .statusBox { + width: 220px; + } } From b007b6779fb10017de1cfed50136449e8749f185 Mon Sep 17 00:00:00 2001 From: jweismann Date: Wed, 19 Aug 2015 09:47:23 +0200 Subject: [PATCH 622/661] danish translation --- lib/language.js | 262 ++++++++++++++++++++++++++++++++++++++++++++++ static/index.html | 1 + 2 files changed, 263 insertions(+) diff --git a/lib/language.js b/lib/language.js index 3ac555045b0..f9b98c84f99 100644 --- a/lib/language.js +++ b/lib/language.js @@ -18,6 +18,7 @@ function init() { ,ro: 'Activ pe portul' ,bg: 'Активиране на порта' ,hr: 'Slušanje na portu' + ,dk: 'Lytter på port' } // Client ,'Language' : { @@ -52,6 +53,9 @@ function init() { } , 'Swedish': { + } + , 'Danish': { + } ,'Mo' : { cs: 'Po' @@ -63,6 +67,7 @@ function init() { ,ro: 'Lu' ,bg: 'Пон' ,hr: 'Pon' + ,dk: 'Man' } ,'Tu' : { cs: 'Út' @@ -74,6 +79,7 @@ function init() { ,ro: 'Ma' ,bg: 'Вт' ,hr: 'Ut' + ,dk: 'Tir' }, ',We' : { cs: 'St' @@ -85,6 +91,7 @@ function init() { ,ro: 'Mie' ,bg: 'Ср' ,hr: 'Sri' + ,dk: 'Ons' } ,'Th' : { cs: 'Čt' @@ -96,6 +103,7 @@ function init() { ,ro: 'Jo' ,bg: 'Четв' ,hr: 'Čet' + ,dk: 'Tor' } ,'Fr' : { cs: 'Pá' @@ -107,6 +115,7 @@ function init() { ,ro: 'Vi' ,bg: 'Пет' ,hr: 'Pet' + ,dk: 'Fre' } ,'Sa' : { cs: 'So' @@ -118,6 +127,7 @@ function init() { ,ro: 'Sa' ,bg: 'Съб' ,hr: 'Sub' + ,dk: 'Lør' } ,'Su' : { cs: 'Ne' @@ -129,6 +139,7 @@ function init() { ,ro: 'Du' ,bg: 'Нед' ,hr: 'Ned' + ,dk: 'Søn' } ,'Monday' : { cs: 'Pondělí' @@ -140,6 +151,7 @@ function init() { ,ro: 'Luni' ,bg: 'Понеделник' ,hr: 'Ponedjeljak' + ,dk: 'Mandag' } ,'Tuesday' : { cs: 'Úterý' @@ -151,6 +163,7 @@ function init() { ,bg: 'Вторник' ,hr: 'Utorak' ,sv: 'Tisdag' + ,dk: 'Tirsdag' } ,'Wednesday' : { cs: 'Středa' @@ -162,6 +175,7 @@ function init() { ,ro: 'Miercuri' ,bg: 'Сряда' ,hr: 'Srijeda' + ,dk: 'Onsdag' } ,'Thursday' : { cs: 'Čtvrtek' @@ -173,6 +187,7 @@ function init() { ,ro: 'Joi' ,bg: 'Четвъртък' ,hr: 'Četvrtak' + ,dk: 'Torsdag' } ,'Friday' : { cs: 'Pátek' @@ -184,6 +199,7 @@ function init() { ,es: 'Viernes' ,bg: 'Петък' ,hr: 'Petak' + ,dk: 'Fredag' } ,'Saturday' : { cs: 'Sobota' @@ -195,6 +211,7 @@ function init() { ,bg: 'Събота' ,hr: 'Subota' ,sv: 'Lördag' + ,dk: 'Lørdag' } ,'Sunday' : { cs: 'Neděle' @@ -206,6 +223,7 @@ function init() { ,bg: 'Неделя' ,hr: 'Nedjelja' ,sv: 'Söndag' + ,dk: 'Søndag' } ,'Category' : { cs: 'Kategorie' @@ -217,6 +235,7 @@ function init() { ,ro: 'Categorie' ,bg: 'Категория' ,hr: 'Kategorija' + ,dk: 'Kategori' } ,'Subcategory' : { cs: 'Podkategorie' @@ -228,6 +247,7 @@ function init() { ,ro: 'Subcategorie' ,bg: 'Подкатегория' ,hr: 'Podkategorija' + ,dk: 'Underkategori' } ,'Name' : { cs: 'Jméno' @@ -239,6 +259,7 @@ function init() { ,ro: 'Nume' ,bg: 'Име' ,hr: 'Ime' + ,dk: 'Navn' } ,'Today' : { cs: 'Dnes' @@ -250,6 +271,7 @@ function init() { ,bg: 'Днес' ,hr: 'Danas' ,sv: 'Idag' + ,dk: 'Idag' } ,'Last 2 days' : { cs: 'Poslední 2 dny' @@ -261,6 +283,7 @@ function init() { ,bg: 'Последните 2 дни' ,hr: 'Posljednja 2 dana' ,sv: 'Senaste 2 dagarna' + ,dk: 'Sidste 2 dage' } ,'Last 3 days' : { cs: 'Poslední 3 dny' @@ -272,6 +295,7 @@ function init() { ,ro: 'Ultimele 3 zile' ,bg: 'Последните 3 дни' ,hr: 'Posljednja 3 dana' + ,dk: 'Sidste 3 dage' } ,'Last week' : { cs: 'Poslední týden' @@ -283,6 +307,7 @@ function init() { ,bg: 'Последната седмица' ,hr: 'Protekli tjedan' ,sv: 'Senaste veckan' + ,dk: 'Sidste uge' } ,'Last 2 weeks' : { cs: 'Poslední 2 týdny' @@ -294,6 +319,7 @@ function init() { ,bg: 'Последните 2 седмици' ,hr: 'Protekla 2 tjedna' ,sv: 'Senaste 2 veckorna' + ,dk: 'Sidste 2 uger' } ,'Last month' : { cs: 'Poslední měsíc' @@ -305,6 +331,7 @@ function init() { ,bg: 'Последният месец' ,hr: 'Protekli mjesec' ,sv: 'Senaste månaden' + ,dk: 'Sidste måned' } ,'Last 3 months' : { cs: 'Poslední 3 měsíce' @@ -316,6 +343,7 @@ function init() { ,bg: 'Последните 3 месеца' ,hr: 'Protekla 3 mjeseca' ,sv: 'Senaste 3 månaderna' + ,dk: 'Sidste 3 måneder' } ,'From' : { cs: 'Od' @@ -327,6 +355,7 @@ function init() { ,ro: 'De la' ,bg: 'От' ,hr: 'Od' + ,dk: 'Fra' } ,'To' : { cs: 'Do' @@ -338,6 +367,7 @@ function init() { ,bg: 'До' ,hr: 'Do' ,sv: 'Till' + ,dk: 'Til' } ,'Notes' : { cs: 'Poznámky' @@ -349,6 +379,7 @@ function init() { ,ro: 'Note' ,bg: 'Бележки' ,hr: 'Bilješke' + ,dk: 'Noter' } ,'Food' : { cs: 'Jídlo' @@ -360,6 +391,7 @@ function init() { ,ro: 'Mâncare' ,bg: 'Храна' ,hr: 'Hrana' + ,dk: 'Mad' } ,'Insulin' : { cs: 'Inzulín' @@ -371,6 +403,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Inzulin' ,sv: 'Insulin' + ,dk: 'Insulin' } ,'Carbs' : { cs: 'Sacharidy' @@ -382,6 +415,7 @@ function init() { ,bg: 'Въглехидрати' ,hr: 'Ugljikohidrati' ,sv: 'Kolhydrater' + ,dk: 'Kulhydrater' } ,'Notes contain' : { cs: 'Poznámky obsahují' @@ -393,6 +427,7 @@ function init() { ,bg: 'бележките съдържат' ,hr: 'Sadržaj bilješki' ,sv: 'Notering innehåller' + ,dk: 'Noter indeholder' } ,'Event type contains' : { cs: 'Typ události obsahuje' @@ -404,6 +439,7 @@ function init() { ,bg: 'Типа събитие включва' ,hr: 'Sadržaj vrste događaja' ,sv: 'Händelsen innehåller' + ,dk: 'Hændelsen indeholder' } ,'Target bg range bottom' : { cs: 'Cílová glykémie spodní' @@ -415,6 +451,7 @@ function init() { ,bg: 'Долна граница на КЗ' ,hr: 'Ciljna donja granica GUK-a' ,sv: 'Gräns för nedre blodsockervärde' + ,dk: 'Nedre grænse for blodsukkerværdier' } ,'top' : { cs: 'horní' @@ -426,6 +463,7 @@ function init() { ,bg: 'горе' ,hr: 'Gornja' ,sv: 'Toppen' + ,dk: 'Top' } ,'Show' : { cs: 'Zobraz' @@ -436,6 +474,7 @@ function init() { ,ro: 'Arată' ,bg: 'Покажи' ,hr: 'Prikaži' + ,dk: 'Vis' } ,'Display' : { cs: 'Zobraz' @@ -447,6 +486,7 @@ function init() { ,bg: 'Покажи' ,hr: 'Prikaži' ,sv: 'Visa' + ,dk: 'Vis' } ,'Loading' : { cs: 'Nahrávám' @@ -458,6 +498,7 @@ function init() { ,bg: 'Зареждане' ,hr: 'Učitavanje' ,sv: 'Laddar' + ,dk: 'Indlæser' } ,'Loading profile' : { cs: 'Nahrávám profil' @@ -469,6 +510,7 @@ function init() { ,ro: 'Încarc profilul' ,bg: 'Зареждане на профил' ,hr: 'Učitavanje profila' + ,dk: 'Indlæser profil' } ,'Loading status' : { cs: 'Nahrávám status' @@ -480,6 +522,7 @@ function init() { ,ro: 'Încarc statusul' ,bg: 'Зареждане на статус' ,hr: 'Učitavanje statusa' + ,dk: 'Indlæsnings status' } ,'Loading food database' : { cs: 'Nahrávám databázi jídel' @@ -491,6 +534,7 @@ function init() { ,ro: 'Încarc baza de date de alimente' ,bg: 'Зареждане на данни за храни' ,hr: 'Učitavanje baze podataka o hrani' + ,dk: 'Indlæser mad database' } ,'not displayed' : { cs: 'není zobrazeno' @@ -502,6 +546,7 @@ function init() { ,bg: 'Не се показва' ,hr: 'Ne prikazuje se' ,sv: 'Visas ej' + ,dk: 'Vises ikke' } ,'Loading CGM data of' : { cs: 'Nahrávám CGM data' @@ -513,6 +558,7 @@ function init() { ,ro: 'Încarc datele CGM ale lui' ,bg: 'Зареждане на CGM данни от' ,hr: 'Učitavanja podataka CGM-a' + ,dk: 'Indlæser CGM-data for' } ,'Loading treatments data of' : { cs: 'Nahrávám data ošetření' @@ -524,6 +570,7 @@ function init() { ,ro: 'Încarc datele despre tratament pentru' ,bg: 'Зареждане на въведените лечения от' ,hr: 'Učitavanje podataka o tretmanu' + ,dk: 'Indlæser data for' } ,'Processing data of' : { cs: 'Zpracovávám data' @@ -535,6 +582,7 @@ function init() { ,ro: 'Procesez datele lui' ,bg: 'Зареждане на данни от' ,hr: 'Obrada podataka' + ,dk: 'Behandler data for' } ,'Portion' : { cs: 'Porce' @@ -546,6 +594,7 @@ function init() { ,bg: 'Порция' ,hr: 'Dio' ,sv: 'Portion' + ,dk: 'Portion' } ,'Size' : { cs: 'Rozměr' @@ -556,6 +605,7 @@ function init() { ,ro: 'Mărime' ,bg: 'Големина' ,hr: 'Veličina' + ,dk: 'Størrelse' } ,'(none)' : { cs: '(Prázdný)' @@ -566,6 +616,7 @@ function init() { ,ro: '(fără)' ,bg: 'няма' ,hr: '(Prazno)' + ,dk: '(ingen)' } ,'Result is empty' : { cs: 'Prázdný výsledek' @@ -576,6 +627,7 @@ function init() { ,ro: 'Fără rezultat' ,bg: 'Няма резултат' ,hr: 'Prazan rezultat' + ,dk: 'Tomt resultat' } // ported reporting ,'Day to day' : { @@ -588,6 +640,7 @@ function init() { ,ro: 'Zi cu zi' ,bg: 'Ден за ден' ,hr: 'Svakodnevno' + ,dk: 'Dag til dag' } ,'Daily Stats' : { cs: 'Denní statistiky' @@ -599,6 +652,7 @@ function init() { ,ro: 'Statistici zilnice' ,bg: 'Дневна статистика' ,hr: 'Dnevna statistika' + ,dk: 'Daglig statistik' } ,'Percentile Chart' : { cs: 'Percentil' @@ -610,6 +664,7 @@ function init() { ,bg: 'Процентна графика' ,hr: 'Tablica u postotcima' ,sv: 'Procentgraf' + ,dk: 'Procentgraf' } ,'Distribution' : { cs: 'Rozložení' @@ -621,6 +676,7 @@ function init() { ,bg: 'Разпределение' ,hr: 'Distribucija' ,sv: 'Distribution' + ,dk: 'Distribution' } ,'Hourly stats' : { cs: 'Statistika po hodinách' @@ -632,6 +688,7 @@ function init() { ,ro: 'Statistici orare' ,bg: 'Статистика по часове' ,hr: 'Statistika po satu' + ,dk: 'Timestatistik' } ,'Weekly success' : { cs: 'Statistika po týdnech' @@ -643,6 +700,7 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,dk: 'Uge resultat' } ,'No data available' : { cs: 'Žádná dostupná data' @@ -654,6 +712,7 @@ function init() { ,bg: 'Няма данни за показване' ,hr: 'Nema raspoloživih podataka' ,sv: 'Data saknas' + ,dk: 'Mangler data' } ,'Low' : { cs: 'Nízká' @@ -665,6 +724,7 @@ function init() { ,ro: 'Prea jos' ,bg: 'Ниска' ,hr: 'Nizak' + ,dk: 'Lav' } ,'In Range' : { cs: 'V rozsahu' @@ -676,6 +736,7 @@ function init() { ,ro: 'În interval' ,bg: 'В граници' ,hr: 'U rasponu' + ,dk: 'Indenfor intervallet' } ,'Period' : { cs: 'Období' @@ -687,6 +748,7 @@ function init() { ,ro: 'Perioada' ,bg: 'Период' ,hr: 'Period' + ,dk: 'Period' } ,'High' : { cs: 'Vysoká' @@ -698,6 +760,7 @@ function init() { ,ro: 'Prea sus' ,bg: 'Висока' ,hr: 'Visok' + ,dk: 'Høj' } ,'Average' : { cs: 'Průměrná' @@ -709,6 +772,7 @@ function init() { ,ro: 'Media' ,bg: 'Средна' ,hr: 'Prosjek' + ,dk: 'Gennemsnit' } ,'Low Quartile' : { cs: 'Nízký kvartil' @@ -720,6 +784,7 @@ function init() { ,bg: 'Ниска четвъртинка' ,hr: 'Donji kvartil' ,sv: 'Nedre kvadranten' + ,dk: 'Nedre kvartil' } ,'Upper Quartile' : { cs: 'Vysoký kvartil' @@ -731,6 +796,7 @@ function init() { ,bg: 'Висока четвъртинка' ,hr: 'Gornji kvartil' ,sv: 'Övre kvadranten' + ,dk: 'Øvre kvartil' } ,'Quartile' : { cs: 'Kvartil' @@ -741,6 +807,7 @@ function init() { ,ro: 'Pătrime' ,bg: 'Четвъртинка' ,hr: 'Kvartil' + ,dk: 'Kvartil' } ,'Date' : { cs: 'Datum' @@ -752,6 +819,7 @@ function init() { ,ro: 'Data' ,bg: 'Дата' ,hr: 'Datum' + ,dk: 'Dato' } ,'Normal' : { cs: 'Normální' @@ -763,6 +831,7 @@ function init() { ,ro: 'Normal' ,bg: 'Нормално' ,hr: 'Normalno' + ,dk: 'Normal' } ,'Median' : { cs: 'Medián' @@ -774,6 +843,7 @@ function init() { ,bg: 'Средно' ,hr: 'Srednje' ,sv: 'Median' + ,dk: 'Median' } ,'Readings' : { cs: 'Záznamů' @@ -785,6 +855,7 @@ function init() { ,ro: 'Valori' ,bg: 'Измервания' ,hr: 'Vrijednosti' + ,dk: 'Aflæsning' } ,'StDev' : { cs: 'St. odchylka' @@ -796,6 +867,7 @@ function init() { ,ro: 'Dev Std' ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' + ,dk: 'Standard afvigelse' } ,'Daily stats report' : { cs: 'Denní statistiky' @@ -807,6 +879,7 @@ function init() { ,bg: 'Дневна статистика' ,hr: 'Izvješće o dnevnim statistikama' ,sv: 'Dygnsstatistik' + ,dk: 'Daglig statistik rapport' } ,'Glucose Percentile report' : { cs: 'Tabulka percentil glykémií' @@ -818,6 +891,7 @@ function init() { ,ro: 'Raport percentile glicemii' ,bg: 'Графика на КЗ' ,hr: 'Izvješće o postotku GUK-a' + ,dk: 'Glukoserapport i procent' } ,'Glucose distribution' : { cs: 'Rozložení glykémií' @@ -829,6 +903,7 @@ function init() { ,bg: 'Разпределение на КЗ' ,hr: 'Distribucija GUK-a' ,sv: 'Glukosdistribution' + ,dk: 'Glukosefordeling' } ,'days total' : { cs: 'dní celkem' @@ -840,6 +915,7 @@ function init() { ,ro: 'total zile' ,bg: 'общо за деня' ,hr: 'ukupno dana' + ,dk: 'antal dage' } ,'Overall' : { cs: 'Celkem' @@ -851,6 +927,7 @@ function init() { ,ro: 'General' ,bg: 'Общо' ,hr: 'Ukupno' + ,dk: 'Overall' } ,'Range' : { cs: 'Rozsah' @@ -862,6 +939,7 @@ function init() { ,ro: 'Interval' ,bg: 'Диапазон' ,hr: 'Raspon' + ,dk: 'Interval' } ,'% of Readings' : { cs: '% záznamů' @@ -873,6 +951,7 @@ function init() { ,ro: '% de valori' ,bg: '% от измервания' ,hr: '% očitanja' + ,dk: '% af aflæsningerne' } ,'# of Readings' : { cs: 'počet záznamů' @@ -884,6 +963,7 @@ function init() { ,ro: 'nr. de valori' ,bg: '№ от измервания' ,hr: 'broj očitanja' + ,dk: 'Antal af aflæsninger' } ,'Mean' : { cs: 'Střední hodnota' @@ -895,6 +975,7 @@ function init() { ,ro: 'Medie' ,bg: 'Средна стойност' ,hr: 'Prosjek' + ,dk: 'Gennemsnit' } ,'Standard Deviation' : { cs: 'Standardní odchylka' @@ -906,6 +987,7 @@ function init() { ,bg: 'Стандартно отклонение' ,hr: 'Standardna devijacija' ,sv: 'Standardavvikelse' + ,dk: 'Standardafgivelse' } ,'Max' : { cs: 'Max' @@ -917,6 +999,7 @@ function init() { ,ro: 'Max' ,bg: 'Максимално' ,hr: 'Max' + ,dk: 'Max' } ,'Min' : { cs: 'Min' @@ -928,6 +1011,7 @@ function init() { ,ro: 'Min' ,bg: 'Минимално' ,hr: 'Min' + ,dk: 'Min' } ,'A1c estimation*' : { cs: 'Předpokládané HBA1c*' @@ -939,6 +1023,7 @@ function init() { ,bg: 'Очакван HbA1c' ,hr: 'Procjena HbA1c-a' ,sv: 'Beräknat A1c-värde ' + ,dk: 'Beregnet A1c-værdi ' } ,'Weekly Success' : { cs: 'Týdenní úspěšnost' @@ -950,6 +1035,7 @@ function init() { ,bg: 'Седмичен успех' ,hr: 'Tjedni uspjeh' ,sv: 'Veckoresultat' + ,dk: 'Uge resultat' } ,'There is not sufficient data to run this report. Select more days.' : { cs: 'Není dostatek dat. Vyberte delší časové období.' @@ -961,6 +1047,7 @@ function init() { ,bg: 'Няма достатъчно данни за показване. Изберете повече дни.' ,hr: 'Nema dovoljno podataka za izvođenje izvještaja. Odaberite još dana.' ,sv: 'Data saknas för att köra rapport. Välj fler dagar.' + ,dk: 'Der er utilstrækkeligt data til at generere rapporten. Vælg flere dage.' } // food editor ,'Using stored API secret hash' : { @@ -973,6 +1060,7 @@ function init() { ,bg: 'Използване на запаметена API парола' ,hr: 'Koristi se pohranjeni API tajni hash' ,sv: 'Använd hemlig API-nyckel' + ,dk: 'Anvender gemt API-nøgle' } ,'No API secret hash stored yet. You need to enter API secret.' : { cs: 'Není uložený žádný hash API hesla. Musíte zadat API heslo.' @@ -984,6 +1072,7 @@ function init() { ,bg: 'Няма запаметена API парола. Tрябва да въведете API парола' ,hr: 'Nema pohranjenog API tajnog hasha. Unesite tajni API' ,sv: 'Hemlig api-nyckel saknas. Du måste ange API hemlighet' + ,dk: 'Mangler API-nøgle. Du skal indtaste API hemmelighed' } ,'Database loaded' : { cs: 'Databáze načtena' @@ -995,6 +1084,7 @@ function init() { ,bg: 'База с данни заредена' ,hr: 'Baza podataka je učitana' ,sv: 'Databas laddad' + ,dk: 'Database indlæst' } ,'Error: Database failed to load' : { cs: 'Chyba při načítání databáze' @@ -1006,6 +1096,7 @@ function init() { ,bg: 'ГРЕШКА. Базата с данни не успя да се зареди' ,hr: 'Greška: Baza podataka nije učitana' ,sv: 'Error: Databas kan ej laddas' + ,dk: 'Fejl: Database kan ikke indlæses' } ,'Create new record' : { cs: 'Vytvořit nový záznam' @@ -1017,6 +1108,7 @@ function init() { ,bg: 'Създаване на нов запис' ,hr: 'Kreiraj novi zapis' ,sv: 'Skapa ny post' + ,dk: 'Danner ny post' } ,'Save record' : { cs: 'Uložit záznam' @@ -1028,6 +1120,7 @@ function init() { ,bg: 'Запази запис' ,hr: 'Spremi zapis' ,sv: 'Spara post' + ,dk: 'Gemmer post' } ,'Portions' : { cs: 'Porcí' @@ -1039,6 +1132,7 @@ function init() { ,bg: 'Порции' ,hr: 'Dijelovi' ,sv: 'Portion' + ,dk: 'Portioner' } ,'Unit' : { cs: 'Jedn' @@ -1050,6 +1144,7 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinica' ,sv: 'Enhet' + ,dk: 'Enheder' } ,'GI' : { cs: 'GI' @@ -1061,6 +1156,7 @@ function init() { ,ro: 'CI' ,bg: 'ГИ' ,hr: 'GI' + ,dk: 'GI' } ,'Edit record' : { cs: 'Upravit záznam' @@ -1072,6 +1168,7 @@ function init() { ,bg: 'Редактирай запис' ,hr: 'Uredi zapis' ,sv: 'Editera post' + ,dk: 'Editere post' } ,'Delete record' : { cs: 'Smazat záznam' @@ -1083,6 +1180,7 @@ function init() { ,bg: 'Изтрий запис' ,hr: 'Izbriši zapis' ,sv: 'Ta bort post' + ,dk: 'Slet post' } ,'Move to the top' : { cs: 'Přesuň na začátek' @@ -1094,6 +1192,7 @@ function init() { ,ro: 'Mergi la început' ,bg: 'Преместване в началото' ,hr: 'Premjesti na vrh' + ,dk: 'Gå til toppen' } ,'Hidden' : { cs: 'Skrytý' @@ -1105,6 +1204,7 @@ function init() { ,ro: 'Ascuns' ,bg: 'Скрити' ,hr: 'Skriveno' + ,dk: 'Skjult' } ,'Hide after use' : { cs: 'Skryj po použití' @@ -1116,6 +1216,7 @@ function init() { ,bg: 'Скрий след употреба' ,hr: 'Sakrij nakon korištenja' ,sv: 'Dölj efter användning' + ,dk: 'Skjul efter brug' } ,'Your API secret must be at least 12 characters long' : { cs: 'Vaše API heslo musí mít alespoň 12 znaků' @@ -1127,6 +1228,7 @@ function init() { ,bg: 'Вашата АPI парола трябва да е дълга поне 12 символа' ,hr: 'Vaš tajni API mora sadržavati barem 12 znakova' ,sv: 'Hemlig API-nyckel måsta innehålla 12 tecken' + ,dk: 'Din API nøgle skal være mindst 12 tegn lang' } ,'Bad API secret' : { cs: 'Chybné API heslo' @@ -1138,6 +1240,7 @@ function init() { ,bg: 'Некоректна API парола' ,hr: 'Neispravan tajni API' ,sv: 'Felaktig API-nyckel' + ,dk: 'Forkert API-nøgle' } ,'API secret hash stored' : { cs: 'Hash API hesla uložen' @@ -1149,6 +1252,7 @@ function init() { ,bg: 'УРА! API парола запаметена' ,hr: 'API tajni hash je pohranjen' ,sv: 'Hemlig API-hash lagrad' + ,dk: 'Hemmelig API-hash gemt' } ,'Status' : { cs: 'Status' @@ -1160,6 +1264,7 @@ function init() { ,ro: 'Status' ,bg: 'Статус' ,hr: 'Status' + ,dk: 'Status' } ,'Not loaded' : { cs: 'Nenačtený' @@ -1171,6 +1276,7 @@ function init() { ,bg: 'Не е заредено' ,hr: 'Nije učitano' ,sv: 'Ej laddad' + ,dk: 'Ikke indlæst' } ,'Food editor' : { cs: 'Editor jídel' @@ -1182,6 +1288,7 @@ function init() { ,bg: 'Редактор за храна' ,hr: 'Editor hrane' ,sv: 'Födoämneseditor' + ,dk: 'Mad editor' } ,'Your database' : { cs: 'Vaše databáze' @@ -1193,6 +1300,7 @@ function init() { ,ro: 'Baza de date' ,bg: 'Твоята база с данни' ,hr: 'Vaša baza podataka' + ,dk: 'Din database' } ,'Filter' : { cs: 'Filtr' @@ -1204,6 +1312,7 @@ function init() { ,ro: 'Filtru' ,bg: 'Филтър' ,hr: 'Filter' + ,dk: 'Filter' } ,'Save' : { cs: 'Ulož' @@ -1215,6 +1324,7 @@ function init() { ,bg: 'Запази' ,hr: 'Spremi' ,sv: 'Spara' + ,dk: 'Gem' } ,'Clear' : { cs: 'Vymaž' @@ -1226,6 +1336,7 @@ function init() { ,bg: 'Изчисти' ,hr: 'Očisti' ,sv: 'Rensa' + ,dk: 'Rense' } ,'Record' : { cs: 'Záznam' @@ -1237,6 +1348,7 @@ function init() { ,ro: 'Înregistrare' ,bg: 'Запиши' ,hr: 'Zapis' + ,dk: 'Post' } ,'Quick picks' : { cs: 'Rychlý výběr' @@ -1248,6 +1360,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,dk: 'Hurtig valg' } ,'Show hidden' : { cs: 'Zobraz skryté' @@ -1259,6 +1372,7 @@ function init() { ,bg: 'Покажи скритото' ,hr: 'Prikaži skriveno' ,sv: 'Visa dolda' + ,dk: 'Vis skjulte' } ,'Your API secret' : { cs: 'Vaše API heslo' @@ -1270,6 +1384,7 @@ function init() { ,ro: 'Cheia API' ,bg: 'Твоята API парола' ,hr: 'Vaš tajni API' + ,dk: 'Din API-nøgle' } ,'Store hash on this computer (Use only on private computers)' : { cs: 'Ulož hash na tomto počítači (používejte pouze na soukromých počítačích)' @@ -1281,6 +1396,7 @@ function init() { ,bg: 'Запамети данните на този компютър. ( Използвай само на собствен компютър)' ,hr: 'Pohrani hash na ovom računalu (Koristiti samo na osobnom računalu)' ,sv: 'Lagra hashvärde på denna dator (använd endast på privat dator)' + ,dk: 'Gemme hash på denne computer (brug kun på privat computer)' } ,'Treatments' : { cs: 'Ošetření' @@ -1292,6 +1408,7 @@ function init() { ,ro: 'Tratamente' ,bg: 'Събития' ,hr: 'Tretmani' + ,dk: 'Behandling' } ,'Time' : { cs: 'Čas' @@ -1303,6 +1420,7 @@ function init() { ,ro: 'Ora' ,bg: 'Време' ,hr: 'Vrijeme' + ,dk: 'Tid' } ,'Event Type' : { cs: 'Typ události' @@ -1314,6 +1432,7 @@ function init() { ,ro: 'Tip eveniment' ,bg: 'Вид събитие' ,hr: 'Vrsta događaja' + ,dk: 'Hændelsestype' } ,'Blood Glucose' : { cs: 'Glykémie' @@ -1325,6 +1444,7 @@ function init() { ,ro: 'Glicemie' ,bg: 'Кръвна захар' ,hr: 'GUK' + ,dk: 'Glukoseværdi' } ,'Entered By' : { cs: 'Zadal' @@ -1336,6 +1456,7 @@ function init() { ,ro: 'Introdus de' ,bg: 'Въведено от' ,hr: 'Unos izvršio' + ,dk: 'Indtastet af' } ,'Delete this treatment?' : { cs: 'Vymazat toto ošetření?' @@ -1347,6 +1468,7 @@ function init() { ,bg: 'Изтрий това събитие' ,hr: 'Izbriši ovaj tretman?' ,sv: 'Ta bort händelse?' + ,dk: 'Slet denne hændelse?' } ,'Carbs Given' : { cs: 'Sacharidů' @@ -1358,6 +1480,7 @@ function init() { ,bg: 'ВХ' ,hr: 'Količina UH' ,sv: 'Antal kolhydrater' + ,dk: 'Antal kulhydrater' } ,'Inzulin Given' : { cs: 'Inzulínu' @@ -1369,6 +1492,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina inzulina' ,sv: 'Insulin' + ,dk: 'Insulin' } ,'Event Time' : { cs: 'Čas události' @@ -1380,6 +1504,7 @@ function init() { ,ro: 'Ora evenimentului' ,bg: 'Въвеждане' ,hr: 'Vrijeme događaja' + ,dk: 'Tidspunkt for hændelsen' } ,'Please verify that the data entered is correct' : { cs: 'Prosím zkontrolujte, zda jsou údaje zadány správně' @@ -1391,6 +1516,7 @@ function init() { ,bg: 'Моля проверете, че датата е въведена правилно' ,hr: 'Molim Vas provjerite jesu li uneseni podaci ispravni' ,sv: 'Vänligen verifiera att inlagd data stämmer' + ,dk: 'Venligst verificer at indtastet data er korrekt' } ,'BG' : { cs: 'Glykémie' @@ -1402,6 +1528,7 @@ function init() { ,ro: 'Glicemie' ,bg: 'КЗ' ,hr: 'GUK' + ,dk: 'BS' } ,'Use BG correction in calculation' : { cs: 'Použij korekci na glykémii' @@ -1413,6 +1540,7 @@ function init() { ,bg: 'Въведи корекция за КЗ ' ,hr: 'Koristi korekciju GUK-a u izračunu' ,sv: 'Använd BS-korrektion för uträkning' + ,dk: 'Anvend BS-korrektion før beregning' } ,'BG from CGM (autoupdated)' : { cs: 'Glykémie z CGM (automaticky aktualizovaná)' @@ -1424,6 +1552,7 @@ function init() { ,ro: 'Glicemie în senzor (automat)' ,bg: 'КЗ от сензора (автоматично)' ,hr: 'GUK sa CGM-a (ažuriran automatski)' + ,dk: 'BS fra CGM (automatisk)' } ,'BG from meter' : { cs: 'Glykémie z glukoměru' @@ -1435,6 +1564,7 @@ function init() { ,ro: 'Glicemie în glucometru' ,bg: 'КЗ от глюкомер' ,hr: 'GUK s glukometra' + ,dk: 'BS fra blodsukkerapperat' } ,'Manual BG' : { cs: 'Ručně zadaná glykémie' @@ -1446,6 +1576,7 @@ function init() { ,bg: 'Ръчно въведена КЗ' ,hr: 'Ručno unesen GUK' ,sv: 'Manuellt BS' + ,dk: 'Manuelt BS' } ,'Quickpick' : { cs: 'Rychlý výběr' @@ -1457,6 +1588,7 @@ function init() { ,bg: 'Бърз избор' ,hr: 'Brzi izbor' ,sv: 'Snabbval' + ,dk: 'Hurtig snack' } ,'or' : { cs: 'nebo' @@ -1468,6 +1600,7 @@ function init() { ,ro: 'sau' ,bg: 'или' ,hr: 'ili' + ,dk: 'eller' } ,'Add from database' : { cs: 'Přidat z databáze' @@ -1479,6 +1612,7 @@ function init() { ,bg: 'Добави към базата с данни' ,hr: 'Dodaj iz baze podataka' ,sv: 'Lägg till från databas' + ,dk: 'Tilføj fra database' } ,'Use carbs correction in calculation' : { cs: 'Použij korekci na sacharidy' @@ -1490,6 +1624,7 @@ function init() { ,bg: 'Въведи корекция за въглехидратите' ,hr: 'Koristi korekciju za UH u izračunu' ,sv: 'Använd kolhydratkorrektion i utäkning' + ,dk: 'Benyt kulhydratkorrektion i beregning' } ,'Use COB correction in calculation' : { cs: 'Použij korekci na COB' @@ -1501,6 +1636,7 @@ function init() { ,bg: 'Въведи корекция за останалите въглехидрати' ,hr: 'Koristi aktivne UH u izračunu' ,sv: 'Använd aktiva kolhydrater för beräkning' + ,dk: 'Benyt aktive kulhydrater i beregning' } ,'Use IOB in calculation' : { cs: 'Použij IOB ve výpočtu' @@ -1512,6 +1648,7 @@ function init() { ,bg: 'Използвай активния инсулин' ,hr: 'Koristi aktivni inzulin u izračunu"' ,sv: 'Använd aktivt insulin för uträkning' + ,dk: 'Benyt aktivt insulin i beregningen' } ,'Other correction' : { cs: 'Jiná korekce' @@ -1523,6 +1660,7 @@ function init() { ,bg: 'Друга корекция' ,hr: 'Druga korekcija' ,sv: 'Övrig korrektion' + ,dk: 'Øvrig korrektion' } ,'Rounding' : { cs: 'Zaokrouhlení' @@ -1534,6 +1672,7 @@ function init() { ,ro: 'Rotunjire' ,bg: 'Закръгляне' ,hr: 'Zaokruživanje' + ,dk: 'Afrunding' } ,'Enter insulin correction in treatment' : { cs: 'Zahrň inzulín do záznamu ošetření' @@ -1545,6 +1684,7 @@ function init() { ,bg: 'Въведи корекция с инсулин като лечение' ,hr: 'Unesi korekciju inzulinom u tretman' ,sv: 'Ange insulinkorrektion för händelse' + ,dk: 'Indtast insulionkorrektion' } ,'Insulin needed' : { cs: 'Potřebný inzulín' @@ -1556,6 +1696,7 @@ function init() { ,bg: 'Необходим инсулин' ,hr: 'Potrebno inzulina' ,sv: 'Beräknad insulinmängd' + ,dk: 'Insulin påkrævet' } ,'Carbs needed' : { cs: 'Potřebné sach' @@ -1567,6 +1708,7 @@ function init() { ,bg: 'Необходими въглехидрати' ,hr: 'Potrebno UH' ,sv: 'Beräknad kolhydratmängd' + ,dk: 'Kulhydrater påkrævet' } ,'Carbs needed if Insulin total is negative value' : { cs: 'Chybějící sacharidy v případě, že výsledek je záporný' @@ -1578,6 +1720,7 @@ function init() { ,bg: 'Необходими въглехидрати, ако няма инсулин' ,hr: 'Potrebno UH ako je ukupna vrijednost inzulina negativna' ,sv: 'Nödvändig kolhydratmängd för angiven insulinmängd' + ,dk: 'Kulhydrater er nødvendige når total insulin mængde er negativ' } ,'Basal rate' : { cs: 'Bazál' @@ -1589,6 +1732,7 @@ function init() { ,bg: 'Базален инсулин' ,hr: 'Bazal' ,sv: 'Basaldos' + ,dk: 'Basal rate' } ,'60 minutes earlier' : { cs: '60 min předem' @@ -1600,6 +1744,7 @@ function init() { ,ro: 'acum 60 min' ,bg: 'Преди 60 минути' ,hr: 'Prije 60 minuta' + ,dk: '60 min tidligere' } ,'45 minutes earlier' : { cs: '45 min předem' @@ -1611,6 +1756,7 @@ function init() { ,ro: 'acum 45 min' ,bg: 'Преди 45 минути' ,hr: 'Prije 45 minuta' + ,dk: '45 min tidligere' } ,'30 minutes earlier' : { cs: '30 min předem' @@ -1622,6 +1768,7 @@ function init() { ,ro: 'acum 30 min' ,bg: 'Преди 30 минути' ,hr: 'Prije 30 minuta' + ,dk: '30 min tidigere' } ,'20 minutes earlier' : { cs: '20 min předem' @@ -1633,6 +1780,7 @@ function init() { ,ro: 'acum 20 min' ,bg: 'Преди 20 минути' ,hr: 'Prije 20 minuta' + ,dk: '20 min tidligere' } ,'15 minutes earlier' : { cs: '15 min předem' @@ -1644,6 +1792,7 @@ function init() { ,ro: 'acu 15 min' ,bg: 'Преди 15 минути' ,hr: 'Prije 15 minuta' + ,dk: '15 min tidligere' } ,'Time in minutes' : { cs: 'Čas v minutách' @@ -1655,6 +1804,7 @@ function init() { ,ro: 'Timp în minute' ,bg: 'Времето в минути' ,hr: 'Vrijeme u minutama' + ,dk: 'Tid i minutter' } ,'15 minutes later' : { cs: '15 min po' @@ -1665,6 +1815,7 @@ function init() { ,bg: 'След 15 минути' ,hr: '15 minuta kasnije' ,sv: '15 min senare' + ,dk: '15 min senere' } ,'20 minutes later' : { cs: '20 min po' @@ -1676,6 +1827,7 @@ function init() { ,bg: 'След 20 минути' ,hr: '20 minuta kasnije' ,sv: '20 min senare' + ,dk: '20 min senere' } ,'30 minutes later' : { cs: '30 min po' @@ -1687,6 +1839,7 @@ function init() { ,bg: 'След 30 минути' ,hr: '30 minuta kasnije' ,sv: '30 min senare' + ,dk: '30 min senere' } ,'45 minutes later' : { cs: '45 min po' @@ -1698,6 +1851,7 @@ function init() { ,bg: 'След 45 минути' ,hr: '45 minuta kasnije' ,sv: '45 min senare' + ,dk: '45 min senere' } ,'60 minutes later' : { cs: '60 min po' @@ -1709,6 +1863,7 @@ function init() { ,bg: 'След 60 минути' ,hr: '60 minuta kasnije' ,sv: '60 min senare' + ,dk: '60 min senere' } ,'Additional Notes, Comments' : { cs: 'Dalši poznámky, komentáře' @@ -1720,6 +1875,7 @@ function init() { ,bg: 'Допълнителни бележки, коментари' ,hr: 'Dodatne bilješke, komentari' ,sv: 'Notering, övrigt' + ,dk: 'Ekstra noter, kommentarer' } ,'RETRO MODE' : { cs: 'V MINULOSTI' @@ -1731,6 +1887,7 @@ function init() { ,ro: 'MOD RETROSPECTIV' ,bg: 'МИНАЛО ВРЕМЕ' ,hr: 'Retrospektivni način' + ,dk: 'Retro mode' } ,'Now' : { cs: 'Nyní' @@ -1742,6 +1899,7 @@ function init() { ,ro: 'Acum' ,bg: 'Сега' ,hr: 'Sad' + ,dk: 'Nu' } ,'Other' : { cs: 'Jiný' @@ -1753,6 +1911,7 @@ function init() { ,ro: 'Altul' ,bg: 'Друго' ,hr: 'Drugo' + ,dk: 'Øvrige' } ,'Submit Form' : { cs: 'Odeslat formulář' @@ -1764,6 +1923,7 @@ function init() { ,ro: 'Trimite formularul' ,bg: 'Въвеждане на данните' ,hr: 'Predaj obrazac' + ,dk: 'Gem hændelsen' } ,'Profile editor' : { cs: 'Editor profilu' @@ -1775,6 +1935,7 @@ function init() { ,ro: 'Editare profil' ,bg: 'Редактор на профила' ,hr: 'Editor profila' + ,dk: 'Profil editor' } ,'Reporting tool' : { cs: 'Výkazy' @@ -1786,6 +1947,7 @@ function init() { ,ro: 'Instrument de rapoarte' ,bg: 'Статистика' ,hr: 'Alat za prijavu' + ,dk: 'Rapporteringsværktøj' } ,'Add food from your database' : { cs: 'Přidat jidlo z Vaší databáze' @@ -1797,6 +1959,7 @@ function init() { ,bg: 'Добави храна от твоята база с данни' ,hr: 'Dodajte hranu iz svoje baze podataka' ,sv: 'Lägg till livsmedel från databas' + ,dk: 'Tilføj mad fra din database' } ,'Reload database' : { cs: 'Znovu nahraj databázi' @@ -1808,6 +1971,7 @@ function init() { ,bg: 'Презареди базата с данни' ,hr: 'Ponovo učitajte bazu podataka' ,sv: 'Ladda om databas' + ,dk: 'Genindlæs databasen' } ,'Add' : { cs: 'Přidej' @@ -1819,6 +1983,7 @@ function init() { ,bg: 'Добави' ,hr: 'Dodaj' ,sv: 'Lägg till' + ,dk: 'Tilføj' } ,'Unauthorized' : { cs: 'Neautorizováno' @@ -1830,6 +1995,7 @@ function init() { ,ro: 'Neautorizat' ,bg: 'Нямаш достъп' ,hr: 'Neautorizirano' + ,dk: 'Uautoriseret' } ,'Entering record failed' : { cs: 'Vložení záznamu selhalo' @@ -1841,6 +2007,7 @@ function init() { ,bg: 'Въвеждане на записа не се осъществи' ,hr: 'Neuspjeli unos podataka' ,sv: 'Lägga till post nekas' + ,dk: 'Tilføjelse af post fejlede' } ,'Device authenticated' : { cs: 'Zařízení ověřeno' @@ -1852,6 +2019,7 @@ function init() { ,ro: 'Dispozitiv autentificat' ,bg: 'Устройстово е разпознато' ,hr: 'Uređaj autenticiran' + ,dk: 'Enhed godkendt' } ,'Device not authenticated' : { cs: 'Zařízení není ověřeno' @@ -1863,6 +2031,7 @@ function init() { ,ro: 'Dispozitiv neautentificat' ,bg: 'Устройсройството не е разпознато' ,hr: 'Uređaj nije autenticiran' + ,dk: 'Enhed ikke godkendt' } ,'Authentication status' : { cs: 'Stav ověření' @@ -1874,6 +2043,7 @@ function init() { ,bg: 'Статус на удостоверяване' ,hr: 'Status autentikacije' ,sv: 'Autentiseringsstatus' + ,dk: 'Autentifikationsstatus' } ,'Authenticate' : { cs: 'Ověřit' @@ -1885,6 +2055,7 @@ function init() { ,ro: 'Autentificare' ,bg: 'Удостоверяване' ,hr: 'Autenticirati' + ,dk: 'Godkende' } ,'Remove' : { cs: 'Vymazat' @@ -1896,6 +2067,7 @@ function init() { ,bg: 'Премахни' ,hr: 'Ukloniti' ,sv: 'Ta bort' + ,dk: 'Fjern' } ,'Your device is not authenticated yet' : { cs: 'Toto zařízení nebylo dosud ověřeno' @@ -1907,6 +2079,7 @@ function init() { ,bg: 'Вашето устройство все още не е удостоверено' ,hr: 'Vaš uređaj još nije autenticiran' ,sv: 'Din enhet är ej autentiserad' + ,dk: 'Din enhed er ikke godkendt endnu' } ,'Sensor' : { cs: 'Senzor' @@ -1918,6 +2091,7 @@ function init() { ,ro: 'Senzor' ,bg: 'Сензор' ,hr: 'Senzor' + ,dk: 'Sensor' } ,'Finger' : { cs: 'Glukoměr' @@ -1929,6 +2103,7 @@ function init() { ,ro: 'Deget' ,bg: 'От пръстта' ,hr: 'Prst' + ,dk: 'Finger' } ,'Manual' : { cs: 'Ručně' @@ -1940,6 +2115,7 @@ function init() { ,ro: 'Manual' ,bg: 'Ръчно' ,hr: 'Ručno' + ,dk: 'Manuel' } ,'Scale' : { cs: 'Měřítko' @@ -1951,6 +2127,7 @@ function init() { ,bg: 'Скала' ,hr: 'Skala' ,sv: 'Skala' + ,dk: 'Skala' } ,'Linear' : { cs: 'lineární' @@ -1962,6 +2139,7 @@ function init() { ,ro: 'Liniar' ,bg: 'Линеен' ,hr: 'Linearno' + ,dk: 'Lineær' } ,'Logarithmic' : { cs: 'logaritmické' @@ -1973,6 +2151,7 @@ function init() { ,ro: 'Logaritmic' ,bg: 'Логоритмичен' ,hr: 'Logaritamski' + ,dk: 'Logaritmisk' } ,'Silence for 30 minutes' : { cs: 'Ztlumit na 30 minut' @@ -1984,6 +2163,7 @@ function init() { ,bg: 'Заглуши за 30 минути' ,hr: 'Tišina 30 minuta' ,sv: 'Tyst i 30 min' + ,dk: 'Stilhed i 30 min' } ,'Silence for 60 minutes' : { cs: 'Ztlumit na 60 minut' @@ -1995,6 +2175,7 @@ function init() { ,bg: 'Заглуши за 60 минути' ,hr: 'Tišina 60 minuta' ,sv: 'Tyst i 60 min' + ,dk: 'Stilhed i 60 min' } ,'Silence for 90 minutes' : { cs: 'Ztlumit na 90 minut' @@ -2006,6 +2187,7 @@ function init() { ,bg: 'Заглуши за 90 минути' ,hr: 'Tišina 90 minuta' ,sv: 'Tyst i 90 min' + ,dk: 'Stilhed i 90 min' } ,'Silence for 120 minutes' : { cs: 'Ztlumit na 120 minut' @@ -2017,6 +2199,7 @@ function init() { ,bg: 'Заглуши за 120 минути' ,hr: 'Tišina 120 minuta' ,sv: 'Tyst i 120 min' + ,dk: 'Stilhed i 120 min' } ,'3HR' : { cs: '3hod' @@ -2028,6 +2211,7 @@ function init() { ,ro: '3h' ,bg: '3часа' ,hr: '3h' + ,dk: '3tim' } ,'6HR' : { cs: '6hod' @@ -2039,6 +2223,7 @@ function init() { ,ro: '6h' ,bg: '6часа' ,hr: '6h' + ,dk: '6tim' } ,'12HR' : { cs: '12hod' @@ -2050,6 +2235,7 @@ function init() { ,ro: '12h' ,bg: '12часа' ,hr: '12h' + ,dk: '12tim' } ,'24HR' : { cs: '24hod' @@ -2061,6 +2247,7 @@ function init() { ,ro: '24h' ,bg: '24часа' ,hr: '24h' + ,dk: '24tim' } ,'Settings' : { cs: 'Nastavení' @@ -2071,6 +2258,7 @@ function init() { ,ro: 'Setări' ,bg: 'Настройки' ,hr: 'Postavke' + ,dk: 'Opsætning' } ,'Units' : { cs: 'Jednotky' @@ -2082,6 +2270,7 @@ function init() { ,bg: 'Единици' ,hr: 'Jedinice' ,sv: 'Enheter' + ,dk: 'Enheder' } ,'Date format' : { cs: 'Formát datumu' @@ -2093,6 +2282,7 @@ function init() { ,ro: 'Formatul datei' ,bg: 'Формат на датата' ,hr: 'Format datuma' + ,dk: 'dato format' } ,'12 hours' : { cs: '12 hodin' @@ -2104,6 +2294,7 @@ function init() { ,ro: '12 ore' ,bg: '12 часа' ,hr: '12 sati' + ,dk: '12 timer' } ,'24 hours' : { cs: '24 hodin' @@ -2115,6 +2306,7 @@ function init() { ,ro: '24 ore' ,bg: '24 часа' ,hr: '24 sata' + ,dk: '24 timer' } ,'Log a Treatment' : { cs: 'Záznam ošetření' @@ -2126,6 +2318,7 @@ function init() { ,bg: 'Въвеждане на събитие' ,hr: 'Evidencija tretmana' ,sv: 'Ange händelse' + ,dk: 'Log en hændelse' } ,'BG Check' : { cs: 'Kontrola glykémie' @@ -2137,6 +2330,7 @@ function init() { ,ro: 'Verificare glicemie' ,bg: 'Проверка на КЗ' ,hr: 'Kontrola GUK-a' + ,dk: 'BS kontrol' } ,'Meal Bolus' : { cs: 'Bolus na jídlo' @@ -2148,6 +2342,7 @@ function init() { ,bg: 'Болус-основно хранене' ,hr: 'Bolus za obrok' ,sv: 'Måltidsbolus' + ,dk: 'Måltidsbolus' } ,'Snack Bolus' : { cs: 'Bolus na svačinu' @@ -2159,6 +2354,7 @@ function init() { ,ro: 'Bolus gustare' ,bg: 'Болус-лека закуска' ,hr: 'Bolus za užinu' + ,dk: 'Mellemmåltidsbolus' } ,'Correction Bolus' : { cs: 'Bolus na glykémii' @@ -2170,6 +2366,7 @@ function init() { ,bg: 'Болус корекция' ,hr: 'Korekcija' ,sv: 'Korrektionsbolus' + ,dk: 'Korrektionsbolus' } ,'Carb Correction' : { cs: 'Přídavek sacharidů' @@ -2181,6 +2378,7 @@ function init() { ,bg: 'Корекция за въглехидратите' ,hr: 'Bolus za hranu' ,sv: 'Kolhydratskorrektion' + ,dk: 'Kulhydratskorrektion' } ,'Note' : { cs: 'Poznámka' @@ -2192,6 +2390,7 @@ function init() { ,bg: 'Бележка' ,hr: 'Bilješka' ,sv: 'Notering' + ,dk: 'Note' } ,'Question' : { cs: 'Otázka' @@ -2203,6 +2402,7 @@ function init() { ,ro: 'Întrebare' ,bg: 'Въпрос' ,hr: 'Pitanje' + ,dk: 'Spørgsmål' } ,'Exercise' : { cs: 'Cvičení' @@ -2214,6 +2414,7 @@ function init() { ,bg: 'Спорт' ,hr: 'Aktivnost' ,sv: 'Aktivitet' + ,dk: 'Træning' } ,'Pump Site Change' : { cs: 'Přepíchnutí kanyly' @@ -2225,6 +2426,7 @@ function init() { ,bg: 'Смяна на сет' ,hr: 'Promjena seta' ,sv: 'Pump/nålbyte' + ,dk: 'Skift insulin infusionssted' } ,'Sensor Start' : { cs: 'Spuštění sensoru' @@ -2236,6 +2438,7 @@ function init() { ,ro: 'Start senzor' ,bg: 'Стартиране на сензор' ,hr: 'Start senzora' + ,dk: 'Sensorstart' } ,'Sensor Change' : { cs: 'Výměna sensoru' @@ -2247,6 +2450,7 @@ function init() { ,ro: 'Schimbare senzor' ,bg: 'Смяна на сензор' ,hr: 'Promjena senzora' + ,dk: 'Sensor ombytning' } ,'Dexcom Sensor Start' : { cs: 'Spuštění sensoru' @@ -2258,6 +2462,7 @@ function init() { ,ro: 'Pornire senzor Dexcom' ,bg: 'Поставяне на Декском сензор' ,hr: 'Start Dexcom senzora' + ,dk: 'Dexcom sensor start' } ,'Dexcom Sensor Change' : { cs: 'Výměna sensoru' @@ -2269,6 +2474,7 @@ function init() { ,ro: 'Schimbare senzor Dexcom' ,bg: 'Смяна на Декском сензор' ,hr: 'Promjena Dexcom senzora' + ,dk: 'Dexcom sensor ombytning' } ,'Insulin Cartridge Change' : { cs: 'Výměna inzulínu' @@ -2280,6 +2486,7 @@ function init() { ,bg: 'Смяна на резервоар' ,hr: 'Promjena spremnika inzulina' ,sv: 'Insulinreservoarbyte' + ,dk: 'Skift insulin beholder' } ,'D.A.D. Alert' : { cs: 'D.A.D. Alert' @@ -2291,6 +2498,7 @@ function init() { ,bg: 'Сигнал от обучено куче' ,hr: 'Obavijest dijabetičkog psa' ,sv: 'Voff voff! (Diabeteshundalarm!)' + ,dk: 'Vuf Vuf! (Diabeteshundealarm!)' } ,'Glucose Reading' : { cs: 'Hodnota glykémie' @@ -2302,6 +2510,7 @@ function init() { ,ro: 'Valoare glicemie' ,bg: 'Кръвна захар' ,hr: 'Vrijednost GUK-a' + ,dk: 'Glukose aflæsning' } ,'Measurement Method' : { cs: 'Metoda měření' @@ -2313,6 +2522,7 @@ function init() { ,bg: 'Метод на измерване' ,hr: 'Metoda mjerenja' ,sv: 'Mätmetod' + ,dk: 'Målemetode' } ,'Meter' : { cs: 'Glukoměr' @@ -2324,6 +2534,7 @@ function init() { ,es: 'Glucómetro' ,bg: 'Глюкомер' ,hr: 'Glukometar' + ,dk: 'Glukosemeter' } ,'Insulin Given' : { cs: 'Inzulín' @@ -2335,6 +2546,7 @@ function init() { ,bg: 'Инсулин' ,hr: 'Količina iznulina' ,sv: 'Insulindos' + ,dk: 'Insulin dosis' } ,'Amount in grams' : { cs: 'Množství v gramech' @@ -2346,6 +2558,7 @@ function init() { ,ro: 'Cantitate în grame' ,bg: ' К-во в грамове' ,hr: 'Količina u gramima' + ,dk: 'Antal gram' } ,'Amount in units' : { cs: 'Množství v jednotkách' @@ -2357,6 +2570,7 @@ function init() { ,bg: 'К-во в единици' ,hr: 'Količina u jedinicama' ,sv: 'Antal enheter' + ,dk: 'Antal enheder' } ,'View all treatments' : { cs: 'Zobraz všechny ošetření' @@ -2368,6 +2582,7 @@ function init() { ,ro: 'Vezi toate evenimentele' ,bg: 'Преглед на всички събития' ,hr: 'Prikaži sve tretmane' + ,dk: 'Vis behandlinger' } ,'Enable Alarms' : { cs: 'Povolit alarmy' @@ -2378,6 +2593,7 @@ function init() { ,ro: 'Activează alarmele' ,bg: 'Активни аларми' ,hr: 'Aktiviraj alarme' + ,dk: 'Aktivere alarmer' } ,'When enabled an alarm may sound.' : { cs: 'Při povoleném alarmu zní zvuk' @@ -2388,6 +2604,7 @@ function init() { ,ro: 'Când este activ, poate suna o alarmă.' ,bg: 'Когато е активирано, алармата ще има звук' ,hr: 'Kad je aktiviran, alarm se može oglasiti' + ,dk: 'Når aktiveret kan alarm lyde' } ,'Urgent High Alarm' : { cs: 'Urgentní vysoká glykémie' @@ -2398,6 +2615,7 @@ function init() { ,ro: 'Alarmă urgentă hiper' ,bg: 'Много висока КЗ' ,hr: 'Hitni alarm za hiper' + ,dk: 'Høj grænse overskredet' } ,'High Alarm' : { cs: 'Vysoká glykémie' @@ -2408,6 +2626,7 @@ function init() { ,ro: 'Alarmă hiper' ,bg: 'Висока КЗ' ,hr: 'Alarm za hiper' + ,dk: 'Høj grænse overskredet' } ,'Low Alarm' : { cs: 'Nízká glykémie' @@ -2418,6 +2637,7 @@ function init() { ,ro: 'Alarmă hipo' ,bg: 'Ниска КЗ' ,hr: 'Alarm za hipo' + ,dk: 'Lav grænse overskredet' } ,'Urgent Low Alarm' : { cs: 'Urgentní nízká glykémie' @@ -2428,6 +2648,7 @@ function init() { ,ro: 'Alarmă urgentă hipo' ,bg: 'Много ниска КЗ' ,hr: 'Hitni alarm za hipo' + ,dk: 'Advarsel: Lav' } ,'Stale Data: Warn' : { cs: 'Zastaralá data' @@ -2438,6 +2659,7 @@ function init() { ,ro: 'Date învechite: alertă' ,bg: 'Стари данни' ,hr: 'Pažnja: Stari podaci' + ,dk: 'Advarsel: Gamle data' } ,'Stale Data: Urgent' : { cs: 'Zastaralá data urgentní' @@ -2449,6 +2671,7 @@ function init() { ,ro: 'Date învechite: urgent' ,bg: 'Много стари данни' ,hr: 'Hitno: Stari podaci' + ,dk: 'Advarsel: Gamle data' } ,'mins' : { cs: 'min' @@ -2460,6 +2683,7 @@ function init() { ,ro: 'min' ,bg: 'мин' ,hr: 'min' + ,dk: 'min' } ,'Night Mode' : { cs: 'Noční mód' @@ -2471,6 +2695,7 @@ function init() { ,ro: 'Mod nocturn' ,bg: 'Нощен режим' ,hr: 'Noćni način' + ,dk: 'Nat mode' } ,'When enabled the page will be dimmed from 10pm - 6am.' : { cs: 'Když je povoleno, obrazovka je ztlumena 22:00 - 6:00' @@ -2481,6 +2706,7 @@ function init() { ,ro: 'La activare va scădea iluminarea între 22 și 6' ,bg: 'Когато е активирано, страницата ще е затъмнена от 22-06ч' ,hr: 'Kad je uključen, stranica će biti zatamnjena od 22-06' + ,dk: 'Når aktiveret vil denne side nedtones fra 22:00-6:00' } ,'Enable' : { cs: 'Povoleno' @@ -2492,6 +2718,7 @@ function init() { ,ro: 'Activează' ,bg: 'Активно' ,hr: 'Aktiviraj' + ,dk: 'Aktivere' } ,'Show Raw BG Data' : { cs: 'Zobraz RAW data' @@ -2503,6 +2730,7 @@ function init() { ,bg: 'Показвай RAW данни' ,hr: 'Prikazuj sirove podatke o GUK-u' ,sv: 'Visa RAW-data' + ,dk: 'Vis rå data' } ,'Never' : { cs: 'Nikdy' @@ -2514,6 +2742,7 @@ function init() { ,ro: 'Niciodată' ,bg: 'Никога' ,hr: 'Nikad' + ,dk: 'Aldrig' } ,'Always' : { cs: 'Vždy' @@ -2525,6 +2754,7 @@ function init() { ,ro: 'Întotdeauna' ,bg: 'Винаги' ,hr: 'Uvijek' + ,dk: 'Altid' } ,'When there is noise' : { cs: 'Při šumu' @@ -2536,6 +2766,7 @@ function init() { ,ro: 'Atunci când este diferență' ,bg: 'Когато има шум' ,hr: 'Kad postoji šum' + ,dk: 'Når der er støj' } ,'When enabled small white dots will be disaplyed for raw BG data' : { cs: 'Když je povoleno, malé tečky budou zobrazeny pro RAW data' @@ -2546,6 +2777,7 @@ function init() { ,ro: 'La activare vor apărea puncte albe reprezentând citirea brută a glicemiei' ,bg: 'Когато е активирано, малки бели точки ще показват RAW данните' ,hr: 'Kad je omogućeno, male bijele točkice će prikazivati sirove podatke o GUK-u.' + ,dk: 'Ved aktivering vil små hvide prikker blive vist for rå BG tal' } ,'Custom Title' : { cs: 'Vlastní název stránky' @@ -2557,6 +2789,7 @@ function init() { ,ro: 'Titlu particularizat' ,bg: 'Име на страницата' ,hr: 'Vlastiti naziv' + ,dk: 'Egen titel' } ,'Theme' : { cs: 'Téma' @@ -2568,6 +2801,7 @@ function init() { ,bg: 'Тема' ,hr: 'Tema' ,sv: 'Tema' + ,dk: 'Tema' } ,'Default' : { cs: 'Výchozí' @@ -2579,6 +2813,7 @@ function init() { ,bg: 'Черно-бяла' ,hr: 'Default' ,sv: 'Standard' + ,dk: 'Standard' } ,'Colors' : { cs: 'Barevné' @@ -2589,6 +2824,7 @@ function init() { ,ro: 'Colorată' ,bg: 'Цветна' ,hr: 'Boje' + ,dk: 'Farver' } ,'Reset, and use defaults' : { cs: 'Vymaž a nastav výchozí hodnoty' @@ -2600,6 +2836,7 @@ function init() { ,ro: 'Resetează și folosește setările implicite' ,bg: 'Нулирай и използвай стандартните настройки' ,hr: 'Resetiraj i koristi defaultne vrijednosti' + ,dk: 'Returner til standardopsætning' } ,'Calibrations' : { cs: 'Kalibrace' @@ -2610,6 +2847,7 @@ function init() { ,ro: 'Calibrări' ,bg: 'Калибрации' ,hr: 'Kalibriranje' + ,dk: 'Kalibrering' } ,'Alarm Test / Smartphone Enable' : { cs: 'Test alarmu' @@ -2620,6 +2858,7 @@ function init() { ,ro: 'Teste alarme / Activează pe smartphone' ,bg: 'Тестване на алармата / Активно за мобилни телефони' ,hr: 'Alarm test / Aktiviraj smartphone' + ,dk: 'Alarm test / Smartphone aktiveret' } ,'Bolus Wizard' : { cs: 'Bolusový kalkulátor' @@ -2631,6 +2870,7 @@ function init() { ,ro: 'Calculator sugestie bolus' ,bg: 'Съветник при изчисление на болуса' ,hr: 'Bolus wizard' + ,dk: 'Bolusberegner' } ,'in the future' : { cs: 'v budoucnosti' @@ -2642,6 +2882,7 @@ function init() { ,ro: 'în viitor' ,bg: 'в бъдещето' ,hr: 'U budućnosti' + ,dk: 'fremtiden' } ,'time ago' : { cs: 'min zpět' @@ -2653,6 +2894,7 @@ function init() { ,ro: 'în trecut' ,bg: 'преди време' ,hr: 'prije' + ,dk: 'tid siden' } ,'hr ago' : { cs: 'hod zpět' @@ -2663,6 +2905,7 @@ function init() { ,ro: 'oră în trecut' ,bg: 'час по-рано' ,hr: 'sat unazad' + ,dk: 'Time siden' } ,'hrs ago' : { cs: 'hod zpět' @@ -2674,6 +2917,7 @@ function init() { ,ro: 'h în trecut' ,bg: 'часа по-рано' ,hr: 'sati unazad' + ,dk: 'Timer siden' } ,'min ago' : { cs: 'min zpět' @@ -2685,6 +2929,7 @@ function init() { ,ro: 'minut în trecut' ,bg: 'минута по-рано' ,hr: 'minuta unazad' + ,dk: 'minutter siden' } ,'mins ago' : { cs: 'min zpět' @@ -2696,6 +2941,7 @@ function init() { ,ro: 'minute în trecut' ,bg: 'минути по-рано' ,hr: 'minuta unazad' + ,dk: 'minutter siden' } ,'day ago' : { cs: 'den zpět' @@ -2707,6 +2953,7 @@ function init() { ,ro: 'zi în trecut' ,bg: 'ден по-рано' ,hr: 'dan unazad' + ,dk: 'dag siden' } ,'days ago' : { cs: 'dnů zpět' @@ -2718,6 +2965,7 @@ function init() { ,ro: 'zile în trecut' ,bg: 'дни по-рано' ,hr: 'dana unazad' + ,dk: 'dage siden' } ,'long ago' : { cs: 'dlouho zpět' @@ -2729,6 +2977,7 @@ function init() { ,ro: 'timp în trecut' ,bg: 'преди много време' ,hr: 'prije dosta vremena' + ,dk: 'længe siden' } ,'Clean' : { cs: 'Čistý' @@ -2740,6 +2989,7 @@ function init() { ,ro: 'Curat' ,bg: 'Чист' ,hr: 'Čisto' + ,dk: 'Rent' } ,'Light' : { cs: 'Lehký' @@ -2751,6 +3001,7 @@ function init() { ,ro: 'Ușor' ,bg: 'Лек' ,hr: 'Lagano' + ,dk: 'Let' } ,'Medium' : { cs: 'Střední' @@ -2762,6 +3013,7 @@ function init() { ,ro: 'Mediu' ,bg: 'Среден' ,hr: 'Srednje' + ,dk: 'Middel' } ,'Heavy' : { cs: 'Velký' @@ -2773,6 +3025,7 @@ function init() { ,ro: 'Puternic' ,bg: 'Висок' ,hr: 'Teško' + ,dk: 'Voldsom' } ,'Treatment type' : { cs: 'Typ ošetření' @@ -2784,6 +3037,7 @@ function init() { ,ro: 'Tip tratament' ,bg: 'Вид събитие' ,hr: 'Vrsta tretmana' + ,dk: 'Behandlingstype' } ,'Raw BG' : { cs: 'Glykémie z RAW dat' @@ -2795,6 +3049,7 @@ function init() { ,ro: 'Citire brută a glicemiei' ,bg: 'Непреработена КЗ' ,hr: 'Sirovi podaci o GUK-u' + ,dk: 'Råt BS' } ,'Device' : { cs: 'Zařízení' @@ -2806,6 +3061,7 @@ function init() { ,ro: 'Dispozitiv' ,bg: 'Устройство' ,hr: 'Uređaj' + ,dk: 'Enhed' } ,'Noise' : { cs: 'Šum' @@ -2817,6 +3073,7 @@ function init() { ,ro: 'Zgomot' ,bg: 'Шум' ,hr: 'Šum' + ,dk: 'Støj' } ,'Calibration' : { cs: 'Kalibrace' @@ -2828,6 +3085,7 @@ function init() { ,ro: 'Calibrare' ,bg: 'Калибрация' ,hr: 'Kalibriranje' + ,dk: 'Kalibrering' } ,'Show Plugins' : { cs: 'Zobrazuj pluginy' @@ -2838,6 +3096,7 @@ function init() { ,ro: 'Arată plugin-urile' ,bg: 'Покажи добавките' ,hr: 'Prikaži plugine' + ,dk: 'Vis plugins' } ,'About' : { cs: 'O aplikaci' @@ -2849,6 +3108,7 @@ function init() { ,bg: 'Относно' ,hr: 'O aplikaciji' ,sv: 'Om' + ,dk: 'Om' } ,'Value in' : { cs: 'Hodnota v' @@ -2860,6 +3120,7 @@ function init() { ,bg: 'Стойност в' ,hr: 'Vrijednost u' ,sv: 'Värde om' + ,dk: 'Værdi i' } ,'Carb Time' : { cs: 'Čas jídla' @@ -2869,6 +3130,7 @@ function init() { ,bg: 'ВХ действа след' ,hr: 'Vrijeme unosa UH' ,sv: 'Kolhydratstid' + ,dk: 'Kulhydratstid' } }; diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..6678e90ffe5 100644 --- a/static/index.html +++ b/static/index.html @@ -110,6 +110,7 @@ +
    From 98d0b5dedd5a30aa63880c9e25a5dc0ff002244e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:26:13 -0700 Subject: [PATCH 623/661] allow the pills to wrap, lots of little adjustments --- lib/client/chart.js | 26 +++++++++++------------ lib/client/index.js | 19 ++++++----------- static/css/main.css | 51 +++++++++++++++++++-------------------------- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index 237557032d3..a88b9d7416c 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -5,7 +5,7 @@ var times = require('../times'); var DEBOUNCE_MS = 10 , UPDATE_TRANS_MS = 750 // milliseconds - , padding = { top: 0, right: 10, bottom: 30, left: 10 } + , padding = { bottom: 30 } ; function init (client, d3, $) { @@ -115,8 +115,7 @@ function init (client, d3, $) { // create svg and g to contain the chart contents chart.charts = d3.select('#chartContainer').append('svg') .append('g') - .attr('class', 'chartContainer') - .attr('transform', 'translate(' + padding.left + ',' + padding.top + ')'); + .attr('class', 'chartContainer'); chart.focus = chart.charts.append('g'); @@ -179,10 +178,9 @@ function init (client, d3, $) { // get current data range var dataRange = client.dataExtent(); - // get the entire container height and width subtracting the padding var chartContainerRect = chartContainer[0].getBoundingClientRect(); - var chartWidth = chartContainerRect.width - padding.left - padding.right; - var chartHeight = chartContainerRect.height - padding.top - padding.bottom; + var chartWidth = chartContainerRect.width; + var chartHeight = chartContainerRect.height - padding.bottom; // get the height of each chart based on its container size ratio var focusHeight = chart.focusHeight = chartHeight * .7; @@ -192,14 +190,14 @@ function init (client, d3, $) { var currentBrushExtent = createAdjustedRange(); // only redraw chart if chart size has changed - if ((client.prevChartWidth !== chartWidth) || (client.prevChartHeight !== chartHeight)) { + if ((chart.prevChartWidth !== chartWidth) || (chart.prevChartHeight !== chartHeight)) { - client.prevChartWidth = chartWidth; - client.prevChartHeight = chartHeight; + chart.prevChartWidth = chartWidth; + chart.prevChartHeight = chartHeight; //set the width and height of the SVG element - chart.charts.attr('width', chartWidth + padding.left + padding.right) - .attr('height', chartHeight + padding.top + padding.bottom); + chart.charts.attr('width', chartWidth) + .attr('height', chartHeight + padding.bottom); // ranges are based on the width and height available so reset chart.xScale.range([0, chartWidth]); @@ -296,7 +294,7 @@ function init (client, d3, $) { // add a y-axis line that opens up the brush extent from the context to the focus chart.focus.append('line') .attr('class', 'open-top') - .attr('stroke', 'black') + .attr('stroke', '#111') .attr('stroke-width', 2); // add a x-axis line that closes the the brush container on left side @@ -490,14 +488,14 @@ function init (client, d3, $) { .attr('x1', chart.xScale2(chart.brush.extent()[0])) .attr('y1', chart.focusHeight) .attr('x2', chart.xScale2(chart.brush.extent()[0])) - .attr('y2', client.prevChartHeight); + .attr('y2', chart.prevChartHeight); // transition open-right line to correct location chart.focus.select('.open-right') .attr('x1', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) .attr('y1', chart.focusHeight) .attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime))) - .attr('y2', client.prevChartHeight); + .attr('y2', chart.prevChartHeight); chart.focus.select('.now-line') .transition() diff --git a/lib/client/index.js b/lib/client/index.js index 47614614d38..063a8d00460 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -90,7 +90,9 @@ client.init = function init(serverSettings, plugins) { }; client.bottomOfPills = function bottomOfPills ( ) { - return minorPills.offset().top + minorPills.height(); + var bottomOfMinorPills = minorPills.offset().top + minorPills.height(); + var bottomOfStatusPills = statusPills.offset().top + statusPills.height(); + return Math.max(bottomOfMinorPills, bottomOfStatusPills); }; function formatTime(time, compact) { @@ -289,17 +291,6 @@ client.init = function init(serverSettings, plugins) { //only shown plugins get a chance to update visualisations plugins.updateVisualisations(client.sbx); - - if (client.bottomOfPills() != preBottomOfPills) { - chart.update(false); - } - - var chartContainer = $('#chartContainer'); - - console.info('>>>>updatePlugins client.bottomOfPills()', client.bottomOfPills()); - chartContainer.css('top', (client.bottomOfPills() + 10) + 'px'); - chartContainer.find('svg').css('height', 'calc(100vh - ' + (client.bottomOfPills() + 10)+ 'px)'); - } function clearCurrentSGV ( ) { @@ -335,6 +326,8 @@ client.init = function init(serverSettings, plugins) { updatePlugins([], nowDate); } + $('#chartContainer').css('top', client.bottomOfPills() + 'px'); + updateTimeAgo(); chart.scroll(nowDate); @@ -580,7 +573,6 @@ client.init = function init(serverSettings, plugins) { updateClock(); updateTimeAgoSoon(); - function Dropdown(el) { this.ddmenuitem = 0; @@ -811,6 +803,7 @@ client.init = function init(serverSettings, plugins) { if (!isInitialData) { isInitialData = true; chart = client.chart = require('./chart')(client, d3, $); + brushed(); chart.update(true); } else { chart.update(false); diff --git a/static/css/main.css b/static/css/main.css index 40aa2177fcf..e32b0d306fd 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -40,15 +40,10 @@ body { .primary { font-family: 'Ubuntu', Helvetica, Arial, sans-serif; - height: 180px; vertical-align: middle; clear: both; } -.has-minor-pills .primary { - height: 200px; -} - .bgStatus { float: right; text-align: center; @@ -86,6 +81,7 @@ body { line-height: 90px; vertical-align: middle; border: none; + background: none; } .bgStatus .majorPills { @@ -94,8 +90,8 @@ body { .minorPills { font-size: 22px; - margin-top: 10px; z-index: 500; + white-space: normal; } .majorPills > span:not(:first-child) { @@ -110,21 +106,26 @@ body { white-space: nowrap; border-radius: 5px; border: 2px solid #808080; + background: #808080; + display: inline-block; + margin-bottom: 4px; + padding: 2px 0; } .pill em, .pill label { - padding-left: 2px; - padding-right: 2px; + padding: 2px; + white-space: nowrap; } .pill em { font-style: normal; font-weight: bold; + background: #000; + border-radius: 3px; } .pill label { color: #000; - background: #808080; } .pill.warn em { @@ -160,7 +161,6 @@ body { .status { color: #808080; font-size: 100px; - line-height: 100px; } .statusBox { text-align: center; @@ -168,7 +168,6 @@ body { } .statusPills { font-size: 20px; - line-height: 30px; } .loading .statusPills { @@ -183,7 +182,6 @@ body { .statusPills .pill em { color: #bdbdbd; background: #000; - border-radius: 4px 0 0 4px; } .statusPills .pill label { @@ -195,18 +193,22 @@ body { } #chartContainer { - left:0; - right:0; - bottom:0; - height:auto; + left: 0; + right: 0; + bottom: 0; + height: auto; font-size: 20px; background: #111; display: block; - position:absolute; + position: absolute; + margin: 2px; } #chartContainer svg { width: 100%; + height: 100%; + margin: 0; + padding: 0; } #silenceBtn { @@ -234,6 +236,7 @@ body { display: inline-block; border-radius: 2px; border: 1px solid #808080; + padding: 0; } .pill.rawbg em { @@ -241,6 +244,7 @@ body { background-color: black; display: block; font-size: 20px; + border-radius: 0; } .pill.rawbg label { @@ -359,13 +363,11 @@ body { .status { font-size: 70px; - line-height: 60px; padding-top: 8px; } .statusPills { font-size: 15px; - line-height: 40px; } .pill.upbat label { @@ -427,7 +429,6 @@ body { .status { font-size: 50px; - line-height: 35px; width: 250px; } } @@ -436,7 +437,6 @@ body { .primary { text-align: center; margin-bottom: 0; - height: 152px; } .bgStatus { @@ -505,7 +505,7 @@ body { .focus-range { position: absolute; - top: 80px; + top: 60px; left: auto; right: 10px; margin: 0; @@ -574,7 +574,6 @@ body { .status { font-size: 50px; - line-height: 40px; padding-top: 5px; } @@ -594,7 +593,6 @@ body { text-align: center; width: 220px; left: 0; - position: absolute; } .bgStatus .currentBG { @@ -636,11 +634,6 @@ body { display: block; } - .status { - position: absolute; - top: 90px; - } - .statusBox { width: 220px; } From a0b025d507fb4eca90870f1dc66b9b94769315cc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:33:29 -0700 Subject: [PATCH 624/661] fix bottomOfPills to work when there aren't offsets in the tests --- lib/client/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index 063a8d00460..dbe14106702 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -90,8 +90,9 @@ client.init = function init(serverSettings, plugins) { }; client.bottomOfPills = function bottomOfPills ( ) { - var bottomOfMinorPills = minorPills.offset().top + minorPills.height(); - var bottomOfStatusPills = statusPills.offset().top + statusPills.height(); + //the offset's might not exist for some tests + var bottomOfMinorPills = minorPills.offset() ? minorPills.offset().top + minorPills.height() : 0; + var bottomOfStatusPills = statusPills.offset() ? statusPills.offset().top + statusPills.height() : 0; return Math.max(bottomOfMinorPills, bottomOfStatusPills); }; From 1a470d25a8a66f05d1cf18738fde5223d7f3b17c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:48:19 -0700 Subject: [PATCH 625/661] adjust renderer since prevChartWidth moved to chart; and tweek to prevent pill truncation --- lib/client/index.js | 4 +--- lib/client/renderer.js | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/client/index.js b/lib/client/index.js index dbe14106702..5e5dc542c3c 100644 --- a/lib/client/index.js +++ b/lib/client/index.js @@ -273,8 +273,6 @@ client.init = function init(serverSettings, plugins) { function updatePlugins (sgvs, time) { var pluginBase = plugins.base(majorPills, minorPills, statusPills, bgStatus, client.tooltip); - var preBottomOfPills = client.bottomOfPills(); - client.sbx = sandbox.clientInit( client.settings , new Date(time).getTime() //make sure we send a timestamp @@ -327,7 +325,7 @@ client.init = function init(serverSettings, plugins) { updatePlugins([], nowDate); } - $('#chartContainer').css('top', client.bottomOfPills() + 'px'); + $('#chartContainer').css('top', (client.bottomOfPills() + 5) + 'px'); updateTimeAgo(); chart.scroll(nowDate); diff --git a/lib/client/renderer.js b/lib/client/renderer.js index a24890c42ee..bd1626c187b 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -26,7 +26,7 @@ function init (client, d3) { } var dotRadius = function(type) { - var radius = client.prevChartWidth > WIDTH_BIG_DOTS ? 4 : (client.prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); + var radius = chart().prevChartWidth > WIDTH_BIG_DOTS ? 4 : (chart().prevChartWidth < WIDTH_SMALL_DOTS ? 2 : 3); if (type === 'mbg') { radius *= 2; } else if (type === 'forecast') { @@ -55,7 +55,7 @@ function init (client, d3) { renderer.bubbleScale = function bubbleScale ( ) { // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - return (client.prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (client.prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment(); + return (chart().prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (chart().prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment(); }; function isDexcom(device) { From 44caab378fd33fbbe12e23137abaea2041d649e3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 01:53:23 -0700 Subject: [PATCH 626/661] adjust the width of the open top line, to cover the bottom of the X axis --- lib/client/chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/chart.js b/lib/client/chart.js index a88b9d7416c..07a725b94ad 100644 --- a/lib/client/chart.js +++ b/lib/client/chart.js @@ -295,7 +295,7 @@ function init (client, d3, $) { chart.focus.append('line') .attr('class', 'open-top') .attr('stroke', '#111') - .attr('stroke-width', 2); + .attr('stroke-width', 15); // add a x-axis line that closes the the brush container on left side chart.focus.append('line') From 1f42e32eb88f7d517d87a0d940336baff44cc29f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 02:06:02 -0700 Subject: [PATCH 627/661] css clean up get rid of !important some hacks --- static/css/drawer.css | 9 --------- static/css/main.css | 21 --------------------- 2 files changed, 30 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index ea6ae3379f2..fbf7ad9f325 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -246,15 +246,6 @@ h1, legend, padding-left: 12px; } -.icon-battery-25, -.icon-battery-50, -.icon-battery-75, -.icon-battery-100, -.icon-angle-double-up { - font-size: 21px !important; - padding-left: 9px !important; -} - #notification { display: none; font-size: 14px; diff --git a/static/css/main.css b/static/css/main.css index e32b0d306fd..b0f0fd7f2ac 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -181,15 +181,6 @@ body { .statusPills .pill em { color: #bdbdbd; - background: #000; -} - -.statusPills .pill label { - background: #808080; -} - -.pill.upbat label { - padding: 0 !important; } #chartContainer { @@ -370,10 +361,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - .focus-range { margin: 0; } @@ -499,10 +486,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - .focus-range { position: absolute; top: 60px; @@ -581,10 +564,6 @@ body { font-size: 15px; } - .pill.upbat label { - font-size: 15px !important; - } - } @media (max-height: 480px) and (max-width: 400px) { From 868952a987a42690852cb1699e5f3c2410a689ca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 10:27:49 -0700 Subject: [PATCH 628/661] adjust rawbg pill padding --- static/css/main.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index b0f0fd7f2ac..f5fddb46811 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -236,11 +236,13 @@ body { display: block; font-size: 20px; border-radius: 0; + padding: 0 2px; } .pill.rawbg label { display: block; font-size: 14px; + padding: 0 2px; } .alarming .bgButton { From d4ab336733c9a6327d595f2420a60572fc183d15 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 15:11:13 -0700 Subject: [PATCH 629/661] minor tweeks to prevent extra scroll bars in IE --- static/css/drawer.css | 12 ++++++------ static/css/main.css | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index fbf7ad9f325..cd30fab0909 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -43,8 +43,9 @@ input[type=number]:invalid { } #drawer .actions a { - float: right; - padding: 10px 0; + display: block; + text-align: right; + padding: 10px 0; } #treatmentDrawer { @@ -137,14 +138,13 @@ input[type=number]:invalid { } #about { - margin-top: 1em; + margin-bottom: 55px; } #about div { - font-size: 0.75em; + font-size: 12px; } #about .links { - margin-top: 1em; - margin-bottom: 1em; + margin: 10px; } #about a { color: #fff; diff --git a/static/css/main.css b/static/css/main.css index f5fddb46811..891c60f4d99 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -12,6 +12,7 @@ body { font-family: 'Open Sans', Helvetica, Arial, sans-serif; background: #000; color: #bdbdbd; + overflow: hidden; } .container { @@ -193,6 +194,7 @@ body { display: block; position: absolute; margin: 2px; + overflow: hidden; } #chartContainer svg { @@ -285,6 +287,7 @@ body { background: white; border: 0; border-radius: 8px; + float: right; } .alarms { From 5ad6a2511b3b9942ef18f87ac4d9b73f5d978fc6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 15:11:55 -0700 Subject: [PATCH 630/661] fix more css indents --- static/css/drawer.css | 242 ++++++++++++++++++++-------------------- static/css/dropdown.css | 34 +++--- 2 files changed, 138 insertions(+), 138 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index cd30fab0909..a3c4c3b0ce8 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -1,45 +1,45 @@ #drawer button, #treatmentDrawer button{ - font-size: 18px; - font-weight: bolder; - background-color: #ccc; - padding: 5px 10px; - border-radius: 5px; - border: 2px solid #aaa; - box-shadow: 2px 2px 0 #eee; - width: 100%; + font-size: 18px; + font-weight: bolder; + background-color: #ccc; + padding: 5px 10px; + border-radius: 5px; + border: 2px solid #aaa; + box-shadow: 2px 2px 0 #eee; + width: 100%; } #drawer { - background-color: #666; - border-left: 1px solid #999; - box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); - color: #eee; - display: none; - font-size: 16px; - height: 100%; - overflow-y: auto; - position: absolute; - margin-top: 45px; - right: -200px; - width: 300px; - top: 0; - z-index: 1; + background-color: #666; + border-left: 1px solid #999; + box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); + color: #eee; + display: none; + font-size: 16px; + height: 100%; + overflow-y: auto; + position: absolute; + margin-top: 45px; + right: -200px; + width: 300px; + top: 0; + z-index: 1; } #drawer i, #treatmentDrawer i { - opacity: 0.6; + opacity: 0.6; } #drawer a, #treatmentDrawer a { - color: white; + color: white; } input[type=number]:invalid { - background-color: #FFCCCC; + background-color: #FFCCCC; } #drawer .actions { - margin: 10px 2px; + margin: 10px 2px; } #drawer .actions a { @@ -49,20 +49,20 @@ input[type=number]:invalid { } #treatmentDrawer { - background-color: #666; - border-left: 1px solid #999; - box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); - color: #eee; - display: none; - font-size: 16px; - height: 100%; - overflow-y: auto; - position: absolute; - margin-top: 45px; - right: -200px; - width: 300px; - top: 0; - z-index: 1; + background-color: #666; + border-left: 1px solid #999; + box-shadow: inset 4px 4px 5px 0 rgba(50, 50, 50, 0.75); + color: #eee; + display: none; + font-size: 16px; + height: 100%; + overflow-y: auto; + position: absolute; + margin-top: 45px; + right: -200px; + width: 300px; + top: 0; + z-index: 1; } #treatmentDrawer input { @@ -98,7 +98,7 @@ input[type=number]:invalid { } #eventTime { - padding-bottom: 15px; + padding-bottom: 15px; } #eventTime > span { @@ -138,164 +138,164 @@ input[type=number]:invalid { } #about { - margin-bottom: 55px; + margin-bottom: 55px; } #about div { - font-size: 12px; + font-size: 12px; } #about .links { - margin: 10px; + margin: 10px; } #about a { - color: #fff; + color: #fff; } form { - margin: 0; - padding: 0 10px; + margin: 0; + padding: 0 10px; } .serverSettings { - display: none; + display: none; } fieldset { - margin: 0.5em 0 1em 0; + margin: 0.5em 0 1em 0; } legend { - font-size: 1.1em; + font-size: 1.1em; } dl { - margin: 0 0 1em 0; + margin: 0 0 1em 0; } dl:last-child { - margin-bottom: 0; + margin-bottom: 0; } dd { - margin-left: 0; + margin-left: 0; } dd.numbers input { - text-align: right; - width: 50px; + text-align: right; + width: 50px; } dd.numbers label { - display: block; - float: left; - width: 50px; + display: block; + float: left; + width: 50px; } h1, legend, #toolbar .customTitle, #about .appName, #about .version { - font-weight: bold; - text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + font-weight: bold; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); } #about .appName { - font-size: 1em; + font-size: 1em; } #about .version { - font-size: 1.25em; - margin-left: 0.25em; + font-size: 1.25em; + margin-left: 0.25em; } #toolbar { - background: url(/images/logo2.png) no-repeat 3px 3px #333; - border-bottom: 1px solid #999; - top: 0; - margin: 0; - height: 44px; - text-shadow: 0 0 5px black; + background: url(/images/logo2.png) no-repeat 3px 3px #333; + border-bottom: 1px solid #999; + top: 0; + margin: 0; + height: 44px; + text-shadow: 0 0 5px black; } #toolbar .customTitle { - color: #ccc; - font-size: 16px; - margin-top: 0; - margin-left: 42px; - padding-top: 10px; - padding-right: 150px; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; + color: #ccc; + font-size: 16px; + margin-top: 0; + margin-left: 42px; + padding-top: 10px; + padding-right: 150px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } #buttonbar { - padding-right: 10px; - height: 44px; - opacity: 0.75; - vertical-align: middle; - position: absolute; - right: 0; - z-index: 500; + padding-right: 10px; + height: 44px; + opacity: 0.75; + vertical-align: middle; + position: absolute; + right: 0; + z-index: 500; } #buttonbar a, #buttonbar i { - color: #ccc; - font-size: 16px; - height: 44px; - line-height: 44px; + color: #ccc; + font-size: 16px; + height: 44px; + line-height: 44px; } #buttonbar a { - float: left; - text-decoration: none; - width: 34px; + float: left; + text-decoration: none; + width: 34px; } #buttonbar i { - padding-left: 12px; + padding-left: 12px; } #notification { - display: none; - font-size: 14px; - position: absolute; + display: none; + font-size: 14px; + position: absolute; } #notification, #notification a { - overflow: hidden; + overflow: hidden; } #notification { - opacity: 0.75; - top: 4px; - z-index: 99; + opacity: 0.75; + top: 4px; + z-index: 99; } #notification.info a { - background: #00f; - color: #fff; + background: #00f; + color: #fff; } #notification.warn a { - background: #cc0; - color: #000; + background: #cc0; + color: #000; } #notification.success a { - background: #090; - color: #fff; + background: #090; + color: #fff; } #notification.urgent a { - background: #c00; - color: #fff; + background: #c00; + color: #fff; } #notification a { - background: #c00; - border-radius: 5px; - color: #fff; - display: block; - height: 20px; - padding: 0.5em; - text-decoration: none; + background: #c00; + border-radius: 5px; + color: #fff; + display: block; + height: 20px; + padding: 0.5em; + text-decoration: none; } #notification span { - margin-right: 0.25em; + margin-right: 0.25em; } #notification i { - float: right; - opacity: 0.5; - vertical-align: top; + float: right; + opacity: 0.5; + vertical-align: top; } .experiments a { - color: #fff; - font-size: 1.5em; + color: #fff; + font-size: 1.5em; } diff --git a/static/css/dropdown.css b/static/css/dropdown.css index 0c2f5ec4f3d..a2211b84983 100644 --- a/static/css/dropdown.css +++ b/static/css/dropdown.css @@ -1,28 +1,28 @@ .dropdown-menu { - font-size: 200%; - margin: 0; - padding: 0; - position: absolute; - visibility: hidden; - right: 8px; + font-size: 200%; + margin: 0; + padding: 0; + position: absolute; + visibility: hidden; + right: 8px; } .dropdown-menu li { - list-style: none; - float: none; - display: inline; + list-style: none; + float: none; + display: inline; } .dropdown-menu li a { - display: block; - text-decoration: none; - white-space: nowrap; - width: auto; - background: #BBBBBB; - color: #24313C; - padding: 2px 10px; + display: block; + text-decoration: none; + white-space: nowrap; + width: auto; + background: #BBBBBB; + color: #24313C; + padding: 2px 10px; } .dropdown-menu li a:hover { - background: #EEEEFF; + background: #EEEEFF; } From 2ce698216f5f555d43716f3681fde78f7cb7aa68 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 16:47:37 -0700 Subject: [PATCH 631/661] adjust line-height to get pill borders just right --- static/css/main.css | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 891c60f4d99..50459110c9a 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -87,10 +87,12 @@ body { .bgStatus .majorPills { font-size: 30px; + line-height: 34px; } .minorPills { font-size: 22px; + line-height: 25px; z-index: 500; white-space: normal; } @@ -169,6 +171,7 @@ body { } .statusPills { font-size: 20px; + line-height: 23px; } .loading .statusPills { @@ -351,10 +354,12 @@ body { .bgStatus .majorPills { font-size: 20px; + line-height: 24px; } .bgStatus .minorPills { font-size: 16px; + line-height: 18px; } .status { @@ -364,6 +369,7 @@ body { .statusPills { font-size: 15px; + line-height: 17px; } .focus-range { @@ -413,10 +419,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } .status { @@ -461,10 +469,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } #silenceBtn a { @@ -489,6 +499,7 @@ body { display: inline; margin-left: auto; font-size: 15px; + line-height: 17px; } .focus-range { @@ -554,10 +565,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .minorPills { font-size: 12px; + line-height: 13px; } .status { @@ -567,6 +580,7 @@ body { .statusPills { font-size: 15px; + line-height: 17px; } } @@ -595,10 +609,12 @@ body { .bgStatus .majorPills { font-size: 15px; + line-height: 17px; } .bgStatus .majorPills { font-size: 12px; + line-height: 13px; } #silenceBtn { From fb231c84d4984af859ea43991ca22fbf36ad060c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 19 Aug 2015 17:00:22 -0700 Subject: [PATCH 632/661] no more !important's --- static/css/main.css | 12 ++++++------ static/index.html | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 50459110c9a..d5ab2afda9d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -131,12 +131,12 @@ body { color: #000; } -.pill.warn em { - color: yellow !important; +.container .pill.warn em { + color: yellow; } -.pill.urgent em { - color: red !important; +.container .pill.urgent em { + color: red; } .alarming-timeago #lastEntry.pill.warn { @@ -483,7 +483,7 @@ body { .status { padding-top: 0; - font-size: 20px !important; + font-size: 20px; } .statusBox { @@ -531,7 +531,7 @@ body { @media (max-height: 480px) { #toolbar { float: right; - height: auto !important; + height: auto; } #toolbar .customTitle { diff --git a/static/index.html b/static/index.html index 98c1f1bbdca..f200c42166d 100644 --- a/static/index.html +++ b/static/index.html @@ -25,9 +25,9 @@ + - From 167454e1c65d5510e94d0467b296f8d21bbddd0c Mon Sep 17 00:00:00 2001 From: jweismann Date: Thu, 20 Aug 2015 10:01:51 +0200 Subject: [PATCH 633/661] ups --- lib/language.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/language.js b/lib/language.js index ce35ad1c87c..e0fbe66b2b2 100644 --- a/lib/language.js +++ b/lib/language.js @@ -2595,7 +2595,7 @@ function init() { ,bg: 'Въпрос' ,hr: 'Pitanje' ,it: 'Domanda' - ,dk: 'Spørgsmål + ,dk: 'Spørgsmål' } ,'Announcement' : { bg: 'Известяване' From ebd48d9cbcbfab7bd0f3e4ff01eb00283ccd2289 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 10:46:49 -0700 Subject: [PATCH 634/661] more line-height and font size pill tweeks --- static/css/main.css | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index d5ab2afda9d..3be68b995b7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -498,8 +498,8 @@ body { .statusPills { display: inline; margin-left: auto; - font-size: 15px; - line-height: 17px; + font-size: 12px; + line-height: 13px; } .focus-range { @@ -612,7 +612,7 @@ body { line-height: 17px; } - .bgStatus .majorPills { + .bgStatus .minorPills { font-size: 12px; line-height: 13px; } @@ -638,4 +638,10 @@ body { width: 220px; } + .statusPills { + display: inline; + margin-left: auto; + font-size: 12px; + line-height: 13px; + } } From c8293c00c504512b3f3a72f1ad6cca13afc12e0d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 10:58:00 -0700 Subject: [PATCH 635/661] adjust sort of languages --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index 23ca79330dc..f9d266d4456 100644 --- a/static/index.html +++ b/static/index.html @@ -103,6 +103,7 @@ + @@ -111,7 +112,6 @@ - From c8d9a0dda81f7592913b26995db2491a3b3f3762 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 14:14:23 -0700 Subject: [PATCH 636/661] if there isn't a battery value, don't set the field --- lib/pebble.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index e96fe8af32f..34157a4a1fd 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -78,7 +78,10 @@ function prepareSGVs (req, sbx) { bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); } - bgs[0].battery = data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery.toString(); + if (data.devicestatus && data.devicestatus.uploaderBattery) { + bgs[0].battery = data.devicestatus.uploaderBattery.toString(); + } + if (req.iob) { var iobResult = iob.calcTotal(data.treatments, data.profile, Date.now()); if (iobResult) { From 70c226501cbafb76242f9fcc0e50c0e70a745935 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 20 Aug 2015 22:35:24 -0700 Subject: [PATCH 637/661] some pebble refactoring and a few more tests --- lib/pebble.js | 75 +++++++++++++++++++++++++++----------------- tests/pebble.test.js | 46 +++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 34157a4a1fd..0deffb221e4 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -35,10 +35,7 @@ function reverseAndSlice (entries, req) { } -function prepareSGVs (req, sbx) { - var bgs = []; - var data = sbx.data; - +function mapSGVs(req, sbx) { function scaleMgdlAPebbleLegacyHackThatWillNotGoAway (bg) { if (req.mmol) { return units.mgdlToMMOL(bg); @@ -47,49 +44,69 @@ function prepareSGVs (req, sbx) { } } - //for compatibility we're keeping battery and iob here, but they would be better somewhere else - if (data.sgvs.length > 0) { - var cal = sbx.lastEntry(sbx.data.cals); - - bgs = _.map(reverseAndSlice(data.sgvs, req), function transformSGV (sgv) { - var transformed = { - sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl) - , trend: directionToTrend(sgv.direction) - , direction: sgv.direction - , datetime: sgv.mills - }; - - if (req.rawbg && cal) { - transformed.filtered = sgv.filtered; - transformed.unfiltered = sgv.unfiltered; - transformed.noise = sgv.noise; - } + var cal = sbx.lastEntry(sbx.data.cals); - return transformed; - }); + return _.map(reverseAndSlice(sbx.data.sgvs, req), function transformSGV(sgv) { + var transformed = { + sgv: scaleMgdlAPebbleLegacyHackThatWillNotGoAway(sgv.mgdl), trend: directionToTrend(sgv.direction), direction: sgv.direction, datetime: sgv.mills + }; + + if (req.rawbg && cal) { + transformed.filtered = sgv.filtered; + transformed.unfiltered = sgv.unfiltered; + transformed.noise = sgv.noise; + } + + return transformed; + }); + +} + +function addExtraData (first, req, sbx) { + //for compatibility we're keeping battery and iob on the first bg, but they would be better somewhere else + var data = sbx.data; + + function addDelta() { var prev = data.sgvs.length >= 2 ? data.sgvs[data.sgvs.length - 2] : null; var current = sbx.lastSGVEntry(); //for legacy reasons we need to return a 0 for delta if it can't be calculated var deltaResult = delta.calc(prev, current, sbx); - bgs[0].bgdelta = deltaResult && deltaResult.scaled || 0; + first.bgdelta = deltaResult && deltaResult.scaled || 0; if (req.mmol) { - bgs[0].bgdelta = bgs[0].bgdelta.toFixed(1); + first.bgdelta = first.bgdelta.toFixed(1); } + } - if (data.devicestatus && data.devicestatus.uploaderBattery) { - bgs[0].battery = data.devicestatus.uploaderBattery.toString(); + function addBattery() { + if (data.devicestatus && data.devicestatus.uploaderBattery && data.devicestatus.uploaderBattery >= 0) { + first.battery = data.devicestatus.uploaderBattery.toString(); } + } + function addIOB() { if (req.iob) { var iobResult = iob.calcTotal(data.treatments, data.profile, Date.now()); if (iobResult) { - bgs[0].iob = iobResult.display; + first.iob = iobResult.display; } } } + addDelta(); + addBattery(); + addIOB(); +} + +function prepareBGs (req, sbx) { + if (sbx.data.sgvs.length === 0) { + return []; + } + + var bgs = mapSGVs(req, sbx); + addExtraData(bgs[0], req, sbx); + return bgs; } @@ -119,7 +136,7 @@ function pebble (req, res) { res.setHeader('content-type', 'application/json'); res.write(JSON.stringify({ status: [ {now: Date.now()} ] - , bgs: prepareSGVs(req, sbx) + , bgs: prepareBGs(req, sbx) , cals: prepareCals(req, sbx) })); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 876807866bc..f5ccf6060a2 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -165,11 +165,57 @@ describe('Pebble Endpoint', function ( ) { done( ); }); }); + + it('/pebble without battery', function (done) { + delete ctx.data.devicestatus.uploaderBattery; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); + + it('/pebble with a negative battery', function (done) { + ctx.data.devicestatus.uploaderBattery = -1; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); + + it('/pebble with a false battery', function (done) { + ctx.data.devicestatus.uploaderBattery = false; + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + var bgs = res.body.bgs; + bgs.length.should.equal(1); + should.not.exist(bgs[0].battery); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); }); describe('Pebble Endpoint with Raw and IOB', function ( ) { var pebbleRaw = require('../lib/pebble'); before(function (done) { + ctx.data.devicestatus.uploaderBattery = 100; var envRaw = require('../env')( ); envRaw.settings.enable = 'rawbg iob'; this.appRaw = require('express')( ); From 94103c76ae89948df16758477faeffee522e87dc Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Sat, 22 Aug 2015 10:38:28 +0300 Subject: [PATCH 638/661] Fixed to work with latest dev --- lib/pebble.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index e7ddbea9300..877c4764b30 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -5,8 +5,8 @@ var _ = require('lodash'); var sandbox = require('./sandbox')(); var units = require('./units')(); var iob = require('./plugins/iob')(); -var delta = require('./plugins/delta')(); var bwp = require('./plugins/boluswizardpreview')(); +var delta = require('./plugins/delta')(); var DIRECTIONS = { NONE: 0 @@ -92,13 +92,15 @@ function addExtraData (first, req, sbx) { if (iobResult) { first.iob = iobResult.display; } - + sbx.properties.iob = iobResult; var bwpResult = bwp.calc(sbx); + if (bwpResult) { - bgs[0].bwp = bwpResult.bolusEstimateDisplay; - bgs[0].bwpo = bwpResult.outcomeDisplay; + first.bwp = bwpResult.bolusEstimateDisplay; + first.bwpo = bwpResult.outcomeDisplay; } + } } From 1787eb3123b40911b86ea95500656b5f6b8a23b4 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 10:36:02 -0700 Subject: [PATCH 639/661] some minor fixes and improvements for the profile editor --- lib/api/profile/index.js | 4 ++-- lib/profile.js | 2 +- static/css/main.css | 1 - static/css/profile.css | 25 +++++++++++++++++-------- static/profile/index.html | 24 ++++++++++++++---------- static/profile/js/profile.js | 33 +++++++++++++++++++++------------ 6 files changed, 55 insertions(+), 34 deletions(-) diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index c8f62323ddd..99295b05a57 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -41,7 +41,7 @@ function configure (app, wares, ctx) { console.log(err); } else { res.json(created); - console.log('Profile created'); + console.log('Profile created', created); } }); @@ -57,7 +57,7 @@ function configure (app, wares, ctx) { console.log(err); } else { res.json(created); - console.log('Profile saved'); + console.log('Profile saved', created); } }); diff --git a/lib/profile.js b/lib/profile.js index d1d80bbfbdd..7beff461a16 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -16,7 +16,7 @@ function storage (collection, ctx) { obj.created_at = (new Date( )).toISOString( ); } api().save(obj, function (err, doc) { - fn(err, doc.ops); + fn(err, obj); }); } diff --git a/static/css/main.css b/static/css/main.css index 3be68b995b7..ac92ef617c7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -12,7 +12,6 @@ body { font-family: 'Open Sans', Helvetica, Arial, sans-serif; background: #000; color: #bdbdbd; - overflow: hidden; } .container { diff --git a/static/css/profile.css b/static/css/profile.css index 943b2642714..e5567da3bff 100644 --- a/static/css/profile.css +++ b/static/css/profile.css @@ -1,10 +1,19 @@ #profile-editor h1 { - position: absolute; - top: 0; - right: 0; - left: 50px; - margin-top: 0; - margin-right: auto; - margin-left: auto; - text-align: center; + text-align: left; + margin: 10px; +} + +#pe_form input[type="text"], #pe_form input[type="number"] { + width: 60px; +} + +#pe_submit { + font-size: 18px; + font-weight: bolder; + background-color: #ccc; + padding: 5px 10px; + border-radius: 5px; + border: 2px solid #aaa; + box-shadow: 2px 2px 0 #eee; + margin: 20px 0; } \ No newline at end of file diff --git a/static/profile/index.html b/static/profile/index.html index 3db92f1c66b..57694e6151f 100644 --- a/static/profile/index.html +++ b/static/profile/index.html @@ -31,17 +31,17 @@ -
    +
    - Status: Not loaded + Status: Not loaded
    -

    Nightscout

    -
    -

    Profile editor

    -
    -
    - -
    +

    Nightscout

    +
    + +
    +

    Profile editor

    +
    +
    General patient profile settings @@ -89,7 +89,7 @@

    Profile editor

    ( - use values specific to GI of food + use GI specific values ) @@ -128,6 +128,10 @@

    Profile editor

    Authentication status: +

    + Status: Not loaded +

    + diff --git a/static/profile/js/profile.js b/static/profile/js/profile.js index 6ab51dd55de..bcc648371bc 100644 --- a/static/profile/js/profile.js +++ b/static/profile/js/profile.js @@ -9,6 +9,7 @@ var c_profile = null; //some commonly used selectors + var peStatus = $('.pe_status'); var timezoneInput = $('#pe_timezone'); var timeInput = $('#pe_time'); var dateInput = $('#pe_date'); @@ -76,7 +77,7 @@ var mongoprofiles = []; // Fetch data from mongo - $('#pe_status').hide().text('Loading profile records ...').fadeIn('slow'); + peStatus.hide().text('Loading profile records ...').fadeIn('slow'); $.ajax('/api/v1/profile.json', { success: function (records) { c_profile = {}; @@ -99,18 +100,18 @@ } convertToRanges(c_profile); - $('#pe_status').hide().text('Values loaded.').fadeIn('slow'); + peStatus.hide().text('Values loaded.').fadeIn('slow'); mongoprofiles.unshift(c_profile); } else { c_profile = _.cloneDeep(defaultprofile); mongoprofiles.unshift(c_profile); - $('#pe_status').hide().text('Default values used.').fadeIn('slow'); + peStatus.hide().text('Default values used.').fadeIn('slow'); } }, error: function () { c_profile = _.cloneDeep(defaultprofile); mongoprofiles.unshift(c_profile); - $('#pe_status').hide().text('Error. Default values used.').fadeIn('slow'); + peStatus.hide().text('Error. Default values used.').fadeIn('slow'); } }).done(initeditor); @@ -433,7 +434,7 @@ GUIToObject(); if (new Date(c_profile.startDate) > new Date()) { alert('Date must be set in the past'); - $('#pe_status').hide().html('Wrong date').fadeIn('slow'); + peStatus.hide().html('Wrong date').fadeIn('slow'); return false; } @@ -458,10 +459,11 @@ adjustedProfile.startDate = adjustedProfile.startDate.toISOString( ); + console.info('saving profile'); if (submitButton.text().indexOf('Create new record')>-1) { if (mongoprofiles.length > 1 && (new Date(c_profile.startDate) <= new Date(mongoprofiles[1].validfrom))) { alert('Date must be greater than last record '+new Date(mongoprofiles[1].startDate)); - $('#pe_status').hide().html('Wrong date').fadeIn('slow'); + peStatus.hide().html('Wrong date').fadeIn('slow'); return false; } @@ -475,13 +477,17 @@ , headers: { 'api-secret': Nightscout.client.hashauth.hash() } - }).done(function postSuccess (response) { - console.info('profile saved', response); + }).done(function postSuccess (data, status) { + console.info('profile created', data); + peStatus.hide().text(status).fadeIn('slow'); //not using the adjustedProfile here (doesn't have the defaults other code needs) var newprofile = _.cloneDeep(c_profile); mongoprofiles.unshift(newprofile); initeditor(); + }).fail(function(xhr, status, errorThrown) { + console.error('Profile not saved', status, errorThrown); + peStatus.hide().text(status).fadeIn('slow'); }); } else { $.ajax({ @@ -491,12 +497,15 @@ , headers: { 'api-secret': Nightscout.client.hashauth.hash() } - }).done(function putSuccess (response) { - console.info('profile updated', response); - $('#pe_status').hide().text(response).fadeIn('slow'); + }).done(function putSuccess (data, status) { + console.info('profile updated', data); + peStatus.hide().text(status).fadeIn('slow'); + }).fail(function(xhr, status, errorThrown) { + console.error('Profile not saved', status, errorThrown); + peStatus.hide().text(status).fadeIn('slow'); }); } - + if (event) { event.preventDefault(); } From 5a1baebdfd4455010a344cc55f1f8fefaeff8021 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 10:39:47 -0700 Subject: [PATCH 640/661] fix profile editor test --- tests/profileeditor.test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 632cff2ac43..6b643e3e1c2 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -85,6 +85,9 @@ describe('profile editor', function ( ) { opts.success([exampleProfile]); } fn(); + return { + fail: function () {} + } } }; }; From eda1fd7298be669c6da72498a6840ddbc2be3ae7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:02:44 -0700 Subject: [PATCH 641/661] adjust size is WIDTH_SMALL_DOTS limit since there is a little less padding we still want small dot in portrait mode for iPhone 6+ and Nexus 6 --- lib/client/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/renderer.js b/lib/client/renderer.js index bd1626c187b..dc11c86e63e 100644 --- a/lib/client/renderer.js +++ b/lib/client/renderer.js @@ -3,7 +3,7 @@ var times = require('../times'); var DEFAULT_FOCUS = times.hours(3).msecs - , WIDTH_SMALL_DOTS = 400 + , WIDTH_SMALL_DOTS = 420 , WIDTH_BIG_DOTS = 800 , TOOLTIP_TRANS_MS = 200 // milliseconds , UPDATE_TRANS_MS = 750 // milliseconds From 647fceee5fe024b995125fdccc067ae9cb440e1d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:08:34 -0700 Subject: [PATCH 642/661] fix some issues found by codacy --- lib/profile.js | 3 ++- tests/profileeditor.test.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/profile.js b/lib/profile.js index 7beff461a16..2131b088642 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -15,7 +15,8 @@ function storage (collection, ctx) { if (!obj.created_at) { obj.created_at = (new Date( )).toISOString( ); } - api().save(obj, function (err, doc) { + api().save(obj, function (err) { + //id should be added for new docs fn(err, obj); }); } diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 6b643e3e1c2..89d6db58ff1 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -87,7 +87,7 @@ describe('profile editor', function ( ) { fn(); return { fail: function () {} - } + }; } }; }; From e19af929c222a6190b9638beefe5aeb22b922b79 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 17:59:30 -0700 Subject: [PATCH 643/661] use client settings instead of the base serverSettings resolves #822 --- static/profile/index.html | 2 +- .../js/{profile.js => profileeditor.js} | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) rename static/profile/js/{profile.js => profileeditor.js} (95%) diff --git a/static/profile/index.html b/static/profile/index.html index 57694e6151f..54e51fe2c23 100644 --- a/static/profile/index.html +++ b/static/profile/index.html @@ -142,6 +142,6 @@

    Profile editor

    - + diff --git a/static/profile/js/profile.js b/static/profile/js/profileeditor.js similarity index 95% rename from static/profile/js/profile.js rename to static/profile/js/profileeditor.js index bcc648371bc..db568640be7 100644 --- a/static/profile/js/profile.js +++ b/static/profile/js/profileeditor.js @@ -5,6 +5,7 @@ var _ = window._; var moment = window.moment; var Nightscout = window.Nightscout; + var client = Nightscout.client; var c_profile = null; @@ -18,10 +19,10 @@ if (serverSettings === undefined) { console.error('server settings were not loaded, will not call init'); } else { - window.Nightscout.client.init(serverSettings, Nightscout.plugins); + client.init(serverSettings, Nightscout.plugins); } - var translate = Nightscout.client.translate; + var translate = client.translate; var defaultprofile = { //General values @@ -141,9 +142,9 @@ $('#pe_perGIvalues').change(switchStyle); // display status - $('#pe_units').text(serverSettings.settings.units); - $('#pe_timeformat').text(serverSettings.settings.timeFormat+'h'); - $('#pe_title').text(serverSettings.settings.customTitle); + $('#pe_units').text(client.settings.units); + $('#pe_timeformat').text(client.settings.timeFormat+'h'); + $('#pe_title').text(client.settings.customTitle); var lastvalidfrom = new Date(mongoprofiles[1] && mongoprofiles[1].startDate ? mongoprofiles[1].startDate : null); @@ -170,7 +171,7 @@ // Handling valid from date change function dateChanged(event) { - var newdate = new Date(Nightscout.client.utils.mergeInputTime(timeInput.val(), dateInput.val())); + var newdate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())); if (mongoprofiles.length<2 || !mongoprofiles[1].startDate || mongoprofiles.length>=2 && new Date(mongoprofiles[1].startDate).getTime() === newdate.getTime()) { submitButton.text('Update record').css('display',''); timeInput.css({'background-color':'white'}); @@ -384,7 +385,7 @@ function GUIToObject() { c_profile.dia = parseFloat($('#pe_dia').val()); - c_profile.startDate = new Date(Nightscout.client.utils.mergeInputTime(timeInput.val(), dateInput.val())); + c_profile.startDate = new Date(client.utils.mergeInputTime(timeInput.val(), dateInput.val())); c_profile.carbs_hr = parseInt($('#pe_hr').val()); c_profile.delay = 20; c_profile.perGIvalues = $('#pe_perGIvalues').is(':checked'); @@ -427,7 +428,7 @@ function toDisplayTime (minfrommidnight) { var time = moment().startOf('day').add(minfrommidnight,'minutes'); - return serverSettings.settings.timeFormat === '24' ? time.format('HH:mm') : time.format('h:mm A'); + return client.settings.timeFormat === '24' ? time.format('HH:mm') : time.format('h:mm A'); } function profileSubmit(event) { @@ -438,12 +439,12 @@ return false; } - if (!Nightscout.client.hashauth.isAuthenticated()) { + if (!client.hashauth.isAuthenticated()) { alert(translate('Your device is not authenticated yet')); return false; } - c_profile.units = serverSettings.settings.units; + c_profile.units = client.settings.units; var adjustedProfile = _.cloneDeep(c_profile); @@ -475,7 +476,7 @@ , url: '/api/v1/profile/' , data: adjustedProfile , headers: { - 'api-secret': Nightscout.client.hashauth.hash() + 'api-secret': client.hashauth.hash() } }).done(function postSuccess (data, status) { console.info('profile created', data); @@ -495,7 +496,7 @@ , url: '/api/v1/profile/' , data: adjustedProfile , headers: { - 'api-secret': Nightscout.client.hashauth.hash() + 'api-secret': client.hashauth.hash() } }).done(function putSuccess (data, status) { console.info('profile updated', data); From 26fd7733b888c00fb3f950c5d212a31272cf19e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:08:36 -0700 Subject: [PATCH 644/661] fix path for renamed file --- tests/profileeditor.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/profileeditor.test.js b/tests/profileeditor.test.js index 89d6db58ff1..ac6e3c95f29 100644 --- a/tests/profileeditor.test.js +++ b/tests/profileeditor.test.js @@ -111,7 +111,7 @@ describe('profile editor', function ( ) { }); benv.require(__dirname + '/../bundle/bundle.source.js'); - benv.require(__dirname + '/../static/profile/js/profile.js'); + benv.require(__dirname + '/../static/profile/js/profileeditor.js'); done(); }); From e30a5ae6036eb8c08eab096a17329ecc5e12b35b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:46:02 -0700 Subject: [PATCH 645/661] split example profiles out from the readme resolves #813 --- example-profiles.md | 77 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 example-profiles.md diff --git a/example-profiles.md b/example-profiles.md new file mode 100644 index 00000000000..717bc1df9dc --- /dev/null +++ b/example-profiles.md @@ -0,0 +1,77 @@ + + +**Table of Contents** + +- [Example Profiles](#example-profiles) + - [Simple profile](#simple-profile) + - [Profile can also use time periods for any field, for example:](#profile-can-also-use-time-periods-for-any-field-for-example) + + + +#Example Profiles + +These are only examples, make sure you update all fields to fit your needs + +##Simple profile + ```json + { + "dia": 3, + "carbs_hr": 20, + "carbratio": 30, + "sens": 100, + "basal": 0.125, + "target_low": 100, + "target_high": 120 + } + ``` + +##Profile can also use time periods for any field, for example: + + ```json + { + "carbratio": [ + { + "time": "00:00", + "value": 30 + }, + { + "time": "06:00", + "value": 25 + }, + { + "time": "14:00", + "value": 28 + } + ], + "basal": [ + { + "time": "00:00", + "value": 0.175 + }, + { + "time": "02:30", + "value": 0.125 + }, + { + "time": "05:00", + "value": 0.075 + }, + { + "time": "08:00", + "value": 0.100 + }, + { + "time": "14:00", + "value": 0.125 + }, + { + "time": "20:00", + "value": 0.175 + }, + { + "time": "22:00", + "value": 0.200 + } + ] + } + ``` From 460e14c275a089480c9d818330ac89e88dee608d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:47:40 -0700 Subject: [PATCH 646/661] forgot to pop from stash --- README.md | 69 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 2ef4c243a90..1665d9b74c3 100644 --- a/README.md +++ b/README.md @@ -254,73 +254,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Treatment Profile - Some of the [plugins](#plugins) make use of a treatment profile that is stored in Mongo. To use those plugins there should only be a single doc in the `profile` collection. + Some of the [plugins](#plugins) make use of a treatment profile that can be edited using the Profile Editor, see the link in the Settings drawer on your site. - Example Profile (change it to fit you): - - ```json - { - "dia": 3, - "carbs_hr": 20, - "carbratio": 30, - "sens": 100, - "basal": 0.125, - "target_low": 100, - "target_high": 120 - } - ``` - - Profile can also use time periods for any field, for example: - - ```json - { - "carbratio": [ - { - "time": "00:00", - "value": 30 - }, - { - "time": "06:00", - "value": 25 - }, - { - "time": "14:00", - "value": 28 - } - ], - "basal": [ - { - "time": "00:00", - "value": 0.175 - }, - { - "time": "02:30", - "value": 0.125 - }, - { - "time": "05:00", - "value": 0.075 - }, - { - "time": "08:00", - "value": 0.100 - }, - { - "time": "14:00", - "value": 0.125 - }, - { - "time": "20:00", - "value": 0.175 - }, - { - "time": "22:00", - "value": 0.200 - } - ] - } - ``` - Treatment Profile Fields: * `timezone` (Time Zone) - time zone local to the patient. *Should be set.* @@ -332,6 +267,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `basal` The basal rate set on the pump. * `target_high` - Upper target for correction boluses. * `target_low` - Lower target for correction boluses. + + Some example profiles are [here](example-profiles.md) Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). From 9797040d6f9719c5e05a20d9202be3d3a90ab886 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 18:51:47 -0700 Subject: [PATCH 647/661] remove outdated reference to database_configuration.json --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 1665d9b74c3..5d6569d4b15 100644 --- a/README.md +++ b/README.md @@ -85,14 +85,9 @@ $ npm install #Usage The data being uploaded from the server to the client is from a -MongoDB server such as [mongolab][mongodb]. In order to access the -database, the appropriate credentials need to be filled into the -[JSON][json] file in the root directory. SGV data from the database -is assumed to have the following fields: date, sgv. Once all that is -ready, just host your web app on your service of choice. +MongoDB server such as [mongolab][mongodb]. [mongodb]: https://mongolab.com -[json]: https://github.com/rnpenguin/cgm-remote-monitor/blob/master/database_configuration.json [autoconfigure]: http://nightscout.github.io/pages/configure/ [mongostring]: http://nightscout.github.io/pages/mongostring/ [update-fork]: http://nightscout.github.io/pages/update-fork/ From e4eaf8159140b97fc312bfc2f767babe05620141 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Aug 2015 19:10:15 -0700 Subject: [PATCH 648/661] don't link to the out-of-date .info site --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d6569d4b15..339673fc146 100644 --- a/README.md +++ b/README.md @@ -263,9 +263,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `target_high` - Upper target for correction boluses. * `target_low` - Lower target for correction boluses. - Some example profiles are [here](example-profiles.md) - - Additional information can be found [here](http://www.nightscout.info/wiki/labs/the-nightscout-iob-cob-website). + Some example profiles are [here](example-profiles.md). ## Setting environment variables Easy to emulate on the commandline: From 6828a7382a99cec45a549c2f38dd6758f99b0901 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 23 Aug 2015 01:29:45 -0700 Subject: [PATCH 649/661] don't spam the console by logging the whole bundle at startup --- bundle/bundle.source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index 4c39b094011..f9c9e195411 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -10,7 +10,7 @@ , plugins: require('../lib/plugins/')().registerClientDefaults() }; - console.info('Nightscout bundle ready', window.Nightscout); + console.info('Nightscout bundle ready'); })(); From 1779e619c18edc7a8e09fa47b78f9dbe19852301 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 23 Aug 2015 01:59:24 -0700 Subject: [PATCH 650/661] support for disabling default features use decodeURIComponent to support environments where spaces in env vars are a problem converted enable and alarmTypes to arrays --- README.md | 31 ++++-- env.js | 48 +-------- lib/api/index.js | 10 +- lib/settings.js | 124 ++++++++++++++++------- static/index.html | 2 +- tests/api.status.test.js | 6 +- tests/api.treatments.test.js | 2 +- tests/env.test.js | 10 -- tests/pebble.test.js | 2 +- tests/settings.test.js | 185 +++++++++++++++++++++-------------- 10 files changed, 237 insertions(+), 183 deletions(-) diff --git a/README.md b/README.md index 339673fc146..aa43bbe4209 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ Community maintained fork of the - [Core](#core) - [Predefined values for your browser settings (optional)](#predefined-values-for-your-browser-settings-optional) - [Plugins](#plugins) + - [Default Plugins](#default-plugins) + - [Built-in/Example Plugins:](#built-inexample-plugins) - [Extended Settings](#extended-settings) - [Pushover](#pushover) - [IFTTT Maker](#ifttt-maker) @@ -119,7 +121,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. ### Features/Labs - * `ENABLE` - Used to enable optional features, expects a space delimited list such as: `careportal rawbg iob`, see [plugins](#plugins) below + * `ENABLE` - Used to enable optional features, expects a space delimited list, such as: `careportal rawbg iob`, see [plugins](#plugins) below + * `DISABLE` - Used to disable default features, expects a space delimited list, such as: `direction upbat`, see [plugins](#plugins) below * `API_SECRET` - A secret passphrase that must be at least 12 characters long, required to enable `POST` and `PUT`; also required for the Care Portal * `BG_HIGH` (`260`) - must be set using mg/dl units; the high BG outside the target range that is considered urgent * `BG_TARGET_TOP` (`180`) - must be set using mg/dl units; the top of the target range, also used to draw the line on the chart @@ -164,7 +167,23 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. The built-in/example plugins that are available by default are listed below. The plugins may still need to be enabled by adding to the `ENABLE` environment variable. - **Built-in/Example Plugins:** +#### Default Plugins + + These can be disabled by setting the `DISABLE` env var, for example `DISABLE="direction upbat"` + + * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. + * `direction` (BG Direction) - Displays the trend direction. + * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. + * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). + * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. + * Enabled by default if no thresholds are set **OR** `ALARM_TYPES` includes `predict`. + * Use [extended settings](#extended-settings) to adjust AR2 behavior: + * `AR2_USE_RAW` (`false`) - to forecast using `rawbg` values when standard values don't trigger an alarm. + * `AR2_CONE_FACTOR` (`2`) - to adjust size of cone, use `0` for a single line. + * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` thresholds to generate alarms. + * Enabled by default if 1 of these thresholds is set **OR** `ALARM_TYPES` includes `simple`. + +#### Built-in/Example Plugins: * `rawbg` (Raw BG) - Calculates BG using sensor and calibration records from and displays an alternate BG values and noise levels. * `iob` (Insulin-on-Board) - Adds the IOB pill visualization in the client and calculates values that used by other plugins. Uses treatments with insulin doses and the `dia` and `sens` fields from the [treatment profile](#treatment-profile). @@ -179,14 +198,6 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `CAGE_INFO` (`44`) - If time since last `Site Change` matches `CAGE_INFO`, user will be warned of upcoming cannula change * `CAGE_WARN` (`48`) - If time since last `Site Change` matches `CAGE_WARN`, user will be alarmed to to change the cannula * `CAGE_URGENT` (`72`) - If time since last `Site Change` matches `CAGE_URGENT`, user will be issued a persistent warning of overdue change. - * `delta` (BG Delta) - Calculates and displays the change between the last 2 BG values. **Enabled by default.** - * `direction` (BG Direction) - Displays the trend direction. **Enabled by default.** - * `upbat` (Uploader Battery) - Displays the most recent battery status from the uploader phone. **Enabled by default.** - * `ar2` ([Forcasting using AR2 algorithm](https://github.com/nightscout/nightscout.github.io/wiki/Forecasting)) - Generates alarms based on forecasted values. **Enabled by default.** Use [extended settings](#extended-settings) to adjust AR2 behavior: - * `AR2_USE_RAW` (`false`) - to forecast using `rawbg` values when standard values don't trigger an alarm. - * `AR2_CONE_FACTOR` (`2`) - to adjust size of cone, use `0` for a single line. - * `simplealarms` (Simple BG Alarms) - Uses `BG_HIGH`, `BG_TARGET_TOP`, `BG_TARGET_BOTTOM`, `BG_LOW` settings to generate alarms. - * `errorcodes` (CGM Error Codes) - Generates alarms for CGM codes `9` (hourglass) and `10` (???). **Enabled by default.** * `treatmentnotify` (Treatment Notifications) - Generates notifications when a treatment has been entered and snoozes alarms minutes after a treatment. Default snooze is 10 minutes, and can be set using the `TREATMENTNOTIFY_SNOOZE_MINS` [extended setting](#extended-settings). * `basal` (Basal Profile) - Adds the Basal pill visualization to display the basal rate for the current time. Also enables the `bwp` plugin to calculate correction temp basal suggestions. Uses the `basal` field from the [treatment profile](#treatment-profile). * `bridge` (Share2Nightscout bridge) - Glucose reading directly from the Share service, uses these extended settings: diff --git a/env.js b/env.js index bcc9878330d..1b4c94a43e2 100644 --- a/env.js +++ b/env.js @@ -26,8 +26,6 @@ function config ( ) { setMongo(); updateSettings(); - env.hasExtendedSetting = hasExtendedSetting; - return env; } @@ -113,26 +111,6 @@ function setMongo() { function updateSettings() { - var enable = readENV('ENABLE', ''); - - function anyEnabled (features) { - return _.findIndex(features, function (feature) { - return enable.indexOf(feature) > -1; - }) > -1; - } - - //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it - if (hasExtendedSetting('PUSHOVER', process.env)) { - enable += ' pushover'; - } - - if (anyEnabled(['careportal', 'pushover', 'maker'])) { - enable += ' treatmentnotify'; - } - - //TODO: figure out something for default plugins, how can they be disabled? - enable += ' delta direction upbat errorcodes'; - var envNameOverrides = { UNITS: 'DISPLAY_UNITS' }; @@ -142,19 +120,8 @@ function updateSettings() { return readENV(envName); }); - //TODO: maybe get rid of ALARM_TYPES and only use enable? - if (env.settings.alarmTypes.indexOf('simple') > -1) { - enable = 'simplealarms ' + enable; - } - if (env.settings.alarmTypes.indexOf('predict') > -1) { - enable = 'ar2 ' + enable; - } - - env.settings.enable = enable; - env.settings.processRawSettings(); - //should always find extended settings last - env.extendedSettings = findExtendedSettings(enable, process.env); + env.extendedSettings = findExtendedSettings(process.env); } function readENV(varName, defaultValue) { @@ -170,23 +137,14 @@ function readENV(varName, defaultValue) { return value != null ? value : defaultValue; } -function hasExtendedSetting(prefix, envs) { - return _.find(envs, function (value, key) { - return key.indexOf(prefix + '_') >= 0 - || key.indexOf(prefix.toLowerCase() + '_') >= 0 - || key.indexOf('CUSTOMCONNSTR_' + prefix + '_') >= 0 - || key.indexOf('CUSTOMCONNSTR_' + prefix.toLowerCase() + '_') >= 0; - }) !== undefined; -} - -function findExtendedSettings (enables, envs) { +function findExtendedSettings (envs) { var extended = {}; function normalizeEnv (key) { return key.toUpperCase().replace('CUSTOMCONNSTR_', ''); } - enables.split(' ').forEach(function eachEnable(enable) { + _.forEach(env.settings.enable, function eachEnable(enable) { if (_.trim(enable)) { _.forIn(envs, function eachEnvPair (value, key) { var env = normalizeEnv(key); diff --git a/lib/api/index.js b/lib/api/index.js index 7329ab4faba..5e040eb4eca 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,9 +1,10 @@ 'use strict'; function create (env, ctx) { - var express = require('express'), - app = express( ) - ; + var _ = require('lodash') + , express = require('express') + , app = express( ) + ; var wares = require('../middleware/')(env); @@ -25,8 +26,7 @@ function create (env, ctx) { if (env.settings.enable) { app.extendedClientSettings = ctx.plugins && ctx.plugins.extendedClientSettings ? ctx.plugins.extendedClientSettings(env.extendedSettings) : {}; - env.settings.enable.toLowerCase().split(' ').forEach(function (value) { - var enable = value.trim(); + _.each(env.settings.enable, function (enable) { console.info('enabling feature:', enable); app.enable(enable); }); diff --git a/lib/settings.js b/lib/settings.js index 08e1dedf8b2..dbb6edf1490 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -12,7 +12,6 @@ function init ( ) { , customTitle: 'Nightscout' , theme: 'default' , alarmUrgentHigh: true - , alarmTypes: 'predict' , alarmHigh: true , alarmLow: true , alarmUrgentLow: true @@ -30,54 +29,108 @@ function init ( ) { } }; + settings.DEFAULT_FEATURES = ['delta', 'direction', 'upbat', 'errorcodes']; + var wasSet = []; function isSimple (value) { return typeof value !== 'function' && typeof value !== 'object'; } + function nameFromKey (key, nameType) { + return nameType === 'env' ? _.snakeCase(key).toUpperCase() : key; + } + function eachSettingAs (nameType) { - function topKeys (setter, keys) { + + function mapKeys (accessor, keys) { _.forIn(keys, function each (value, key) { if (isSimple(value)) { - var name = nameType === 'env' ? _.snakeCase(key).toUpperCase() : key; - var newValue = setter(name); + var newValue = accessor(nameFromKey(key, nameType)); if (newValue !== undefined) { wasSet.push(key); - keys[key] = setter(name); + keys[key] = newValue; } } }); } - return function allKeys (setter) { - topKeys(setter, settings); - - //for env vars thresholds are at the top level, they aren't set on the client (yet) - if (nameType === 'env') { - topKeys(setter, settings.thresholds); - } + return function allKeys (accessor) { + mapKeys(accessor, settings); + mapKeys(accessor, settings.thresholds); + enableAndDisableFeatures(accessor, nameType); }; } - function adjustShownPlugins ( ) { - //TODO: figure out something for some plugins to have them shown by default - if (settings.showPlugins !== '') { - settings.showPlugins += ' delta direction upbat'; - if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { - settings.showPlugins += ' rawbg'; + function enableAndDisableFeatures (accessor, nameType) { + + function getAndPrepare (key) { + var raw = accessor(nameFromKey(key, nameType)) || ''; + var cleaned = decodeURIComponent(raw).toLowerCase(); + return cleaned ? cleaned.split(' ') : []; + } + + function enableIf (feature, condition) { + if (condition) { + enable.push(feature); } } - } - function adjustAlarmTypes ( ) { - //if any threshold was set, and alarm types was not set default to simple - if (wasSet.indexOf('alarmTypes') === -1) { - var thresholdWasSet = _.findIndex(wasSet, function (name) { - return name.indexOf('bg') === 0; + function anyEnabled (features) { + return _.findIndex(features, function (feature) { + return enable.indexOf(feature) > -1; }) > -1; - settings.alarmTypes = thresholdWasSet ? 'simple' : 'predict'; } + + function prepareAlarmTypes ( ) { + var alarmTypes = _.filter(getAndPrepare('alarmTypes'), function onlyKnownTypes (type) { + return type === 'predict' || type === 'simple'; + }); + + if (alarmTypes.length === 0) { + var thresholdWasSet = _.findIndex(wasSet, function (name) { + return name.indexOf('bg') === 0; + }) > -1; + alarmTypes = thresholdWasSet ? ['simple'] : ['predict']; + } + + return alarmTypes; + } + + var enable = getAndPrepare('enable'); + var disable = getAndPrepare('disable'); + + settings.alarmTypes = prepareAlarmTypes(); + + //don't require pushover to be enabled to preserve backwards compatibility if there are extendedSettings for it + enableIf('pushover', accessor(nameFromKey('pushoverApiToken', nameType))); + + enableIf('treatmentnotify', anyEnabled(['careportal', 'pushover', 'maker'])); + + _.each(settings.DEFAULT_FEATURES, function eachDefault (feature) { + enableIf(feature, enable.indexOf(feature) < 0); + }); + + //TODO: maybe get rid of ALARM_TYPES and only use enable? + enableIf('simplealarms', settings.alarmTypes.indexOf('simple') > -1); + enableIf('ar2', settings.alarmTypes.indexOf('predict') > -1); + + if (disable.length > 0) { + console.info('disabling', disable); + } + + //all enabled feature, without any that have been disabled + settings.enable = _.difference(enable, disable); + + var thresholds = settings.thresholds; + + thresholds.bgHigh = Number(thresholds.bgHigh); + thresholds.bgTargetTop = Number(thresholds.bgTargetTop); + thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom); + thresholds.bgLow = Number(thresholds.bgLow); + + verifyThresholds(); + adjustShownPlugins(); } function verifyThresholds() { @@ -105,18 +158,15 @@ function init ( ) { } } - settings.processRawSettings = function processRawSettings ( ) { - var thresholds = settings.thresholds; - - thresholds.bgHigh = Number(thresholds.bgHigh); - thresholds.bgTargetTop = Number(thresholds.bgTargetTop); - thresholds.bgTargetBottom = Number(thresholds.bgTargetBottom); - thresholds.bgLow = Number(thresholds.bgLow); - - verifyThresholds(); - adjustShownPlugins(); - adjustAlarmTypes(); - }; + function adjustShownPlugins ( ) { + //TODO: figure out something for some plugins to have them shown by default + if (settings.showPlugins !== '') { + settings.showPlugins += ' delta direction upbat'; + if (settings.showRawbg === 'always' || settings.showRawbg === 'noise') { + settings.showPlugins += ' rawbg'; + } + } + } function isEnabled (feature) { var enabled = false; diff --git a/static/index.html b/static/index.html index 629c774ad45..7b647eb47d3 100644 --- a/static/index.html +++ b/static/index.html @@ -50,7 +50,7 @@
    --- - - +