From 4f195ddbf6615314a3bd51d115d30ab87a00bbcf Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 2 Aug 2014 00:26:52 -0700 Subject: [PATCH 001/714] introduce mqtt Introduce basic mqtt. --- env.js | 1 + lib/mqtt.js | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- server.js | 5 +++++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 lib/mqtt.js diff --git a/env.js b/env.js index 1ec8ab1d334..8a1e3f2d82f 100644 --- a/env.js +++ b/env.js @@ -21,6 +21,7 @@ function config ( ) { env.version = software.version; env.name = software.name; + env.MQTT_MONITOR = process.env.MQTT_MONITOR || null; env.DISPLAY_UNITS = process.env.DISPLAY_UNITS || 'mg/dl'; env.PORT = process.env.PORT || 1337; env.mongo = process.env.MONGO_CONNECTION || process.env.CUSTOMCONNSTR_mongo; diff --git a/lib/mqtt.js b/lib/mqtt.js new file mode 100644 index 00000000000..5b129b705f6 --- /dev/null +++ b/lib/mqtt.js @@ -0,0 +1,38 @@ +'use strict'; + +var es = require('event-stream'); +var mqtt = require('mqtt'); + +function process (client) { + var stream = es.through( + function _write (data) { + this.push(data); + } + ); + return stream; +} + +function every (storage) { + function iter (item, next) { + storage.create(item, next); + } + return es.map(iter); +} + +function configure (env) { + var uri = env['MQTT_MONITOR']; + var client = mqtt.connect(uri); + client.subscribe('sgvs'); + client.subscribe('published'); + client.subscribe('entries/sgv', function ( ) { + console.log('granted', arguments); + }); + + client.on('message', function (topic, msg) { console.log('topic', topic); + console.log('msg', msg); + }); + client.entries = process(client); + client.every = every; + return client; +} +module.exports = configure; diff --git a/package.json b/package.json index c0f768dee75..9df7a621a90 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "express-extension-to-accept": "0.0.2", "mongodb": "^1.4.7", "sgvdata": "0.0.2", - "socket.io": "^0.9.17" + "socket.io": "^0.9.17", + "mqtt": "~0.3.11" }, "devDependencies": { "supertest": "~0.13.0", diff --git a/server.js b/server.js index 8d8e460fece..a2dbfadf95c 100644 --- a/server.js +++ b/server.js @@ -72,6 +72,11 @@ store(function ready ( ) { var server = app.listen(PORT); console.log('listening', PORT); + if (env.MQTT_MONITOR) { + var mqtt = require('./lib/mqtt')(env); + mqtt.entries.pipe(mqtt.every(entries)); + } + /////////////////////////////////////////////////// // setup socket io for data and message transmission /////////////////////////////////////////////////// From cc0cd36934faf5984d6b4eb3faa1dff575d95616 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 2 Aug 2014 00:32:51 -0700 Subject: [PATCH 002/714] lint records first --- server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index a2dbfadf95c..93c47b3e0ec 100644 --- a/server.js +++ b/server.js @@ -74,7 +74,8 @@ store(function ready ( ) { if (env.MQTT_MONITOR) { var mqtt = require('./lib/mqtt')(env); - mqtt.entries.pipe(mqtt.every(entries)); + var es = require('event-stream'); + es.pipeline(mqtt.entries, entries.map( ), mqtt.every(entries)); } /////////////////////////////////////////////////// From b9e8a5faf11f21fc752dc201df4c048dbf8c1cfc Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 1 Oct 2014 13:50:31 -0700 Subject: [PATCH 003/714] make mqtt experiments suitable for hacking Uses experimental support in sgvdata for protobuf. --- lib/mqtt.js | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 5b129b705f6..eddd4d6a8e5 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -1,6 +1,7 @@ 'use strict'; var es = require('event-stream'); +var decoders = require('sgvdata/lib/protobuf'); var mqtt = require('mqtt'); function process (client) { @@ -18,16 +19,31 @@ function every (storage) { } return es.map(iter); } +function downloader ( ) { + var opts = { + model: decoders.models.CookieMonsterG4Download + , json: function (o) { return o; } + , payload: function (o) { return o; } + }; + return decoders(opts); +} function configure (env) { var uri = env['MQTT_MONITOR']; var client = mqtt.connect(uri); + var downloads = downloader( ); client.subscribe('sgvs'); client.subscribe('published'); + client.subscribe('/downloads/protobuf'); client.subscribe('entries/sgv', function ( ) { console.log('granted', arguments); }); + client.on('/downloads/protobuf', function (topic, msg) { console.log('topic', topic); + + console.log('DOWNLOAD msg', msg, downloads.parse(msg)); + }); + client.on('message', function (topic, msg) { console.log('topic', topic); console.log('msg', msg); }); diff --git a/package.json b/package.json index b835ffaf7ac..e60fa52612b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "mongodb": "^1.4.7", "moment": "2.8.1", "pushover-notifications": "0.2.0", - "sgvdata": "0.0.2", + "sgvdata": "git://github.com/bewest/sgvdata.git#wip/protobuf", "socket.io": "^0.9.17", "mqtt": "~0.3.11", "git-rev": "git://github.com/bewest/git-rev.git" From 8c98c1d782dc326ad0bc36b049c91160a35ac6e0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 13 Nov 2014 23:45:34 -0800 Subject: [PATCH 004/714] 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 005/714] 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 006/714] 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 007/714] 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 a22ec4c5349aeba9299bf876bcf69cb29aeb2392 Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 19 Nov 2014 13:46:33 -0800 Subject: [PATCH 008/714] add forever.js: make server run forever This helps with heroku and other non-azure hosts. --- Procfile | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Procfile b/Procfile index 489b2700aca..b62d80ef5af 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: node server.js +web: ./node_modules/.bin/forever server.js diff --git a/package.json b/package.json index 9fa88c37110..1b2f4675ab8 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "pushover-notifications": "0.2.0", "sgvdata": "0.0.2", "socket.io": "^0.9.17", - "git-rev": "git://github.com/bewest/git-rev.git" + "git-rev": "git://github.com/bewest/git-rev.git", + "forever": "~0.13.0" }, "devDependencies": { "supertest": "~0.13.0", From 079fe495db93a6f4dc1b436ff63ca623366d01f7 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 21 Nov 2014 12:06:29 -0800 Subject: [PATCH 009/714] tweak how forever runs --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index b62d80ef5af..f5d45241167 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: ./node_modules/.bin/forever server.js +web: ./node_modules/.bin/forever start -c server.js From 812d028008912f361b8df80b02832d754833f05a Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 21 Nov 2014 15:24:59 -0800 Subject: [PATCH 010/714] tweak forever to restart things Found that the parameters didn't work as advertised unless done like this. --- Procfile | 2 +- lib/storage.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Procfile b/Procfile index f5d45241167..32dd1c83adc 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: ./node_modules/.bin/forever start -c server.js +web: ./node_modules/.bin/forever --minUptime 100 -c node server.js diff --git a/lib/storage.js b/lib/storage.js index 58a965a7d87..6a02a83f25a 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,5 +1,7 @@ 'use strict'; var mongodb = require('mongodb'); +var levelup = require('levelup'); +var mongodown = require('mongo-down'); function init (env, cb) { var MongoClient = mongodb.MongoClient; @@ -11,6 +13,9 @@ function init (env, cb) { if (cb && cb.call) { cb(null, mongo); } return; } + if (!env.mongo) { + throw new Error("Mongo string is missing"); + } console.log("Connecting to mongo"); MongoClient.connect(env.mongo, function connected (err, db) { if (err) { From 4f0758a8a83c2f0ec4d2448bad5c048daaba1c0a Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 21 Nov 2014 15:34:07 -0800 Subject: [PATCH 011/714] oops, remove spurious modules --- lib/storage.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index 6a02a83f25a..d5b128a0592 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -1,7 +1,5 @@ 'use strict'; var mongodb = require('mongodb'); -var levelup = require('levelup'); -var mongodown = require('mongo-down'); function init (env, cb) { var MongoClient = mongodb.MongoClient; From 79c1e5ebd87949ed0aa68f872e6a5ff45d6aafa9 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 22 Nov 2014 07:13:58 -0700 Subject: [PATCH 012/714] 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 c8be1deb9d49c7efa3c20a1418b1a58df42921b6 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 22 Nov 2014 16:40:48 -0800 Subject: [PATCH 013/714] get mqtt basically working, very rough --- lib/entries.js | 5 ++++ lib/mqtt.js | 79 +++++++++++++++++++++++++++++++++++++++++++------- server.js | 2 +- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 1746bcefa5e..19f023a9278 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -140,6 +140,11 @@ function entries (name, storage) { }); } + function writeStream (opts) { + function map (item, next) { + } + } + // closure to represent the API function api ( ) { // obtain handle usable for querying the collection associated diff --git a/lib/mqtt.js b/lib/mqtt.js index eddd4d6a8e5..e08f5069ca4 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -1,7 +1,9 @@ 'use strict'; var es = require('event-stream'); +var Long = require('long'); var decoders = require('sgvdata/lib/protobuf'); +var direction = require('sgvdata/lib/utils').direction; var mqtt = require('mqtt'); function process (client) { @@ -28,24 +30,81 @@ function downloader ( ) { return decoders(opts); } -function configure (env) { +function toSGV (proto) { + var ts = long_time(proto.timestamp); + var obj = { + device: 'dexcom' + , date: ts.getTime( ) + , dateString: ts.toISOString( ) + , sgv: proto.sgv + , direction: direction(proto.direction) + , type: 'sgv' + }; + return obj; +} + +function createProtoStream (packet) { + var stream = es.readArray(packet.sgv); + function map (item, next) { + var r = toSGV(item); + console.log("ITEM", item, "TO SGV", r); + next(null, r); + } + return stream.pipe(es.map(map)); +} +function long_time (p) { + var ts = parseInt(new Long(p.low, p.high, p.unsigned).toString( )); + return new Date(ts); +} + +function configure (env, core) { var uri = env['MQTT_MONITOR']; - var client = mqtt.connect(uri); + var opts = {encoding: 'binary'}; + var client = mqtt.connect(uri, opts); var downloads = downloader( ); client.subscribe('sgvs'); client.subscribe('published'); - client.subscribe('/downloads/protobuf'); - client.subscribe('entries/sgv', function ( ) { + client.subscribe('/downloads/protobuf', granted); + client.subscribe('/uploader', granted); + client.subscribe('/entries/sgv', granted); + function granted ( ) { console.log('granted', arguments); - }); - - client.on('/downloads/protobuf', function (topic, msg) { console.log('topic', topic); + } - console.log('DOWNLOAD msg', msg, downloads.parse(msg)); - }); client.on('message', function (topic, msg) { console.log('topic', topic); - console.log('msg', msg); + 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, long_time(packet.download_timestamp)); + console.log("WRITE TO MONGO"); + createProtoStream(packet).pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING TO MONGO", err); + })); + + // core.write(packet); + break; + default: + console.log(topic, 'on message', 'msg', msg); + // core.write(msg); + break; + } }); client.entries = process(client); client.every = every; diff --git a/server.js b/server.js index 2bc3e4a3617..374cde658fa 100644 --- a/server.js +++ b/server.js @@ -83,7 +83,7 @@ store(function ready ( ) { console.log('listening', PORT); if (env.MQTT_MONITOR) { - var mqtt = require('./lib/mqtt')(env); + var mqtt = require('./lib/mqtt')(env, entries); var es = require('event-stream'); es.pipeline(mqtt.entries, entries.map( ), mqtt.every(entries)); } From 756e9e81d440d1bb241f6bdd7eebad10267a8332 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 22 Nov 2014 17:55:36 -0800 Subject: [PATCH 014/714] forgot long --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e60fa52612b..0ecff09ee10 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "sgvdata": "git://github.com/bewest/sgvdata.git#wip/protobuf", "socket.io": "^0.9.17", "mqtt": "~0.3.11", - "git-rev": "git://github.com/bewest/git-rev.git" + "git-rev": "git://github.com/bewest/git-rev.git", + "long": "~2.2.3" }, "devDependencies": { "supertest": "~0.13.0", From 9ad7bf1b2882095fcaeff80b91239fdf4b813d84 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 25 Nov 2014 12:26:09 -0800 Subject: [PATCH 015/714] test coverage --- Makefile | 3 +++ package.json | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/Makefile b/Makefile index bb605d08d82..521d5672779 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ coveralls: ${TESTS} | ./coverall.sh coverhtml: + MONGO_CONNECTION=${MONGO_CONNECTION} \ + CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} \ + CUSTOMCONNSTR_mongo_settings_collection=${CUSTOMCONNSTR_mongo_settings_collection} \ ./node_modules/.bin/mocha ${BLANKET} -R html-cov ${TESTS} > tests/coverage.html test: diff --git a/package.json b/package.json index 0ecff09ee10..2832f403c80 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,16 @@ "test": "make test", "postinstall": "node node_modules/bower/bin/bower install" }, + "config": { + "blanket": { + "pattern": [ + "tests", "lib", "server", "app", "static/js" + ], + "data-cover-never": [ + "node_modules" + ] + } + }, "engines": { "node": ">=0.10 <0.12" }, From c3ce249c7da36a0ab5b29dfdf415e3e6926b5c92 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 1 Dec 2014 17:34:52 -0800 Subject: [PATCH 016/714] 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 017/714] 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 018/714] 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 33e1a36726cd60ed5c3b8aee77c9534a2458732f Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Fri, 26 Dec 2014 03:57:10 -0600 Subject: [PATCH 019/714] Quick hack to get things working --- lib/mqtt.js | 20 +++++++++++++------- package.json | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index e08f5069ca4..fe56ed379f8 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -5,6 +5,7 @@ var Long = require('long'); var decoders = require('sgvdata/lib/protobuf'); var direction = require('sgvdata/lib/utils').direction; var mqtt = require('mqtt'); +var moment = require('moment'); function process (client) { var stream = es.through( @@ -31,13 +32,15 @@ function downloader ( ) { } function toSGV (proto) { - var ts = long_time(proto.timestamp); + var ts = moment(proto.download_timestamp); + console.log("TIMESTAMP", moment(proto.download_timestamp)); var obj = { device: 'dexcom' - , date: ts.getTime( ) - , dateString: ts.toISOString( ) - , sgv: proto.sgv - , direction: direction(proto.direction) + , date: ts.unix() * 1000 + , dateString: ts.format() + , sgv: proto.sgv_mgdl + , direction: direction(proto.trend) + ,noise: proto.noise , type: 'sgv' }; return obj; @@ -59,12 +62,15 @@ function long_time (p) { function configure (env, core) { var uri = env['MQTT_MONITOR']; - var opts = {encoding: 'binary'}; + var opts = { + encoding: 'binary', + clean: false + }; var client = mqtt.connect(uri, opts); var downloads = downloader( ); client.subscribe('sgvs'); client.subscribe('published'); - client.subscribe('/downloads/protobuf', granted); + client.subscribe('/downloads/protobuf',{qos: 2}, granted); client.subscribe('/uploader', granted); client.subscribe('/entries/sgv', granted); function granted ( ) { diff --git a/package.json b/package.json index 2832f403c80..c040d8b4f74 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "mongodb": "^1.4.7", "moment": "2.8.1", "pushover-notifications": "0.2.0", - "sgvdata": "git://github.com/bewest/sgvdata.git#wip/protobuf", + "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", "socket.io": "^0.9.17", "mqtt": "~0.3.11", "git-rev": "git://github.com/bewest/git-rev.git", From 1f1bc4d6f95907025cc81944c9d1dcc8e35bdab3 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 27 Dec 2014 11:14:41 -0800 Subject: [PATCH 020/714] blarg, attempt to debug mqtt --- lib/mqtt.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index fe56ed379f8..efcba12479c 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -31,16 +31,21 @@ function downloader ( ) { return decoders(opts); } +function ReceiverTime (ts) { + var base = Date.parse('2009-01-01T00:00:00-0800'); + return new Date(base + (ts * 1000)); +} + function toSGV (proto) { var ts = moment(proto.download_timestamp); - console.log("TIMESTAMP", moment(proto.download_timestamp)); + console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.timestamp_sec)); var obj = { device: 'dexcom' , date: ts.unix() * 1000 , dateString: ts.format() , sgv: proto.sgv_mgdl , direction: direction(proto.trend) - ,noise: proto.noise + , noise: proto.noise , type: 'sgv' }; return obj; @@ -64,7 +69,8 @@ function configure (env, core) { var uri = env['MQTT_MONITOR']; var opts = { encoding: 'binary', - clean: false + clean: false, + clientId: 'master' }; var client = mqtt.connect(uri, opts); var downloads = downloader( ); @@ -98,7 +104,7 @@ function configure (env, core) { } console.log('DOWNLOAD msg', msg.length, packet); console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, long_time(packet.download_timestamp)); + console.log('download_timestamp', packet.download_timestamp, Date.parse(packet.download_timestamp)); console.log("WRITE TO MONGO"); createProtoStream(packet).pipe(core.persist(function empty(err, result) { console.log("DONE WRITING TO MONGO", err); From f70a031357f3d10b27ee91ae855018710ea0d4be Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 30 Dec 2014 11:56:32 -0800 Subject: [PATCH 021/714] store each record according to own timestamp --- lib/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index efcba12479c..25e1159ca51 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -37,7 +37,7 @@ function ReceiverTime (ts) { } function toSGV (proto) { - var ts = moment(proto.download_timestamp); + var ts = moment(ReceiverTime(proto.timestamp_sec)); console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.timestamp_sec)); var obj = { device: 'dexcom' From bf2f6231da6635050395f80154256e53f5327557 Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 30 Dec 2014 19:06:40 -0800 Subject: [PATCH 022/714] tweak times and protobuf model with @ktind --- lib/mqtt.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 25e1159ca51..38d26525fd9 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -24,7 +24,7 @@ function every (storage) { } function downloader ( ) { var opts = { - model: decoders.models.CookieMonsterG4Download + model: decoders.models.CookieMonsterDownload , json: function (o) { return o; } , payload: function (o) { return o; } }; @@ -37,8 +37,8 @@ function ReceiverTime (ts) { } function toSGV (proto) { - var ts = moment(ReceiverTime(proto.timestamp_sec)); - console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.timestamp_sec)); + var ts = moment(ReceiverTime(proto.disp_timestamp_sec)); + console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.disp_timestamp_sec)); var obj = { device: 'dexcom' , date: ts.unix() * 1000 From 70877f7f67295e6fabfddc9af93ac9986e34d335 Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Tue, 30 Dec 2014 21:08:34 -0600 Subject: [PATCH 023/714] Adding experimental time calculation algorithm --- lib/mqtt.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 25e1159ca51..1d088ceffc7 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -24,7 +24,7 @@ function every (storage) { } function downloader ( ) { var opts = { - model: decoders.models.CookieMonsterG4Download + model: decoders.models.CookieMonsterDownload , json: function (o) { return o; } , payload: function (o) { return o; } }; @@ -36,13 +36,21 @@ function ReceiverTime (ts) { return new Date(base + (ts * 1000)); } -function toSGV (proto) { - var ts = moment(ReceiverTime(proto.timestamp_sec)); - console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.timestamp_sec)); +function toSGV (proto, receiver_time, download_time) { + var ts = moment(download_time); + console.log("Receiver time: ", receiver_time); + console.log("Record time: ", proto.sys_timestamp_sec); + console.log("Download time: ", ts.unix()); + var record_offset = receiver_time - proto.sys_timestamp_sec; + var record_time = ts.subtract(record_offset, 'second'); + + console.log("errr", " Offset: ",record_offset, " Record time: ", record_time.format()); + + //console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.disp_timestamp_sec)); var obj = { device: 'dexcom' - , date: ts.unix() * 1000 - , dateString: ts.format() + , date: record_time.unix() * 1000 + , dateString: record_time.format() , sgv: proto.sgv_mgdl , direction: direction(proto.trend) , noise: proto.noise @@ -53,8 +61,10 @@ function toSGV (proto) { function createProtoStream (packet) { var stream = es.readArray(packet.sgv); + var receiver_time = packet.receiver_system_time_sec; + var download_time = packet.download_timestamp; function map (item, next) { - var r = toSGV(item); + var r = toSGV(item, receiver_time, download_time); console.log("ITEM", item, "TO SGV", r); next(null, r); } From 650bcd1aae32fd3611979614c0a8dbd90d94b270 Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Wed, 31 Dec 2014 03:02:46 -0600 Subject: [PATCH 024/714] First pass at adding MGB, Sensor, Calibration, and Device Status records via MQTT New method to create device status with a backdated timestamp --- lib/devicestatus.js | 69 +++++---- lib/mqtt.js | 336 +++++++++++++++++++++++++++++--------------- server.js | 2 +- 3 files changed, 265 insertions(+), 142 deletions(-) diff --git a/lib/devicestatus.js b/lib/devicestatus.js index fb21d1c2490..9b9cf881364 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -1,34 +1,43 @@ 'use strict'; -function configure (collection, storage) { - - function create (obj, fn) { - obj.created_at = (new Date( )).toISOString( ); - 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 (fn) { - return api( ).find({ }).sort({created_at: -1}).toArray(fn); - } - - function api ( ) { - return storage.pool.db.collection(collection); - } - - api.list = list; - api.create = create; - api.last = last; - return api; +function configure(collection, storage) { + + 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(fn) { + return api().find({}).sort({created_at: -1}).toArray(fn); + } + + function api() { + return storage.pool.db.collection(collection); + } + + api.list = list; + api.create = create; + api.last = last; + return api; } module.exports = configure; diff --git a/lib/mqtt.js b/lib/mqtt.js index 1d088ceffc7..6da3253415b 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -1,135 +1,249 @@ 'use strict'; var es = require('event-stream'); -var Long = require('long'); var decoders = require('sgvdata/lib/protobuf'); 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); +function process(client) { + var stream = es.through( + function _write(data) { + this.push(data); + } + ); + return stream; +} + +function every(storage) { + function iter(item, next) { + storage.create(item, next); } - ); - return stream; + + return es.map(iter); } -function every (storage) { - function iter (item, next) { - storage.create(item, next); - } - return es.map(iter); +function downloader() { + var opts = { + model: decoders.models.CookieMonsterDownload + , json: function (o) { + return o; + } + , payload: function (o) { + return o; + } + }; + return decoders(opts); } -function downloader ( ) { - var opts = { - model: decoders.models.CookieMonsterDownload - , json: function (o) { return o; } - , payload: function (o) { return o; } - }; - return decoders(opts); + +function ReceiverTime(ts) { + var base = Date.parse('2009-01-01T00:00:00-0800'); + return new Date(base + (ts * 1000)); } -function ReceiverTime (ts) { - var base = Date.parse('2009-01-01T00:00:00-0800'); - return new Date(base + (ts * 1000)); +function toSGV(proto, receiver_time, download_time) { + console.log("Receiver time: ", receiver_time); + console.log("Record time: ", proto.sys_timestamp_sec); + console.log("Download time: ", download_time.unix()); + var record_offset = receiver_time - proto.sys_timestamp_sec; + var record_time = download_time.subtract(record_offset, 'second'); + + console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); + + var obj = { + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format() + , sgv: proto.sgv_mgdl + , direction: direction(proto.trend) + , noise: proto.noise + , type: 'sgv' + }; + return obj; } -function toSGV (proto, receiver_time, download_time) { - var ts = moment(download_time); - console.log("Receiver time: ", receiver_time); - console.log("Record time: ", proto.sys_timestamp_sec); - console.log("Download time: ", ts.unix()); - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = ts.subtract(record_offset, 'second'); - - console.log("errr", " Offset: ",record_offset, " Record time: ", record_time.format()); - - //console.log("errr", proto, "TIMESTAMP", ReceiverTime(proto.disp_timestamp_sec)); - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format() - , sgv: proto.sgv_mgdl - , direction: direction(proto.trend) - , noise: proto.noise - , type: 'sgv' - }; - return obj; +function createProtoStream(packet, download_time) { + var stream = es.readArray(packet.sgv); + var receiver_time = packet.receiver_system_time_sec; + + function map(item, next) { + var r = toSGV(item, receiver_time, download_time); + console.log("ITEM", item, "TO SGV", r); + next(null, r); + } + + return stream.pipe(es.map(map)); } -function createProtoStream (packet) { - var stream = es.readArray(packet.sgv); - var receiver_time = packet.receiver_system_time_sec; - var download_time = packet.download_timestamp; - function map (item, next) { - var r = toSGV(item, receiver_time, download_time); - console.log("ITEM", item, "TO SGV", r); - next(null, r); - } - return stream.pipe(es.map(map)); +function toCal(proto, receiver_time, download_time) { + console.log("Receiver time: ", receiver_time); + console.log("Record time: ", proto.sys_timestamp_sec); + console.log("Download time: ", download_time.unix()); + var record_offset = receiver_time - proto.sys_timestamp_sec; + var record_time = download_time.subtract(record_offset, 'second'); + + console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); + + var obj = { + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format() + , slope: proto.slope + , intercept: proto.intercept + , scale: proto.scale + , type: 'cal' + }; + return obj; } -function long_time (p) { - var ts = parseInt(new Long(p.low, p.high, p.unsigned).toString( )); - return new Date(ts); + +function createCalProtoStream(packet, download_time) { + var stream = es.readArray(packet.cal); + var receiver_time = packet.receiver_system_time_sec; + + function map(item, next) { + var r = toCal(item, receiver_time, download_time); + console.log("ITEM", item, "TO CAL", r); + next(null, r); + } + + return stream.pipe(es.map(map)); } -function configure (env, core) { - var uri = env['MQTT_MONITOR']; - var opts = { - encoding: 'binary', - clean: false, - clientId: 'master' - }; - 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, Date.parse(packet.download_timestamp)); - console.log("WRITE TO MONGO"); - createProtoStream(packet).pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING TO MONGO", err); - })); - - // core.write(packet); - break; - default: - console.log(topic, 'on message', 'msg', msg); - // core.write(msg); - break; +function toSensor(proto, receiver_time, download_time) { + console.log("Receiver time: ", receiver_time); + console.log("Record time: ", proto.sys_timestamp_sec); + console.log("Download time: ", download_time.unix()); + var record_offset = receiver_time - proto.sys_timestamp_sec; + var record_time = download_time.subtract(record_offset, 'second'); + + console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); + + var obj = { + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format() + , filtered: proto.filtered + , unfiltered: proto.unfiltered + , rssi: proto.rssi + , type: 'sensor' + }; + return obj; +} + +function createSensorProtoStream(packet, download_time) { + var stream = es.readArray(packet.sensor); + var receiver_time = packet.receiver_system_time_sec; + + function map(item, next) { + var r = toSensor(item, receiver_time, download_time); + console.log("ITEM", item, "TO Sensor", r); + next(null, r); + } + + return stream.pipe(es.map(map)); +} + +function toMeter(proto, receiver_time, download_time) { + console.log("Receiver time: ", receiver_time); + console.log("Record time: ", proto.sys_timestamp_sec); + console.log("Download time: ", download_time.unix()); + var record_offset = receiver_time - proto.sys_timestamp_sec; + var record_time = download_time.subtract(record_offset, 'second'); + + console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); + + var obj = { + device: 'dexcom' + , date: record_time.unix() * 1000 + , dateString: record_time.format() + , mbg: proto.mbg + , type: 'mbg' + }; + return obj; +} + +function createMeterProtoStream(packet, download_time) { + var stream = es.readArray(packet.meter); + var receiver_time = packet.receiver_system_time_sec; + + function map(item, next) { + var r = toMeter(item, receiver_time, download_time); + console.log("ITEM", item, "TO Meter", r); + next(null, r); + } + + return stream.pipe(es.map(map)); +} + +function configure(env, core, devicestatus) { + var uri = env['MQTT_MONITOR']; + var opts = { + encoding: 'binary', + clean: false, + clientId: 'master' + }; + 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.entries = process(client); - client.every = every; - return client; + + + 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, Date.parse(packet.download_timestamp)); + console.log("WRITE TO MONGO"); + var download_timestamp = moment(packet.download_timestamp); + createProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING SGV TO MONGO", result); + })); + createCalProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING Cal TO MONGO", result); + })); + createMeterProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING Meter TO MONGO", result); + })); + createSensorProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING Sensor TO MONGO", err); + })); + 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.write(packet); + break; + default: + console.log(topic, 'on message', 'msg', msg); + // core.write(msg); + break; + } + }); + client.entries = process(client); + client.every = every; + return client; } module.exports = configure; diff --git a/server.js b/server.js index 374cde658fa..9b605dfaab1 100644 --- a/server.js +++ b/server.js @@ -83,7 +83,7 @@ store(function ready ( ) { console.log('listening', PORT); if (env.MQTT_MONITOR) { - var mqtt = require('./lib/mqtt')(env, entries); + var mqtt = require('./lib/mqtt')(env, entries, devicestatus); var es = require('event-stream'); es.pipeline(mqtt.entries, entries.map( ), mqtt.every(entries)); } From b37069eedbdf2a88e02ce9a1e9de05499e24416a Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 31 Dec 2014 13:13:06 -0500 Subject: [PATCH 025/714] allow multiple instances of mqtt to multiplex This is intended to allow multiple listeners on mqtt to each receive data from MQTT, so long as they are writing to different databases. --- env.js | 3 +++ lib/mqtt.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/env.js b/env.js index 6d7c1830a1c..c94d0d64e02 100644 --- a/env.js +++ b/env.js @@ -34,6 +34,9 @@ function config ( ) { env.PORT = readENV('PORT', 1337); env.mongo = readENV('MONGO_CONNECTION') || readENV('MONGO') || readENV('MONGOLAB_URI'); env.mongo_collection = readENV('MONGO_COLLECTION', 'entries'); + if (env.MQTT_MONITOR) { + env.mqtt_client_id = [env.mongo.split('/').pop( ), env.mongo_collection].join('.'); + } env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings'); env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments'); env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus'); diff --git a/lib/mqtt.js b/lib/mqtt.js index 1d088ceffc7..bda036d73f2 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -80,7 +80,7 @@ function configure (env, core) { var opts = { encoding: 'binary', clean: false, - clientId: 'master' + clientId: env.mqtt_client_id }; var client = mqtt.connect(uri, opts); var downloads = downloader( ); From 76b9a155819588607341690018e67e7fbed5b115 Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Wed, 31 Dec 2014 18:52:36 -0600 Subject: [PATCH 026/714] Store the download object in the entries for debugging purposes Converting Sensor filtered and unfiltered data to a proper number --- lib/mqtt.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index c411d659979..544711386b6 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -123,8 +123,8 @@ function toSensor(proto, receiver_time, download_time) { device: 'dexcom' , date: record_time.unix() * 1000 , dateString: record_time.format() - , filtered: proto.filtered - , unfiltered: proto.unfiltered + , filtered: new Long(proto.filtered).toInt() + , unfiltered: new Long(proto.unfiltered).toInt() , rssi: proto.rssi , type: 'sensor' }; @@ -231,10 +231,19 @@ function configure(env, core, devicestatus) { createSensorProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { console.log("DONE WRITING Sensor TO MONGO", err); })); - devicestatus.create( { uploaderBattery: packet.uploader_battery, created_at: download_timestamp.toISOString() } , function empty(err, result) { + packet.type = "download"; + 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) { + console.log("Download written to mongo: ", packet) + }); + + // core.write(packet); break; default: From 11a7d9d687665df3e0e0749139917dee497118f0 Mon Sep 17 00:00:00 2001 From: Kevin Lee Date: Wed, 31 Dec 2014 23:04:29 -0600 Subject: [PATCH 027/714] Updating model and removing dead code --- lib/mqtt.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 544711386b6..81378b94847 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -26,7 +26,7 @@ function every(storage) { function downloader() { var opts = { - model: decoders.models.CookieMonsterDownload + model: decoders.models.G4Download , json: function (o) { return o; } @@ -37,11 +37,6 @@ function downloader() { return decoders(opts); } -function ReceiverTime(ts) { - var base = Date.parse('2009-01-01T00:00:00-0800'); - return new Date(base + (ts * 1000)); -} - function toSGV(proto, receiver_time, download_time) { console.log("Receiver time: ", receiver_time); console.log("Record time: ", proto.sys_timestamp_sec); From 6b4041a4f825f3b561bddb05ac954640d3b91d9b Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 9 Jan 2015 14:38:25 -0800 Subject: [PATCH 028/714] fix mbg undefined --- lib/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index bbee24d08e5..be6378b3b5b 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -152,7 +152,7 @@ function toMeter(proto, receiver_time, download_time) { device: 'dexcom' , date: record_time.unix() * 1000 , dateString: record_time.format() - , mbg: proto.mbg + , mbg: proto.mbg || proto.meter_bg_mgdl , type: 'mbg' }; return obj; From ddc9bf2cb4e86b828764b7eb459b889f85d9faa7 Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 9 Jan 2015 15:32:54 -0800 Subject: [PATCH 029/714] only need one stream factory Pass around the sync function for less code. --- lib/mqtt.js | 191 +++++++++++++++++----------------------------------- 1 file changed, 60 insertions(+), 131 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index be6378b3b5b..b7c81d88a57 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -37,137 +37,62 @@ function downloader() { return decoders(opts); } -function toSGV(proto, receiver_time, download_time) { - console.log("Receiver time: ", receiver_time); - console.log("Record time: ", proto.sys_timestamp_sec); - console.log("Download time: ", download_time.unix()); - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = download_time.clone().subtract(record_offset, 'second'); - - console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); - - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format() - , sgv: proto.sgv_mgdl - , direction: direction(proto.trend) - , noise: proto.noise - , type: 'sgv' - }; - return obj; +function toSGV (proto, vars) { + vars.sgv = proto.sgv_mgdl; + vars.direction = direction(proto.trend); + vars.noise = proto.noise; + vars.type = 'sgv'; + return vars; } -function createProtoStream(packet, download_time) { - var stream = es.readArray(packet.sgv); - var receiver_time = packet.receiver_system_time_sec; - - function map(item, next) { - var r = toSGV(item, receiver_time, download_time); - console.log("ITEM", item, "TO SGV", r); - next(null, r); - } - - return stream.pipe(es.map(map)); +function toCal (proto, vars) { + vars.slope = proto.slope; + vars.intercept = proto.intercept; + vars.scale = proto.scale; + vars.type = 'cal'; + return vars; } -function toCal(proto, receiver_time, download_time) { - console.log("Receiver time: ", receiver_time); - console.log("Record time: ", proto.sys_timestamp_sec); - console.log("Download time: ", download_time.unix()); - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = download_time.clone().subtract(record_offset, 'second'); - - console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); - - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format() - , slope: proto.slope - , intercept: proto.intercept - , scale: proto.scale - , type: 'cal' - }; - return obj; +function toSensor (proto, vars) { + vars.filtered = new Long(proto.filtered).toInt(); + vars.unfiltered = new Long(proto.unfiltered).toInt(); + vars.rssi = proto.rssi; + vars.type = 'sensor'; + return vars; } -function createCalProtoStream(packet, download_time) { - var stream = es.readArray(packet.cal); - var receiver_time = packet.receiver_system_time_sec; - - function map(item, next) { - var r = toCal(item, receiver_time, download_time); - console.log("ITEM", item, "TO CAL", r); - next(null, r); - } - - return stream.pipe(es.map(map)); +function toMeter (proto, result) { + result.type = 'mbg'; + result.mbg = proto.mbg || proto.meter_bg_mgdl; + return result; } -function toSensor(proto, receiver_time, download_time) { - console.log("Receiver time: ", receiver_time); - console.log("Record time: ", proto.sys_timestamp_sec); - console.log("Download time: ", download_time.unix()); - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = download_time.clone().subtract(record_offset, 'second'); - - console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); - - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format() - , filtered: new Long(proto.filtered).toInt() - , unfiltered: new Long(proto.unfiltered).toInt() - , rssi: proto.rssi - , type: 'sensor' - }; - return obj; -} - -function createSensorProtoStream(packet, download_time) { - var stream = es.readArray(packet.sensor); - var receiver_time = packet.receiver_system_time_sec; - - function map(item, next) { - var r = toSensor(item, receiver_time, download_time); - console.log("ITEM", item, "TO Sensor", r); - next(null, r); - } - - return stream.pipe(es.map(map)); +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( ) + }; + return obj; } -function toMeter(proto, receiver_time, download_time) { - console.log("Receiver time: ", receiver_time); - console.log("Record time: ", proto.sys_timestamp_sec); - console.log("Download time: ", download_time.unix()); - var record_offset = receiver_time - proto.sys_timestamp_sec; - var record_time = download_time.clone().subtract(record_offset, 'second'); - - console.log("errr", " Offset: ", record_offset, " Record time: ", record_time.format()); - - var obj = { - device: 'dexcom' - , date: record_time.unix() * 1000 - , dateString: record_time.format() - , mbg: proto.mbg || proto.meter_bg_mgdl - , type: 'mbg' - }; - return obj; -} - -function createMeterProtoStream(packet, download_time) { - var stream = es.readArray(packet.meter); - var receiver_time = packet.receiver_system_time_sec; - +function iter_mqtt_record_stream (packet, prop, sync) { + var list = packet[prop]; + console.log('incoming', prop, (list || [ ]).length); + 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 r = toMeter(item, receiver_time, download_time); - console.log("ITEM", item, "TO Meter", r); + 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); } - return stream.pipe(es.map(map)); } @@ -211,22 +136,26 @@ function configure(env, core, devicestatus) { } console.log('DOWNLOAD msg', msg.length, packet); console.log('download SGV', packet.sgv[0]); - console.log('download_timestamp', packet.download_timestamp, Date.parse(packet.download_timestamp)); + 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) { - createProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING SGV TO MONGO", result); - })); - createCalProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING Cal TO MONGO", result); - })); - createMeterProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING Meter TO MONGO", result); - })); - createSensorProtoStream(packet, download_timestamp).pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING Sensor TO MONGO", err); - })); + iter_mqtt_record_stream(packet, 'sgv', toSGV) + .pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING SGV TO MONGO", err, result.length); + })); + iter_mqtt_record_stream(packet, 'cal', toCal) + .pipe(core.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) { + console.log("DONE WRITING Meter TO MONGO", err, result.length); + })); + iter_mqtt_record_stream(packet, 'sensor', toSensor) + .pipe(core.persist(function empty(err, result) { + console.log("DONE WRITING Sensor TO MONGO", err, result.length); + })); } packet.type = "download"; devicestatus.create({ From de7cb78dcafb51f01d44bd0aaba65ad626d64cbf Mon Sep 17 00:00:00 2001 From: Ben West Date: Fri, 9 Jan 2015 15:43:25 -0800 Subject: [PATCH 030/714] force strict zero check --- lib/mqtt.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index b7c81d88a57..8f2a9fc6f57 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -139,7 +139,7 @@ function configure(env, core, devicestatus) { 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) { + if (packet.download_status === 0) { iter_mqtt_record_stream(packet, 'sgv', toSGV) .pipe(core.persist(function empty(err, result) { console.log("DONE WRITING SGV TO MONGO", err, result.length); From 841b4f1e03010aeec07ddd0504f35b550223eecc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 11 Jan 2015 20:23:44 -0800 Subject: [PATCH 031/714] adjust break point to better support display on iphone6+ in portrait mode --- static/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 9a0d4b8e6d9..39a356acf2f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -257,7 +257,7 @@ div.tooltip { } } -@media (max-width: 700px) { +@media (max-width: 750px) { #bgButton { font-size: 120%; } From 0d44f0a81e197856d9eb820310077ec5951eaa80 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 12 Jan 2015 10:17:33 -0800 Subject: [PATCH 032/714] 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 f3981e5d9205f14ef41d0decb7db656cbe3b0478 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 15 Jan 2015 23:52:07 -0800 Subject: [PATCH 033/714] stop emitting 'now' message to clients; log when data isn't being sent since it's not different --- lib/websocket.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 7175a3b671e..c816db8625e 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -45,7 +45,6 @@ var dir2Char = { function emitData ( ) { console.log('running emitData', now, patientData && patientData.length); if (patientData.length > 0) { - io.sockets.emit("now", now); io.sockets.emit("sgv", patientData); } } @@ -62,7 +61,6 @@ var dir2Char = { function listeners ( ) { io.sockets.on('connection', function (socket) { - io.sockets.emit("now", now); io.sockets.emit("sgv", patientData); io.sockets.emit("clients", ++watchers); socket.on('ack', function(alarmType, silenceTime) { @@ -312,6 +310,7 @@ function loadData() { } } } + function is_different (actual, predicted, mbg, treatment, errorCode) { if (patientData && patientData.length < 3) { return true; @@ -333,6 +332,7 @@ function loadData() { // textual diff of objects if (JSON.stringify(old) == JSON.stringify(last)) { + console.info("data isn't different, will not send to clients"); return false; } return true; From 17a5eaf2f0568a39a483bb4a638aea98a3b4f3f1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 15 Jan 2015 23:53:56 -0800 Subject: [PATCH 034/714] use a single static tooltip element instead of creating 1 in init fixes bug where you can get a double/stuck tooltip --- static/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static/index.html b/static/index.html index 39b393c38d3..280726e76d5 100644 --- a/static/index.html +++ b/static/index.html @@ -213,6 +213,9 @@

Nightscout

+ +
+ From 35ee0200cc4bd2dd9d7f883451642fc09e0ff72d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 15 Jan 2015 23:55:56 -0800 Subject: [PATCH 035/714] update clock clientside only; calculate time ago on each minute; display tooltip with last update/data times --- static/js/client.js | 82 +++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 55bb62d63de..a7e31e0a940 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -26,6 +26,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var socket , isInitialData = false , latestSGV + , latestUpdateTime , prevSGV , errorCode , treatments @@ -192,6 +193,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .style('opacity', function (d) { return highlightBrushPoints(d) }); } + function inRetroMode() { + var brushExtent = brush.extent(); + var elementHidden = document.getElementById('bgButton').hidden == ''; + return brushExtent[1].getTime() - THIRTY_MINS_IN_MS < now && elementHidden != true; + } + // function to call when context chart is brushed function brushed(skipTimer) { @@ -230,7 +237,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - var element = document.getElementById('bgButton').hidden == ''; var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); // predict for retrospective data @@ -239,7 +245,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 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 (brushExtent[1].getTime() - THIRTY_MINS_IN_MS < now && element != true) { + if (inRetroMode()) { // 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; var nowDataRaw = data.filter(function(d) { @@ -319,9 +325,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focusData = focusData.concat(prediction); var dateTime = new Date(now); nowDate = dateTime; - $('#currentTime') - .text(formatTime(dateTime)) - .css('text-decoration', ''); if (errorCode) { var errorDisplay; @@ -355,9 +358,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } else { - var secsSinceLast = (Date.now() - new Date(latestSGV.x).getTime()) / 1000; - $('#lastEntry').text(timeAgo(secsSinceLast)).toggleClass('current', secsSinceLast < 10 * 60); - + updateTimeAgo(); //in this case the SGV is unscaled if (latestSGV.y < 40) { $('.container .currentBG').text('LOW'); @@ -1174,13 +1175,38 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return predicted; } + function updateClock() { + now = Date.now(); + var dateTime = new Date(now); + $('#currentTime').text(formatTime(dateTime)).css('text-decoration', ''); + + var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; + setTimeout(init,interval); + + updateTimeAgo(); + + // Dim the screen by reducing the opacity when at nighttime + if (browserSettings.nightMode) { + if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { + $('body').css({ 'opacity': opacity.NIGHT }); + } else { + $('body').css({ 'opacity': opacity.DAY }); + } + } + } + + function updateTimeAgo() { + if (!latestSGV) return; + + var secsSinceLast = (Date.now() - new Date(latestSGV.x).getTime()) / 1000; + $('#lastEntry').text(timeAgo(secsSinceLast)).toggleClass('current', secsSinceLast < 10 * 60); + } + function init() { jqWindow = $(window); - tooltip = d3.select('body').append('div') - .attr('class', 'tooltip') - .style('opacity', 0); + tooltip = d3.select('body div.tooltip').style('opacity', 0); // Tick Values if (browserSettings.units == 'mmol') { @@ -1244,6 +1270,24 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }, 100); }; + updateClock(); + + $('#lastEntry').mousemove(function (event) { + var element = $('#lastEntry') + , offset = element.offset(); + + tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); + tooltip.html((latestUpdateTime ? 'Last Update Time:' + formatTime(new Date(latestUpdateTime)) + '
' : '') + + (latestSGV ? 'Last Data Time:' + formatTime(new Date(latestSGV.x)) + '
' : '') + ) + .style('left', event.pageX + 'px') + .style('top', (offset.top + element.height() + 15) + 'px'); + }).mouseout(function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); + var silenceDropdown = new Dropdown('.dropdown-menu'); $('#bgButton').click(function (e) { @@ -1260,27 +1304,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// socket = io.connect(); - socket.on('now', function (d) { - now = d; - var dateTime = new Date(now); - $('#currentTime').text(formatTime(dateTime)); - - // Dim the screen by reducing the opacity when at nighttime - if (browserSettings.nightMode) { - if (opacity.current != opacity.NIGHT && (dateTime.getHours() > 21 || dateTime.getHours() < 7)) { - $('body').css({ 'opacity': opacity.NIGHT }); - } else { - $('body').css({ 'opacity': opacity.DAY }); - } - } - }); - socket.on('sgv', function (d) { if (d.length > 1) { errorCode = d.length >= 5 ? d[4] : undefined; // 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]; } From 10c3a418cfa79cd1b83432fc22220cd800303528 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 16 Jan 2015 00:48:51 -0800 Subject: [PATCH 036/714] setTimeout was calling the wrong function, that caused the double tooltip bug and much worse broke scolling the first time setTimeout called the wrong function --- static/index.html | 2 -- static/js/client.js | 24 +++++++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/static/index.html b/static/index.html index 280726e76d5..676efa7aa8c 100644 --- a/static/index.html +++ b/static/index.html @@ -214,8 +214,6 @@

Nightscout

-
- diff --git a/static/js/client.js b/static/js/client.js index a7e31e0a940..5e4c90e96ea 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -194,6 +194,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function inRetroMode() { + if (!brush) return false; + var brushExtent = brush.extent(); var elementHidden = document.getElementById('bgButton').hidden == ''; return brushExtent[1].getTime() - THIRTY_MINS_IN_MS < now && elementHidden != true; @@ -326,6 +328,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var dateTime = new Date(now); nowDate = dateTime; + updateClockDisplay(); + if (errorCode) { var errorDisplay; @@ -1176,12 +1180,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function updateClock() { - now = Date.now(); - var dateTime = new Date(now); - $('#currentTime').text(formatTime(dateTime)).css('text-decoration', ''); - + updateClockDisplay(); var interval = (60 - (new Date()).getSeconds()) * 1000 + 5; - setTimeout(init,interval); + setTimeout(updateClock,interval); updateTimeAgo(); @@ -1195,8 +1196,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } + function updateClockDisplay() { + if (inRetroMode()) return; + now = Date.now(); + var dateTime = new Date(now); + $('#currentTime').text(formatTime(dateTime)).css('text-decoration', ''); + } + function updateTimeAgo() { - if (!latestSGV) return; + if (!latestSGV || inRetroMode()) return; var secsSinceLast = (Date.now() - new Date(latestSGV.x).getTime()) / 1000; $('#lastEntry').text(timeAgo(secsSinceLast)).toggleClass('current', secsSinceLast < 10 * 60); @@ -1206,7 +1214,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; jqWindow = $(window); - tooltip = d3.select('body div.tooltip').style('opacity', 0); + tooltip = d3.select('body').append('div') + .attr('class', 'tooltip') + .style('opacity', 0); // Tick Values if (browserSettings.units == 'mmol') { From 7726194fe3f5f1274830ba151c34b93ee8a00b86 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 18 Jan 2015 14:45:34 -0800 Subject: [PATCH 037/714] removed tooltip on time ago for now, with consider adding something similar later --- static/js/client.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 5e4c90e96ea..fcfd6da6616 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1282,22 +1282,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClock(); - $('#lastEntry').mousemove(function (event) { - var element = $('#lastEntry') - , offset = element.offset(); - - tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); - tooltip.html((latestUpdateTime ? 'Last Update Time:' + formatTime(new Date(latestUpdateTime)) + '
' : '') + - (latestSGV ? 'Last Data Time:' + formatTime(new Date(latestSGV.x)) + '
' : '') - ) - .style('left', event.pageX + 'px') - .style('top', (offset.top + element.height() + 15) + 'px'); - }).mouseout(function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); - var silenceDropdown = new Dropdown('.dropdown-menu'); $('#bgButton').click(function (e) { From 1aa95e227e41974c03a2d709436035c00acb25e7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 21 Jan 2015 18:23:11 -0800 Subject: [PATCH 038/714] only show the alarm threshold value in the alarm setting when simple mode is enabled --- static/index.html | 2 +- static/js/client.js | 1 + static/js/ui-utils.js | 12 ++++++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/static/index.html b/static/index.html index 676efa7aa8c..0fe5dd347e7 100644 --- a/static/index.html +++ b/static/index.html @@ -75,7 +75,7 @@

Nightscout

-
Enable Alarms
+
Enable Alarms
diff --git a/static/js/client.js b/static/js/client.js index fcfd6da6616..b2eb3a7a85a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1413,6 +1413,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , head: xhr.head , apiEnabled: xhr.apiEnabled , thresholds: xhr.thresholds + , alarm_types: xhr.alarm_types , units: xhr.units , careportalEnabled: xhr.careportalEnabled }; diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index f9b70185536..853b6de37f9 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -24,6 +24,10 @@ function getBrowserSettings(storage) { } } + function appendThresholdValue(threshold) { + return app.alarm_types.indexOf('simple') == -1 ? '' : '(' + scaleBg(threshold) + ')'; + } + try { var json = { "units": storage.get("units"), @@ -49,10 +53,10 @@ function getBrowserSettings(storage) { json.alarmHigh = setDefault(json.alarmHigh, defaultSettings.alarmHigh); json.alarmLow = setDefault(json.alarmLow, defaultSettings.alarmLow); json.alarmUrgentLow = setDefault(json.alarmUrgentLow, defaultSettings.alarmUrgentLow); - $("#alarm-urgenthigh-browser").prop("checked", json.alarmUrgentHigh).next().text('Urgent High Alarm (' + scaleBg(app.thresholds.bg_high) + ')'); - $("#alarm-high-browser").prop("checked", json.alarmHigh).next().text('High Alarm (' + scaleBg(app.thresholds.bg_target_top) + ')'); - $("#alarm-low-browser").prop("checked", json.alarmLow).next().text('Low Alarm (' + scaleBg(app.thresholds.bg_target_bottom) + ')'); - $("#alarm-urgentlow-browser").prop("checked", json.alarmUrgentLow).next().text('Urgent Low Alarm (' + scaleBg(app.thresholds.bg_low) + ')'); + $("#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.nightMode = setDefault(json.nightMode, defaultSettings.nightMode); $("#nightmode-browser").prop("checked", json.nightMode); From dae7388f6f8feddcd0fdbd69555b7834f0c259bc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 21 Jan 2015 18:28:52 -0800 Subject: [PATCH 039/714] formatting fix --- static/js/ui-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 853b6de37f9..bdb15c0d427 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -25,7 +25,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 { @@ -53,7 +53,7 @@ function getBrowserSettings(storage) { json.alarmHigh = setDefault(json.alarmHigh, defaultSettings.alarmHigh); json.alarmLow = setDefault(json.alarmLow, defaultSettings.alarmLow); json.alarmUrgentLow = setDefault(json.alarmUrgentLow, defaultSettings.alarmUrgentLow); - $("#alarm-urgenthigh-browser").prop("checked", json.alarmUrgentHigh).next().text('Urgent High Alarm ' + appendThresholdValue(app.thresholds.bg_high)); + $("#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)); From 4f6851783193978dd2da9ebd2f76d09ab85f3287 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Wed, 21 Jan 2015 21:32:44 -0800 Subject: [PATCH 040/714] unify README badge styles resolves #355 --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 97874456d2e..6804d6c2779 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ cgm-remote-monitor (a.k.a. Nightscout) ====================================== -[![Build Status](https://travis-ci.org/nightscout/cgm-remote-monitor.png)](https://travis-ci.org/nightscout/cgm-remote-monitor) -[![Dependency Status](https://david-dm.org/nightscout/cgm-remote-monitor.png)](https://david-dm.org/nightscout/cgm-remote-monitor) -[![Gitter chat](https://badges.gitter.im/nightscout.png)](https://gitter.im/nightscout/public) -[![Stories in Ready](https://badge.waffle.io/nightscout/cgm-remote-monitor.png?label=ready&title=Ready)](https://waffle.io/nightscout/cgm-remote-monitor) -[![Stories in Progress](https://badge.waffle.io/nightscout/cgm-remote-monitor.png?label=in+progress&title=In+Progress)](https://waffle.io/nightscout/cgm-remote-monitor) +[![Build Status][build-img]][build-url] +[![Dependency Status][dependency-img]][dependency-url] +[![Gitter chat][gitter-img]][gitter-url] +[![Stories in Ready][ready-img]][ready-url] +[![Stories in Progress][progress-img]][progress-url] -[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) +[![Deploy to Heroku][heroku-img]][heroku-url] This acts as a web-based CGM (Continuous Glucose Monitor) to allow multiple caregivers to remotely view a patient's glucose data in @@ -19,7 +19,21 @@ autoregressive second order model. Alarms are generated for high and low values, which can be cleared by any watcher of the data. Community maintained fork of the -[original cgm-remote-monitor](https://github.com/rnpenguin/cgm-remote-monitor). +[original cgm-remote-monitor][original]. + +[build-img]: https://img.shields.io/travis/nightscout/cgm-remote-monitor.svg +[build-url]: https://travis-ci.org/nightscout/cgm-remote-monitor +[dependency-img]: https://img.shields.io/david/nightscout/cgm-remote-monitor.svg +[dependency-url]: https://david-dm.org/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 +[ready-url]: https://waffle.io/nightscout/cgm-remote-monitor +[progress-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=in+progress&title=In+Progress +[progress-url]: https://waffle.io/nightscout/cgm-remote-monitor +[heroku-img]: https://www.herokucdn.com/deploy/button.png +[heroku-url]: https://heroku.com/deploy +[original]: https://github.com/rnpenguin/cgm-remote-monitor Install --------------- From 9bb7253aae4213a38f13a7fccdf3401624cb44e0 Mon Sep 17 00:00:00 2001 From: jimsiff Date: Thu, 22 Jan 2015 17:43:31 -0800 Subject: [PATCH 041/714] Alarm Type vars for Heroku Deploy button; general cleanup of var descriptions --- app.json | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/app.json b/app.json index 82bb263341c..77a1a308cd0 100644 --- a/app.json +++ b/app.json @@ -3,27 +3,52 @@ "repository": "https://github.com/nightscout/cgm-remote-monitor", "env": { "MONGO_COLLECTION": { - "description": "The mongo collection for CGM data. Most users should leave this as default.", + "description": "REQUIRED: The mongo collection used for CGM data. Default value is 'entries'. Most users should use the default.", "value": "entries", "required": true }, "API_SECRET": { - "description": "User generated password required for REST API and other features (12 character minimum).", + "description": "REQUIRED: User generated password used for REST API and optional features (12 character minimum).", "value": "", "required": true }, "ENABLE": { - "description": "Space delimited list of optional features to enable. Leave blank for a default site.", + "description": "Space delimited list of optional features to enable, such as 'careportal'.", + "value": "", + "required": false + }, + "ALARM_TYPES": { + "description": "Nightscout alarm behavior control. Default null value implies 'predict'. For adjustable alarm thresholds (set below), set to 'simple'.", + "value": "", + "required": false + }, + "BG_HIGH": { + "description": "Urgent high BG alarm. Default null value implies 260. Must be set in mg/dL. Only used with simple alarms.", + "value": "", + "required": false + }, + "BG_TARGET_TOP": { + "description": "Non-urgent high BG alarm, the top of your target range. Default null value implies 180. Must be set in mg/dL. Only used with simple alarms.", + "value": "", + "required": false + }, + "BG_TARGET_BOTTOM": { + "description": "Non urgent low BG alarm, the bottom of your target range. Default null value implies 80. Must be set in mg/dL. Only used with simple alarms.", + "value": "", + "required": false + }, + "BG_LOW": { + "description": "Urgent Low BG alarm. Default null value implies 55. Must be set in mg/dL. Only used with simple alarms.", "value": "", "required": false }, "PUSHOVER_API_TOKEN": { - "description": "Pushover API token, required for Pushover notifications. Leave blank for a default site.", + "description": "Pushover API token, required for Pushover notifications. Leave blank if not using Pushover.", "value": "", "required": false }, "PUSHOVER_USER_KEY": { - "description": "Pushover user key, required for Pushover notifications. Leave blank for a default site.", + "description": "Pushover user key, required for Pushover notifications. Leave blank if not using Pushover.", "value": "", "required": false } From 034bcefc0f477d48b51538be2ff8b661b548a5d3 Mon Sep 17 00:00:00 2001 From: Douglas Eichelberger Date: Thu, 22 Jan 2015 19:11:09 -0800 Subject: [PATCH 042/714] add coverage report to travis script resolves #268 --- .gitignore | 5 ++++- .travis.yml | 2 +- Makefile | 27 +++++++++------------------ README.md | 10 ++++++---- package.json | 6 ++++-- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 62cdda04fa1..b8b6cad5141 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ static/bower_components/ .DS_Store .vagrant -/iisnode \ No newline at end of file +/iisnode + +# istanbul output +coverage/ diff --git a/.travis.yml b/.travis.yml index 5c97a51b95e..c426b60af49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ before_script: - sleep 10 - echo mongo mongo_travis script: - - make test + - make travis diff --git a/Makefile b/Makefile index bb605d08d82..15dc02705ff 100644 --- a/Makefile +++ b/Makefile @@ -3,32 +3,23 @@ TESTS=tests/*.js MONGO_CONNECTION?=mongodb://localhost/test_db CUSTOMCONNSTR_mongo_settings_collection?=test_settings CUSTOMCONNSTR_mongo_collection?=test_sgvs - -BLANKET=--require blanket +MONGO_SETTINGS=MONGO_CONNECTION=${MONGO_CONNECTION} \ + CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} \ + CUSTOMCONNSTR_mongo_settings_collection=${CUSTOMCONNSTR_mongo_settings_collection} all: test travis-cov: - NODE_ENV=test node_modules/.bin/mocha ${BLANKET} -R 'travis-cov' ${TESTS} - -coveralls: NODE_ENV=test \ - ./node_modules/.bin/mocha ${BLANKET} -R mocha-lcov-reporter \ - ${TESTS} | ./coverall.sh - -coverhtml: - ./node_modules/.bin/mocha ${BLANKET} -R html-cov ${TESTS} > tests/coverage.html + ${MONGO_SETTINGS} \ + istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -vvv -R tap ${TESTS} && \ + cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && \ + rm -rf ./coverage test: - MONGO_CONNECTION=${MONGO_CONNECTION} \ - CUSTOMCONNSTR_mongo_collection=${CUSTOMCONNSTR_mongo_collection} \ - CUSTOMCONNSTR_mongo_settings_collection=${CUSTOMCONNSTR_mongo_settings_collection} \ + ${MONGO_SETTINGS} \ mocha --verbose -vvv -R tap ${TESTS} -precover: - ./node_modules/.bin/mocha ${BLANKET} ${SHOULD} -R html-cov ${TESTS} | w3m -T text/html - - -travis: test travis-cov coveralls coverhtml +travis: test travis-cov .PHONY: test diff --git a/README.md b/README.md index 6804d6c2779..265c14c17fe 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,10 @@ cgm-remote-monitor (a.k.a. Nightscout) [![Build Status][build-img]][build-url] [![Dependency Status][dependency-img]][dependency-url] +[![Coverage Status][coverage-img]][coverage-url] [![Gitter chat][gitter-img]][gitter-url] -[![Stories in Ready][ready-img]][ready-url] -[![Stories in Progress][progress-img]][progress-url] +[![Stories in Ready][ready-img]][waffle] +[![Stories in Progress][progress-img]][waffle] [![Deploy to Heroku][heroku-img]][heroku-url] @@ -25,12 +26,13 @@ Community maintained fork of the [build-url]: https://travis-ci.org/nightscout/cgm-remote-monitor [dependency-img]: https://img.shields.io/david/nightscout/cgm-remote-monitor.svg [dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor +[coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/coverage.svg +[coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=coverage [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 -[ready-url]: https://waffle.io/nightscout/cgm-remote-monitor +[waffle]: https://waffle.io/nightscout/cgm-remote-monitor [progress-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=in+progress&title=In+Progress -[progress-url]: https://waffle.io/nightscout/cgm-remote-monitor [heroku-img]: https://www.herokucdn.com/deploy/button.png [heroku-url]: https://heroku.com/deploy [original]: https://github.com/rnpenguin/cgm-remote-monitor diff --git a/package.json b/package.json index 65fd5067444..ef8c47f9790 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,10 @@ "git-rev": "git://github.com/bewest/git-rev.git" }, "devDependencies": { - "supertest": "~0.13.0", + "coveralls": "~2.11.2", + "istanbul": "~0.3.5", + "mocha": "~1.20.1", "should": "~4.0.4", - "mocha": "~1.20.1" + "supertest": "~0.13.0" } } From 33fa53ab300581528761378be0862e4699060081 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 22 Jan 2015 23:19:21 -0800 Subject: [PATCH 043/714] fixed bug that caused mbg outlines to collect at the far right of the context area --- static/js/client.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index fcfd6da6616..9294609bbbd 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -875,7 +875,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .attr('cx', function (d) { return xScale2(d.date); }) .attr('cy', function (d) { return yScale2(d.sgv); }) .attr('fill', function (d) { return d.color; }) - .style('opacity', function (d) { return highlightBrushPoints(d) }); + .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 new circle then just display contextCircles.enter().append('circle') From a98bd14f36de829210545d83ffffcbdb16ec6fdb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 22 Jan 2015 23:50:33 -0800 Subject: [PATCH 044/714] refactoring to remove duplicate setting of attr's --- static/js/client.js | 123 +++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 9294609bbbd..df15ee5ddcc 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -420,28 +420,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return radius; }; + function prepareFocusCircles(sel) { + sel.attr('cx', function (d) { return xScale(d.date); }) + .attr('cy', function (d) { 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) { + var device = d.device && d.device.toLowerCase(); + return (device == 'dexcom' ? 'white' : '#0099ff'); + }) + .attr('r', function (d) { return dotRadius(d.type); }); + + return sel; + } + // if already existing then transition each circle to its new position - focusCircles - .transition() - .duration(UPDATE_TRANS_MS) - .attr('cx', function (d) { return xScale(d.date); }) - .attr('cy', function (d) { return yScale(d.sgv); }) - .attr('fill', function (d) { return d.color; }) - .attr('r', function (d) {return dotRadius(d.type); }) - .attr('opacity', function (d) { return futureOpacity(d.date.getTime() - latestSGV.x); }); + prepareFocusCircles(focusCircles.transition().duration(UPDATE_TRANS_MS)); // if new circle then just display - focusCircles.enter().append('circle') - .attr('cx', function (d) { return xScale(d.date); }) - .attr('cy', function (d) { 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) { - var device = d.device && d.device.toLowerCase(); - return (device == 'dexcom' ? 'white' : '#0099ff'); - }) - .attr('r', function (d) {return dotRadius(d.type); }) + prepareFocusCircles(focusCircles.enter().append('circle')) .on('mouseover', function (d) { if (d.type != 'sgv' && d.type != 'mbg') return; @@ -518,6 +516,17 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 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(scaledTreatmentBG(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; + } + try { //NOTE: treatments with insulin or carbs are drawn by drawTreatment() @@ -529,35 +538,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; })); // if already existing then transition each circle to its new position - treatCircles.transition() - .duration(UPDATE_TRANS_MS) - .attr('cx', function (d) { return xScale(new Date(d.created_at)); }) - .attr('cy', function (d) { return yScale(scaledTreatmentBG(d)); }); + prepareTreatCircles(treatCircles.transition().duration(UPDATE_TRANS_MS)); // if new circle then just display - treatCircles.enter().append('circle') - .attr('cx', function (d) { return xScale(d.created_at); }) - .attr('cy', function (d) { return yScale(scaledTreatmentBG(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'; }) - .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 - 28) + 'px'); - }) - .on('mouseout', function () { - tooltip.transition() - .duration(TOOLTIP_TRANS_MS) - .style('opacity', 0); - }); + 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 - 28) + 'px'); + }) + .on('mouseout', function () { + tooltip.transition() + .duration(TOOLTIP_TRANS_MS) + .style('opacity', 0); + }); treatCircles.attr('clip-path', 'url(#clip)'); } catch (err) { @@ -869,26 +869,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var contextCircles = context.selectAll('circle') .data(data); + function prepareContextCircles(sel) { + sel.attr('cx', function (d) { return xScale2(d.date); }) + .attr('cy', function (d) { 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;}); + + return sel; + } + // if already existing then transition each circle to its new position - contextCircles.transition() - .duration(UPDATE_TRANS_MS) - .attr('cx', function (d) { return xScale2(d.date); }) - .attr('cy', function (d) { 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;}); + prepareContextCircles(contextCircles.transition().duration(UPDATE_TRANS_MS)); // if new circle then just display - contextCircles.enter().append('circle') - .attr('cx', function (d) { return xScale2(d.date); }) - .attr('cy', function (d) { 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;}); + prepareContextCircles(contextCircles.enter().append('circle')); contextCircles.exit() .remove(); From 9b63c934772395890ed2e005e75cf5a7054a8fdd Mon Sep 17 00:00:00 2001 From: dduugg Date: Sat, 24 Jan 2015 11:12:22 -0800 Subject: [PATCH 045/714] allow travis failures in node v0.11 resolves #369 --- .travis.yml | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5c97a51b95e..5a418200331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: node_js node_js: - "0.10" - "0.11" +matrix: + allow_failures: + - node_js: "0.11" services: - mongodb before_script: diff --git a/package.json b/package.json index 65fd5067444..2b93cce27b9 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "postinstall": "node node_modules/bower/bin/bower install" }, "engines": { - "node": ">=0.10 <0.12" + "node": "~0.10.0" }, "dependencies": { "body-parser": "^1.4.3", From 7952bec1ceaf5d9fb0834cc9ab49db2038115db0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 24 Jan 2015 14:02:40 -0800 Subject: [PATCH 046/714] version bump --- bower.json | 2 +- package.json | 2 +- static/index.html | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bower.json b/bower.json index 901035c25a5..7d3756be363 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.6.0", + "version": "0.6.1", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 45cfe9b4892..6c3e87ff360 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.6.0", + "version": "0.6.1", "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", diff --git a/static/index.html b/static/index.html index 0fe5dd347e7..eb787e36f47 100644 --- a/static/index.html +++ b/static/index.html @@ -6,9 +6,9 @@ Nightscout - + - + @@ -220,8 +220,8 @@

Nightscout

- - + + From 7e089518b5d0b0e7fafb48a1c9ce46e905662a12 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 24 Jan 2015 15:37:12 -0800 Subject: [PATCH 047/714] quick stab at contributing doc --- CONTRIBUTING.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..2b742a38c98 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ + +# Contributing to cgm-remote-monitor + +[![Build Status][build-img]][build-url] +[![Dependency Status][dependency-img]][dependency-url] +[![Coverage Status][coverage-img]][coverage-url] +[![Gitter chat][gitter-img]][gitter-url] +[![Stories in Ready][ready-img]][waffle] +[![Stories in Progress][progress-img]][waffle] + +[build-img]: https://img.shields.io/travis/nightscout/cgm-remote-monitor.svg +[build-url]: https://travis-ci.org/nightscout/cgm-remote-monitor +[dependency-img]: https://img.shields.io/david/nightscout/cgm-remote-monitor.svg +[dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor +[coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/coverage.svg +[coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=dev +[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 +[waffle]: https://waffle.io/nightscout/cgm-remote-monitor +[progress-img]: https://badge.waffle.io/nightscout/cgm-remote-monitor.svg?label=in+progress&title=In+Progress + + +## Design + +Participate in the design process by creating an issue to discuss your +design. + +## Develop on `dev` + +We develop on the `dev` branch. +You can get the dev branch checked out using `git checkout dev`. + +## Create a prototype + +Fork cgm-remote-monitor and create a branch. +You can create a branch using `git checkout -b wip/add-my-widget`. +This creates a new branch called `wip/add-my-widget`. The `wip` +stands for work in progress and is a common prefix so that when know +what to expect when reviewing many branches. + +## Submit a pull request + +When you are done working with your prototype, it can be tempting to +post on popular channels such as Facebook. We encourage contributors +to submit their code for review, debate, and release before announcing +features on social media. + +This can be done by checking your code `git commit -avm 'my +improvements are here'`, the the branch you created back to your own +fork. This will probably look something like +`git push -u origin wip/add-my-widget`. + +Now that the commits are available on github, you can click on the +compare buttons on your fork to create a pull request. Make sure to +select [Nightscout's `dev` branch](https://github.com/nightscout/cgm-remote-monitor/tree/dev). + +## Comments and issues + +We encourage liberal use of the comments, including imgages where +appropriate. + +## Co-ordination + +There is google groups nightscout-core developers list where lots of +people discuss Nightscout. Most cgm-remote-monitor hackers use +github's ticketing system, along with Facebook cgm-in-the-cloud, and +gitter system. + +We use git-flow, with `master` as our production, stable branch, and +`dev` is used to queue up for upcoming releases. Everything else is +done on branches, hopefully with names that indicate what to expect. + +Once `dev` has been reviewed and people feel it's time to release, we +follow the git-flow release process, which creates a new tag and bumps +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. + From 6fd72049debdfad4fd0d4e6ff0e044d6bd975e6a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 26 Jan 2015 17:18:54 -0800 Subject: [PATCH 048/714] fixed minor typos --- CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b742a38c98..dbac299d8e1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,7 +47,7 @@ to submit their code for review, debate, and release before announcing features on social media. This can be done by checking your code `git commit -avm 'my -improvements are here'`, the the branch you created back to your own +improvements are here'`, the branch you created back to your own fork. This will probably look something like `git push -u origin wip/add-my-widget`. @@ -57,12 +57,12 @@ select [Nightscout's `dev` branch](https://github.com/nightscout/cgm-remote-moni ## Comments and issues -We encourage liberal use of the comments, including imgages where +We encourage liberal use of the comments, including images where appropriate. ## Co-ordination -There is google groups nightscout-core developers list where lots of +There is a google groups nightscout-core developers list where lots of people discuss Nightscout. Most cgm-remote-monitor hackers use github's ticketing system, along with Facebook cgm-in-the-cloud, and gitter system. @@ -78,4 +78,3 @@ 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. - From 10ea182315295957614841e3f0c51a34ab7f806e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 27 Jan 2015 20:11:58 -0800 Subject: [PATCH 049/714] fixed coverage badge --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 265c14c17fe..e402869fd33 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Community maintained fork of the [build-url]: https://travis-ci.org/nightscout/cgm-remote-monitor [dependency-img]: https://img.shields.io/david/nightscout/cgm-remote-monitor.svg [dependency-url]: https://david-dm.org/nightscout/cgm-remote-monitor -[coverage-img]: https://img.shields.io/coveralls/nightscout/cgm-remote-monitor/coverage.svg -[coverage-url]: https://coveralls.io/r/nightscout/cgm-remote-monitor?branch=coverage +[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 [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 327c302851f4b73c643857b9c52abab0cba4593a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 27 Jan 2015 22:48:06 -0800 Subject: [PATCH 050/714] fix for nightmode --- static/js/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/client.js b/static/js/client.js index db83a7cdd96..8cd03eab2a0 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1188,6 +1188,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)) { $('body').css({ 'opacity': opacity.NIGHT }); } else { From 0eef982f78d6669ca47f77ede3492e14edf3a899 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 27 Jan 2015 23:53:50 -0800 Subject: [PATCH 051/714] version bump --- bower.json | 2 +- package.json | 2 +- static/index.html | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bower.json b/bower.json index 7d3756be363..406470963b2 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.6.1", + "version": "0.6.2", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index 6c3e87ff360..efa813acd62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.6.1", + "version": "0.6.2", "description": "Nightscout acts as a web-based CGM (Continuous Glucose Montinor) to allow multiple caregivers to remotely view a patients glucose data in realtime.", "license": "AGPL3", "author": "Nightscout Team", diff --git a/static/index.html b/static/index.html index eb787e36f47..934fdf1ff11 100644 --- a/static/index.html +++ b/static/index.html @@ -6,9 +6,9 @@ Nightscout - + - + @@ -220,8 +220,8 @@

Nightscout

- - + + From e7440425ac0d148215ea941146c3950e50625592 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 21:42:46 -0800 Subject: [PATCH 052/714] initial port from iob-cob branch --- lib/websocket.js | 26 ++++++++++++++++++++++--- static/js/client.js | 47 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index c816db8625e..b3fa785da58 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -28,6 +28,7 @@ var dir2Char = { var cgmData = [], treatmentData = [], mbgData = [], + calData = [], patientData = []; function start ( ) { @@ -164,7 +165,18 @@ function update() { obj.d = element.dateString; obj.device = element.device; obj.direction = directionToChar(element.direction); + obj.filtered = element.filtered; + obj.unfiltered = element.unfiltered; + obj.rssi = element.rssi; cgmData.push(obj); + } else if (element.slope) { + var obj = {}; + obj.x = element.date; + obj.d = element.dateString; + obj.scale = element.scale; + obj.intercept = element.intercept; + obj.slope = element.slope; + calData.push(obj); } } }); @@ -191,6 +203,7 @@ function loadData() { actualCurrent, treatment = [], mbg = [], + cal = [], errorCode; if (cgmData) { @@ -204,7 +217,7 @@ function loadData() { // sgv less than or equal to 10 means error code // or warm up period code, so ignore actual = actual.filter(function (a) { - return a.y > 10; + return (a.y > 10 || a.unfiltered > 0); }) } @@ -222,6 +235,13 @@ function loadData() { }); } + if (calData) { + cal = calData.slice(calData.length-200, calData.length); + cal.sort(function(a, b) { + return a.x - b.x; + }); + } + if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent; var actualLength = actual.length - 1; @@ -255,8 +275,8 @@ function loadData() { //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, errorCode); - patientData = [actual, predicted, mbg, treatment, errorCode]; + var shouldEmit = is_different(actual, predicted, mbg, treatment, errorCode, cal); + patientData = [actual, predicted, mbg, treatment, errorCode, cal]; console.log('patientData', patientData.length); if (shouldEmit) { emitData( ); diff --git a/static/js/client.js b/static/js/client.js index 8cd03eab2a0..9b4bee6b76a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -30,6 +30,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , prevSGV , errorCode , treatments + , cal , padding = { top: 20, right: 10, bottom: 30, left: 10 } , opacity = {current: 1, DAY: 1, NIGHT: 0.5} , now = Date.now() @@ -95,7 +96,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; ]); - // lixgbg: Convert mg/dL BG value to metric mmol + // lixgbg: Convert mg/dL BG value to metric mmol function scaleBg(bg) { if (browserSettings.units == 'mmol') { return (Math.round((bg / 18) * 10) / 10).toFixed(1); @@ -103,6 +104,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return bg; } } + + function rawIsigToRawBg(unfiltered, scale, intercept, slope, filtered, sgv) { + if (slope == 0 || unfiltered == 0 || scale ==0 || slope == null || unfiltered == null || scale == null) return 0; + else if (filtered == 0 || filtered == null || sgv < 30 || sgv == null) { + console.info("Skipping ratio adjustment for SGV " + sgv); + return scale*(unfiltered-intercept)/slope; + } + else { + var ratio = scale*(filtered-intercept)/slope / sgv; + return scale*(unfiltered-intercept)/slope / ratio; + } + } + // initial setup of chart when data is first made available function initializeCharts() { @@ -417,6 +431,7 @@ 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); return radius; }; @@ -1309,9 +1324,32 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; latestSGV = d[0][d[0].length - 1]; prevSGV = d[0][d[0].length - 2]; } - data = d[0].map(function (obj) { + + treatments = d[3]; + treatments.forEach(function (d) { + d.created_at = new Date(d.created_at); + }); + + cal = d[5][d[5].length-1]; + + var temp1 = [ ]; + if (cal) { + temp1 = d[0].map(function (obj) { + var rawBg = rawIsigToRawBg(obj.unfiltered + , cal.scale || [ ] + , cal.intercept + , cal.slope || [ ] + , obj.filtered + , obj.y); + return { date: new Date(obj.x-2*1000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg'} + }); + } + 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'} }); + 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 @@ -1327,11 +1365,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; d.color = 'transparent'; }); - treatments = d[3]; - treatments.forEach(function (d) { - d.created_at = new Date(d.created_at); - }); - if (!isInitialData) { isInitialData = true; initializeCharts(); From ccd917aee58ae473bf2bd0c89523995507349e96 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 22:56:20 -0800 Subject: [PATCH 053/714] only show raw bg if enabled --- lib/api/index.js | 1 + lib/api/status.js | 1 + static/index.html | 4 ++++ static/js/client.js | 4 +++- static/js/ui-utils.js | 13 ++++++++++++- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/api/index.js b/lib/api/index.js index 9325bd124ec..4c9f215f706 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -24,6 +24,7 @@ function create (env, entries, settings, treatments, devicestatus) { } if (env.enable) { + app.enabledOptions = env.enable; 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 76d4de06f39..d1c038ad2d5 100644 --- a/lib/api/status.js +++ b/lib/api/status.js @@ -13,6 +13,7 @@ function configure (app, wares) { var info = { status: 'ok' , apiEnabled: app.enabled('api') , careportalEnabled: app.enabled('api') && app.enabled('careportal') + , enabledOptions: app.enabledOptions , units: app.get('units') , head: wares.get_head( ) , version: app.get('version') diff --git a/static/index.html b/static/index.html index 934fdf1ff11..6a07f4fcfde 100644 --- a/static/index.html +++ b/static/index.html @@ -85,6 +85,10 @@

Nightscout

Night Mode
+
+
Show Raw BG Data
+
+
Custom Title
diff --git a/static/js/client.js b/static/js/client.js index 9b4bee6b76a..51be8d570c9 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1333,7 +1333,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; cal = d[5][d[5].length-1]; var temp1 = [ ]; - if (cal) { + if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg == true) { + console.info(">>>>browserSettings.showRawbg", browserSettings.showRawbg); temp1 = d[0].map(function (obj) { var rawBg = rawIsigToRawBg(obj.unfiltered , cal.scale || [ ] @@ -1446,6 +1447,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , version: xhr.version , head: xhr.head , apiEnabled: xhr.apiEnabled + , enabledOptions: xhr.enabledOptions , thresholds: xhr.thresholds , alarm_types: xhr.alarm_types , units: xhr.units diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index bdb15c0d427..e4d0f68e812 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -36,6 +36,7 @@ function getBrowserSettings(storage) { "alarmLow": storage.get("alarmLow"), "alarmUrgentLow": storage.get("alarmUrgentLow"), "nightMode": storage.get("nightMode"), + "showRawbg": storage.get("showRawbg") "customTitle": storage.get("customTitle"), "theme": storage.get("theme"), "timeFormat": storage.get("timeFormat") @@ -61,6 +62,15 @@ function getBrowserSettings(storage) { json.nightMode = setDefault(json.nightMode, defaultSettings.nightMode); $("#nightmode-browser").prop("checked", json.nightMode); + if (app.enabledOptions.indexOf('rawbg') == -1) { + json.showRawbg = false; + $("#show-rawbg-option").hide(); + } else { + $("#show-rawbg-option").show(); + json.showRawbg = setDefault(json.showRawbg, (app.enabledOptions.indexOf('rawbg-on') > -1)); + $("#show-rawbg").prop("checked", json.showRawbg); + } + if (json.customTitle) { $("h1.customTitle").text(json.customTitle); $("input#customTitle").prop("value", json.customTitle); @@ -72,7 +82,7 @@ function getBrowserSettings(storage) { } else { $("#theme-default-browser").prop("checked", true); } - + json.timeFormat = setDefault(json.timeFormat, defaultSettings.timeFormat); if (json.timeFormat == "24") { @@ -348,6 +358,7 @@ $("input#save").click(function(event) { "alarmLow": $("#alarm-low-browser").prop("checked"), "alarmUrgentLow": $("#alarm-urgentlow-browser").prop("checked"), "nightMode": $("#nightmode-browser").prop("checked"), + "showRawbg": $("#show-rawbg").prop("checked"), "customTitle": $("input#customTitle").prop("value"), "theme": $("input:radio[name=theme-browser]:checked").val(), "timeFormat": $("input:radio[name=timeformat-browser]:checked").val() From beba8b037910569c2be22714059accee3c1e188b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 22:59:52 -0800 Subject: [PATCH 054/714] fixed typo --- static/js/ui-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index e4d0f68e812..c5896ce18bf 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -36,7 +36,7 @@ function getBrowserSettings(storage) { "alarmLow": storage.get("alarmLow"), "alarmUrgentLow": storage.get("alarmUrgentLow"), "nightMode": storage.get("nightMode"), - "showRawbg": storage.get("showRawbg") + "showRawbg": storage.get("showRawbg"), "customTitle": storage.get("customTitle"), "theme": storage.get("theme"), "timeFormat": storage.get("timeFormat") From 30f56f5cc9c2bc3d504b162aec770d6822fa7cc1 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 23:04:03 -0800 Subject: [PATCH 055/714] added new enable option to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e402869fd33..d51ccefedfb 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. #### Features/Labs - * `ENABLE` - Used to enable optional features, currently supports: `careportal` + * `ENABLE` - Used to enable optional features, currently supports: `careportal`, `rawbg` (also `rawbg-on` to auto show raw) * `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 From 3daa7d985776a32bab7e82768854bffbf49dba9f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 23:24:53 -0800 Subject: [PATCH 056/714] inital support for send raw bg data with /pebble --- lib/pebble.js | 53 ++++++++++++++++++++++++++++++++------------------- server.js | 2 +- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 8299623466d..298b171578d 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -20,8 +20,7 @@ function directionToTrend (direction) { } function pebble (req, res) { - var FORTY_MINUTES = 2400000; - var cgmData = [ ]; + var ONE_DAY = 24 * 60 * 60 * 1000; var uploaderBattery; function requestMetric() { @@ -42,6 +41,9 @@ function pebble (req, res) { function get_latest (err, results) { var now = Date.now(); + var sgvData = [ ]; + var calData = [ ]; + results.forEach(function(element, index, array) { var next = null; if (index + 1 < results.length) { @@ -49,25 +51,33 @@ function pebble (req, res) { } if (element) { var obj = {}; - if (!element.sgv) return; - obj.sgv = scaleBg(element.sgv).toString( ); - obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); - if (useMetricBg) { - obj.bgdelta = obj.bgdelta.toFixed(1); - } - if ('direction' in element) { - obj.trend = directionToTrend(element.direction); - obj.direction = element.direction; + if (element.sgv) { + obj.sgv = scaleBg(element.sgv).toString(); + obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); + if (useMetricBg) { + obj.bgdelta = obj.bgdelta.toFixed(1); + } + if ('direction' in element) { + obj.trend = directionToTrend(element.direction); + obj.direction = element.direction; + } + // obj.y = element.sgv; + // obj.x = element.date; + obj.datetime = element.date; + obj.battery = uploaderBattery ? "" + uploaderBattery : undefined; + if (req.rawbg) { + obj.filtered = element.filtered; + obj.unfiltered = element.unfiltered; + obj.rssi = element.rssi; + } + // obj.date = element.date.toString( ); + sgvData.push(obj); + } else if (req.rawbg && element.type == 'cal') { + calData.push(element); } - // obj.y = element.sgv; - // obj.x = element.date; - obj.datetime = element.date; - obj.battery = uploaderBattery ? "" + uploaderBattery : undefined; - // obj.date = element.date.toString( ); - cgmData.push(obj); } }); - var result = { status: [ {now:now}], bgs: cgmData.slice(0, 1) }; + var result = { status: [ {now:now}], bgs: sgvData.slice(0, 1), cals: calData.slice(0, 1) }; res.setHeader('content-type', 'application/json'); res.write(JSON.stringify(result)); res.end( ); @@ -80,13 +90,16 @@ function pebble (req, res) { console.error("req.devicestatus.tail", err); } - req.entries.list({count: 2, find: { "sgv": { $exists: true }}}, get_latest); + var earliest_data = Date.now() - ONE_DAY; + var q = { find: {"date": {"$gte": earliest_data}} }; + req.entries.list(q, get_latest); }); } -function configure (entries, devicestatus) { +function configure (entries, devicestatus, env) { function middle (req, res, next) { req.entries = entries; req.devicestatus = devicestatus; + req.rawbg = env.enable.indexOf('rawbg') > -1; next( ); } return [middle, pebble]; diff --git a/server.js b/server.js index 660540c3747..b1a5dd8b244 100644 --- a/server.js +++ b/server.js @@ -71,7 +71,7 @@ app.enable('trust proxy'); // Allows req.secure test on heroku https connections app.use('/api/v1', api); // pebble data -app.get('/pebble', pebble(entriesStorage, devicestatusStorage)); +app.get('/pebble', pebble(entriesStorage, devicestatusStorage, env)); //app.get('/package.json', software); From b267ee86e33df55211053da70875d3c10f43f10f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 29 Jan 2015 23:59:31 -0800 Subject: [PATCH 057/714] fixed retro error code bug that showed LOW incorrectly --- static/js/client.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 51be8d570c9..9c8f9c21738 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -284,7 +284,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var prevfocusPoint = nowData[nowData.length - 2]; //in this case the SGV is scaled - if (focusPoint.y < 40) { + if (focusPoint.y < 39) { + $('.container .currentBG').text(''); + } else if (focusPoint.y == 39) { $('.container .currentBG').text('LOW'); } else if (focusPoint.y > 400) { $('.container .currentBG').text('HIGH'); From f2509cc82b82f4b86bfb99d2f7e961a81d281cca Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 30 Jan 2015 17:44:50 -0800 Subject: [PATCH 058/714] added noise data when rawbg is enabled --- lib/pebble.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pebble.js b/lib/pebble.js index 298b171578d..f511d58a5cf 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -68,6 +68,7 @@ function pebble (req, res) { if (req.rawbg) { obj.filtered = element.filtered; obj.unfiltered = element.unfiltered; + obj.noise = element.noise; obj.rssi = element.rssi; } // obj.date = element.date.toString( ); From 0828424fa681d8e17f8305353ad8abb1cd2b120f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 30 Jan 2015 17:45:35 -0800 Subject: [PATCH 059/714] refactoring how error codes are sent and displayed --- lib/websocket.js | 16 ++---- static/js/client.js | 126 +++++++++++++++++++++----------------------- 2 files changed, 64 insertions(+), 78 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index b3fa785da58..bba9704c6b6 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -213,12 +213,6 @@ function loadData() { }); actualCurrent = actual.length > 0 ? actual[actual.length - 1].y : null; - - // sgv less than or equal to 10 means error code - // or warm up period code, so ignore - actual = actual.filter(function (a) { - return (a.y > 10 || a.unfiltered > 0); - }) } if (treatmentData) { @@ -275,8 +269,8 @@ function loadData() { //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, errorCode, cal); - patientData = [actual, predicted, mbg, treatment, errorCode, cal]; + var shouldEmit = is_different(actual, predicted, mbg, treatment, cal); + patientData = [actual, predicted, mbg, treatment, cal]; console.log('patientData', patientData.length); if (shouldEmit) { emitData( ); @@ -331,7 +325,7 @@ function loadData() { } } - function is_different (actual, predicted, mbg, treatment, errorCode) { + function is_different (actual, predicted, mbg, treatment, cal) { if (patientData && patientData.length < 3) { return true; } @@ -340,14 +334,14 @@ function loadData() { , predicted: patientData[1].slice(-1).pop( ) , mbg: patientData[2].slice(-1).pop( ) , treatment: patientData[3].slice(-1).pop( ) - , errorCode: patientData.length >= 5 ? patientData[4] : 0 + , cal: patientData[4].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( ) - , errorCode: errorCode + , cal: cal.slice(-1).pop( ) }; // textual diff of objects diff --git a/static/js/client.js b/static/js/client.js index 9c8f9c21738..e85571443d1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -28,7 +28,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , latestSGV , latestUpdateTime , prevSGV - , errorCode , treatments , cal , padding = { top: 20, right: 10, bottom: 30, left: 10 } @@ -215,6 +214,27 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return brushExtent[1].getTime() - THIRTY_MINS_IN_MS < now && elementHidden != true; } + 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 = '⌛'; break; //ABSOLUTE_DEVIATION + case 10: errorDisplay = '???'; break; //POWER_DEVIATION + case 12: errorDisplay = '?RF'; break; //BAD_RF + default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; + } + + return errorDisplay; + } + // function to call when context chart is brushed function brushed(skipTimer) { @@ -285,8 +305,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //in this case the SGV is scaled if (focusPoint.y < 39) { - $('.container .currentBG').text(''); - } else if (focusPoint.y == 39) { + $('.container .currentBG').html(errorCodeToDisplay(focusPoint.y)); + } else if (focusPoint.y < 40) { $('.container .currentBG').text('LOW'); } else if (focusPoint.y > 400) { $('.container .currentBG').text('HIGH'); @@ -314,7 +334,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('.container .currentDelta') .text(retroDeltaString) .css('text-decoration','line-through'); - $('.container .currentDirection').html(focusPoint.direction) + + if (focusPoint.y < 39) { + $('.container .currentDirection').html('✖'); + } else { + $('.container .currentDirection').html(focusPoint.direction) + } } else { $('.container .currentBG') .text('---') @@ -346,38 +371,33 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClockDisplay(); - if (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 = '⌛'; break; //ABSOLUTE_DEVIATION - case 10: errorDisplay = '???'; break; //POWER_DEVIATION - case 12: errorDisplay = '?RF'; break; //BAD_RF - default: errorDisplay = '?' + parseInt(errorCode) + '?'; break; - } + var bgDelta = scaleBg(latestSGV.y) - scaleBg(prevSGV.y); + if (browserSettings.units == 'mmol') { + bgDelta = bgDelta.toFixed(1); + } - $('#lastEntry').text('CGM ERROR').removeClass('current').addClass('urgent'); + var bgDeltaString = bgDelta; + if (bgDelta >= 0) { + bgDeltaString = '+' + bgDelta; + } - $('.container .currentBG').html(errorDisplay) - .css('text-decoration', ''); - $('.container .currentDelta').text('') - .css('text-decoration',''); - $('.container .currentDirection').html('✖'); + if (browserSettings.units == 'mmol') { + bgDeltaString = bgDeltaString + ' mmol/L' + } else { + bgDeltaString = bgDeltaString + ' mg/dL' + } - var color = sgvToColor(errorCode); - $('.container #noButton .currentBG').css({color: color}); - $('.container #noButton .currentDirection').css({color: color}); + $('.container .currentBG').css('text-decoration', ''); - } else { + if (latestSGV.y < 39) { + bgDeltaString = ""; + + $('#lastEntry').text('CGM ERROR').removeClass('current').addClass('urgent'); + $('.container .currentBG').html(errorCodeToDisplay(latestSGV.y)); + $('.container .currentDelta').text('').css('text-decoration',''); + $('.container .currentDirection').html('✖'); + } else { updateTimeAgo(); //in this case the SGV is unscaled if (latestSGV.y < 40) { @@ -387,41 +407,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } else { $('.container .currentBG').text(scaleBg(latestSGV.y)); } - - var bgDelta = scaleBg(latestSGV.y) - scaleBg(prevSGV.y); - if (browserSettings.units == 'mmol') { - bgDelta = bgDelta.toFixed(1); - } - - var bgDeltaString = bgDelta; - if (bgDelta >= 0) { - bgDeltaString = '+' + bgDelta; - } - - if (browserSettings.units == 'mmol') { - bgDeltaString = bgDeltaString + ' mmol/L' - } else { - bgDeltaString = bgDeltaString + ' mg/dL' - } - - $('.container .currentBG').css('text-decoration', ''); - $('.container .currentDelta') - .text(bgDeltaString) - .css('text-decoration',''); $('.container .currentDirection').html(latestSGV.direction); + } - var color = sgvToColor(latestSGV.y); - $('.container #noButton .currentBG').css({color: color}); - $('.container #noButton .currentDirection').css({color: color}); - - // bgDelta and retroDelta to follow sgv color - // instead of Scott Leibrand's wip/iob-cob settings below + $('.container .currentDelta').text(bgDeltaString).css('text-decoration',''); - // var deltaColor = deltaToColor(bgDelta); - // $('.container #noButton .currentDelta').css({color: deltaColor}); + var color = sgvToColor(latestSGV.y); + $('.container #noButton .currentBG').css({color: color}); + $('.container #noButton .currentDirection').css({color: color}); - $('.container #noButton .currentDelta').css({color: color}); - } + $('.container #noButton .currentDelta').css({color: color}); } xScale.domain(brush.extent()); @@ -1318,8 +1313,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; socket.on('sgv', function (d) { if (d.length > 1) { - errorCode = d.length >= 5 ? d[4] : undefined; - // 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(); @@ -1332,11 +1325,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; d.created_at = new Date(d.created_at); }); - cal = d[5][d[5].length-1]; + cal = d[4][d[4].length-1]; var temp1 = [ ]; if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg == true) { - console.info(">>>>browserSettings.showRawbg", browserSettings.showRawbg); temp1 = d[0].map(function (obj) { var rawBg = rawIsigToRawBg(obj.unfiltered , cal.scale || [ ] @@ -1394,7 +1386,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; //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 !!errorCode || latestSGV.y <= app.thresholds.bg_target_top; + return latestSGV.y <= app.thresholds.bg_target_top; } socket.on('alarm', function () { From 60ef5bd0f0bf343a850bfb014d405d6456f0ef43 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 30 Jan 2015 18:13:44 -0800 Subject: [PATCH 060/714] fixed bg delta bug, and added support for optional count param that can be used to get more data --- lib/pebble.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index f511d58a5cf..641c1587ef6 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -45,13 +45,16 @@ function pebble (req, res) { var calData = [ ]; results.forEach(function(element, index, array) { - var next = null; - if (index + 1 < results.length) { - next = results[index + 1]; - } if (element) { var obj = {}; if (element.sgv) { + var next = null; + var sgvs = results.filter(function(d) { + return !!d.sgv; + }); + if (index + 1 < sgvs.length) { + next = sgvs[index + 1]; + } obj.sgv = scaleBg(element.sgv).toString(); obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); if (useMetricBg) { @@ -61,10 +64,7 @@ function pebble (req, res) { obj.trend = directionToTrend(element.direction); obj.direction = element.direction; } - // obj.y = element.sgv; - // obj.x = element.date; obj.datetime = element.date; - obj.battery = uploaderBattery ? "" + uploaderBattery : undefined; if (req.rawbg) { obj.filtered = element.filtered; obj.unfiltered = element.unfiltered; @@ -78,7 +78,14 @@ function pebble (req, res) { } } }); - var result = { status: [ {now:now}], bgs: sgvData.slice(0, 1), cals: calData.slice(0, 1) }; + + var count = parseInt(req.query.count) || 1; + + var bgs = sgvData.slice(0, count); + //for compatibility we're keeping battery here, but it would be better somewhere else + bgs[0].battery = uploaderBattery ? "" + uploaderBattery : undefined; + + var result = { status: [ {now:now}], bgs: bgs, cals: calData.slice(0, count) }; res.setHeader('content-type', 'application/json'); res.write(JSON.stringify(result)); res.end( ); From 1c723b364e89aed629c6382f708fd1b5e7a25943 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 31 Jan 2015 09:49:47 -0800 Subject: [PATCH 061/714] simple test to make sure the /status.json endpoint handles the enable options correctly --- tests/api.status.test.js | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/api.status.test.js diff --git a/tests/api.status.test.js b/tests/api.status.test.js new file mode 100644 index 00000000000..f6a811d0f54 --- /dev/null +++ b/tests/api.status.test.js @@ -0,0 +1,42 @@ + +var request = require('supertest'); +var 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.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; + store(function ( ) { + var entriesStorage = require('../lib/entries').storage(env.mongo_collection, store); + self.app.use('/api', api(env, entriesStorage)); + done(); + }); + }); + + it('should be a module', function ( ) { + api.should.be.ok; + + }); + + it('/status.json', function (done) { + request(this.app) + .get('/api/status.json') + .expect(200) + .end(function (err, res) { + res.body.apiEnabled.should.equal(true); + res.body.careportalEnabled.should.equal(true); + res.body.enabledOptions.should.equal('careportal rawbg'); + done( ); + }); + }); + + +}); + From 9c1018490a69e4af5b7dffae8a598fad1bc296bf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 31 Jan 2015 10:42:41 -0800 Subject: [PATCH 062/714] clean up --- tests/api.status.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api.status.test.js b/tests/api.status.test.js index f6a811d0f54..dc6cfbd9f4b 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -22,7 +22,6 @@ describe('Status REST api', function ( ) { it('should be a module', function ( ) { api.should.be.ok; - }); it('/status.json', function (done) { From a689039fed5bf9146a20f6b6fb769dcd24c8e5d0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 31 Jan 2015 10:43:03 -0800 Subject: [PATCH 063/714] tests for the /pebble endpoint --- tests/pebble.test.js | 168 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 tests/pebble.test.js diff --git a/tests/pebble.test.js b/tests/pebble.test.js new file mode 100644 index 00000000000..ea0f9c19e0f --- /dev/null +++ b/tests/pebble.test.js @@ -0,0 +1,168 @@ + +var request = require('supertest'); +var should = require('should'); + +//Mock entries +var entries = { + list: function(q, callback) { + var results = [ + { 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: 1422647711000, + dateString: 'Fri Jan 30 11:55:11 PST 2015', + slope: 895.8571693029189, + intercept: 34281.06876195567, + scale: 1, + type: 'cal' + }, + { 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 + } + ]; + callback(null, results); + } +}; + +//Mock devicestatus +var devicestatus = { + last: function(callback) { + callback(null, {uploaderBattery: 100}); + } +}; + +describe('Pebble Endpoint without Raw', function ( ) { + var pebble = require('../lib/pebble'); + before(function (done) { + var env = require('../env')( ); + env.enable = ""; + this.app = require('express')( ); + this.app.enable('api'); + this.app.use('/pebble', pebble(entries, devicestatus, env)); + done(); + }); + + it('should be a module', function ( ) { + pebble.should.be.ok; + }); + + it('/pebble', function (done) { + request(this.app) + .get('/pebble?count=2') + .expect(200) + .end(function (err, res) { + 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.trend.should.equal(4); + bg.direction.should.equal('Flat'); + bg.datetime.should.equal(1422727301000); + (bg.filtered == null).should.be.true; + (bg.unfiltered == null).should.be.true; + (bg.noise == null).should.be.true; + (bg.rssi == null).should.be.true; + bg.battery.should.equal('100'); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); +}); + + +describe('Pebble Endpoint with Raw', function ( ) { + var pebbleRaw = require('../lib/pebble'); + before(function (done) { + var envRaw = require('../env')( ); + envRaw.enable = "rawbg"; + this.appRaw = require('express')( ); + this.appRaw.enable('api'); + this.appRaw.use('/pebble', pebbleRaw(entries, devicestatus, envRaw)); + done(); + }); + + it('should be a module', function ( ) { + pebbleRaw.should.be.ok; + }); + + it('/pebble', function (done) { + request(this.appRaw) + .get('/pebble?count=2') + .expect(200) + .end(function (err, res) { + 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.trend.should.equal(4); + bg.direction.should.equal('Flat'); + bg.datetime.should.equal(1422727301000); + bg.filtered.should.equal(113984); + bg.unfiltered.should.equal(111920); + bg.noise.should.equal(1); + bg.rssi.should.equal(179); + bg.battery.should.equal('100'); + + res.body.cals.length.should.equal(1); + var cal = res.body.cals[0]; + cal.slope.should.equal(895.8571693029189); + cal.intercept.should.equal(34281.06876195567); + cal.scale.should.equal(1); + done( ); + }); + }); + +}); \ No newline at end of file From 30ee5f79f71b0a4e1c761e7352b3323108068914 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 31 Jan 2015 11:20:12 -0800 Subject: [PATCH 064/714] more status tests --- tests/api.status.test.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/api.status.test.js b/tests/api.status.test.js index dc6cfbd9f4b..c668bba9f2d 100644 --- a/tests/api.status.test.js +++ b/tests/api.status.test.js @@ -36,6 +36,37 @@ describe('Status REST api', function ( ) { }); }); + it('/status.html', function (done) { + request(this.app) + .get('/api/status.html') + .end(function(err, res) { + res.type.should.equal('text/html'); + res.statusCode.should.equal(200); + done() + }); + }); + + it('/status.js', function (done) { + request(this.app) + .get('/api/status.js') + .end(function(err, res) { + res.type.should.equal('application/javascript'); + res.statusCode.should.equal(200); + res.text.should.startWith('this.serverSettings ='); + done() + }); + }); + + it('/status.png', function (done) { + request(this.app) + .get('/api/status.png') + .end(function(err, res) { + res.headers.location.should.equal('http://img.shields.io/badge/Nightscout-OK-green.png'); + res.statusCode.should.equal(302); + done() + }); + }); + }); From 333125039f8cd28901e37ed625676a628a577211 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 31 Jan 2015 15:11:44 -0800 Subject: [PATCH 065/714] use cleaner should.not.exist syntax --- tests/pebble.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/pebble.test.js b/tests/pebble.test.js index ea0f9c19e0f..d86aa3b68f9 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -109,10 +109,10 @@ describe('Pebble Endpoint without Raw', function ( ) { bg.trend.should.equal(4); bg.direction.should.equal('Flat'); bg.datetime.should.equal(1422727301000); - (bg.filtered == null).should.be.true; - (bg.unfiltered == null).should.be.true; - (bg.noise == null).should.be.true; - (bg.rssi == null).should.be.true; + should.not.exist(bg.filtered); + should.not.exist(bg.unfiltered); + should.not.exist(bg.noise); + should.not.exist(bg.rssi); bg.battery.should.equal('100'); res.body.cals.length.should.equal(0); From 42d1636c377db549bf6630c422dba1c9c5922850 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 1 Feb 2015 19:25:58 -0800 Subject: [PATCH 066/714] support for new 3-way raw options; lots of reformatting to match client.js --- static/js/ui-utils.js | 481 +++++++++++++++++++++--------------------- 1 file changed, 244 insertions(+), 237 deletions(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index c5896ce18bf..594dc99b730 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -1,23 +1,23 @@ -"use strict"; +'use strict'; var drawerIsOpen = false; var treatmentDrawerIsOpen = false; var defaultSettings = { - "units": "mg/dl", - "alarmUrgentHigh": true, - "alarmHigh": true, - "alarmLow": true, - "alarmUrgentLow": true, - "nightMode": false, - "theme": "default", - "timeFormat": "12" + 'units': 'mg/dl', + 'alarmUrgentHigh': true, + 'alarmHigh': true, + 'alarmLow': true, + 'alarmUrgentLow': true, + 'nightMode': false, + 'theme': 'default', + 'timeFormat': '12' }; function getBrowserSettings(storage) { - var json = {}; + var json = {}; function scaleBg(bg) { - if (json.units == "mmol") { + if (json.units == 'mmol') { return (Math.round((bg / 18) * 10) / 10).toFixed(1); } else { return bg; @@ -29,81 +29,86 @@ function getBrowserSettings(storage) { } try { - var json = { - "units": storage.get("units"), - "alarmUrgentHigh": storage.get("alarmUrgentHigh"), - "alarmHigh": storage.get("alarmHigh"), - "alarmLow": storage.get("alarmLow"), - "alarmUrgentLow": storage.get("alarmUrgentLow"), - "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); - } + var json = { + 'units': storage.get('units'), + 'alarmUrgentHigh': storage.get('alarmUrgentHigh'), + 'alarmHigh': storage.get('alarmHigh'), + 'alarmLow': storage.get('alarmLow'), + 'alarmUrgentLow': storage.get('alarmUrgentLow'), + '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, defaultSettings.alarmUrgentHigh); - json.alarmHigh = setDefault(json.alarmHigh, defaultSettings.alarmHigh); - json.alarmLow = setDefault(json.alarmLow, defaultSettings.alarmLow); - json.alarmUrgentLow = setDefault(json.alarmUrgentLow, defaultSettings.alarmUrgentLow); - $("#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.alarmHigh = setDefault(json.alarmHigh, defaultSettings.alarmHigh); + json.alarmLow = setDefault(json.alarmLow, defaultSettings.alarmLow); + json.alarmUrgentLow = setDefault(json.alarmUrgentLow, defaultSettings.alarmUrgentLow); + $('#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.nightMode = setDefault(json.nightMode, defaultSettings.nightMode); - $("#nightmode-browser").prop("checked", json.nightMode); + json.nightMode = setDefault(json.nightMode, defaultSettings.nightMode); + $('#nightmode-browser').prop('checked', json.nightMode); if (app.enabledOptions.indexOf('rawbg') == -1) { json.showRawbg = false; - $("#show-rawbg-option").hide(); + $('#show-rawbg-option').hide(); } else { - $("#show-rawbg-option").show(); - json.showRawbg = setDefault(json.showRawbg, (app.enabledOptions.indexOf('rawbg-on') > -1)); - $("#show-rawbg").prop("checked", json.showRawbg); + $('#show-rawbg-option').show(); + if (json.showRawbg === false) { + json.showRawbg = 'never'; + } else if (json.showRawbg === true) { + json.showRawbg = 'noise'; + } + json.showRawbg = setDefault(json.showRawbg, (app.enabledOptions.indexOf('rawbg-on') > -1 ? 'noise' : 'never')); + $('#show-rawbg-' + json.showRawbg).prop('checked', true); } - if (json.customTitle) { - $("h1.customTitle").text(json.customTitle); - $("input#customTitle").prop("value", json.customTitle); - document.title = "Nightscout: " + json.customTitle; - } + if (json.customTitle) { + $('h1.customTitle').text(json.customTitle); + $('input#customTitle').prop('value', json.customTitle); + document.title = 'Nightscout: ' + json.customTitle; + } - if (json.theme == "colors") { - $("#theme-colors-browser").prop("checked", true); + if (json.theme == 'colors') { + $('#theme-colors-browser').prop('checked', true); } else { - $("#theme-default-browser").prop("checked", true); + $('#theme-default-browser').prop('checked', true); } - json.timeFormat = setDefault(json.timeFormat, defaultSettings.timeFormat); - - if (json.timeFormat == "24") { - $("#24-browser").prop("checked", true); - } else { - $("#12-browser").prop("checked", true); - } - } - catch(err) { + json.timeFormat = setDefault(json.timeFormat, defaultSettings.timeFormat); + + if (json.timeFormat == '24') { + $('#24-browser').prop('checked', true); + } else { + $('#12-browser').prop('checked', true); + } + } + catch(err) { console.error(err); - showLocalstorageError(); - } + showLocalstorageError(); + } - return json; + 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) { @@ -117,125 +122,127 @@ function storeInBrowser(data) { } 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(callback) { - $("#container").animate({marginLeft: "0px"}, 300, callback); - $("#chartContainer").animate({marginLeft: "0px"}, 300); - $("#drawer").animate({right: "-300px"}, 300, function() { - $("#drawer").css("display", "none"); - }); - drawerIsOpen = false; + $('#container').animate({marginLeft: '0px'}, 300, callback); + $('#chartContainer').animate({marginLeft: '0px'}, 300); + $('#drawer').animate({right: '-300px'}, 300, function() { + $('#drawer').css('display', 'none'); + }); + drawerIsOpen = false; } function openDrawer() { - drawerIsOpen = true; - $("#container").animate({marginLeft: "-300px"}, 300); - $("#chartContainer").animate({marginLeft: "-300px"}, 300); - $("#drawer").css("display", "block").animate({right: "0"}, 300); + drawerIsOpen = true; + $('#container').animate({marginLeft: '-300px'}, 300); + $('#chartContainer').animate({marginLeft: '-300px'}, 300); + $('#drawer').css('display', 'block').animate({right: '0'}, 300); } function closeTreatmentDrawer(callback) { - $("#container").animate({marginLeft: "0px"}, 400, callback); - $("#chartContainer").animate({marginLeft: "0px"}, 400); - $("#treatmentDrawer").animate({right: "-300px"}, 400, function() { - $("#treatmentDrawer").css("display", "none"); - }); - treatmentDrawerIsOpen = false; + $('#container').animate({marginLeft: '0px'}, 400, callback); + $('#chartContainer').animate({marginLeft: '0px'}, 400); + $('#treatmentDrawer').animate({right: '-300px'}, 400, function() { + $('#treatmentDrawer').css('display', 'none'); + }); + treatmentDrawerIsOpen = false; } + function openTreatmentDrawer() { - treatmentDrawerIsOpen = true; - $("#container").animate({marginLeft: "-300px"}, 400); - $("#chartContainer").animate({marginLeft: "-300px"}, 400); - $("#treatmentDrawer").css("display", "block").animate({right: "0"}, 400); - - $('#eventType').val('BG Check').focus(); - $('#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()); + treatmentDrawerIsOpen = true; + $('#container').animate({marginLeft: '-300px'}, 400); + $('#chartContainer').animate({marginLeft: '-300px'}, 400); + $('#treatmentDrawer').css('display', 'block').animate({right: '0'}, 400); + + $('#eventType').val('BG Check').focus(); + $('#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 = document.getElementById("enteredBy").value; - data.eventType = document.getElementById("eventType").value; - data.glucose = document.getElementById("glucoseValue").value; + data.enteredBy = document.getElementById('enteredBy').value; + data.eventType = document.getElementById('eventType').value; + data.glucose = document.getElementById('glucoseValue').value; data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = document.getElementById("carbsGiven").value; - data.insulin = document.getElementById("insulinGiven").value; - data.preBolus = document.getElementById("preBolus").value; - data.notes = document.getElementById("notes").value; + data.carbs = document.getElementById('carbsGiven').value; + data.insulin = document.getElementById('insulinGiven').value; + data.preBolus = document.getElementById('preBolus').value; + data.notes = document.getElementById('notes').value; var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != "now") { - var value = document.getElementById("eventTimeValue").value; + if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { + var value = document.getElementById('eventTimeValue').value; var eventTimeParts = value.split(':'); data.eventTime = new Date(); data.eventTime.setHours(eventTimeParts[0]); @@ -245,7 +252,7 @@ function treatmentSubmit(event) { eventTimeDisplay = formatTime(data.eventTime); } - var dataJson = JSON.stringify(data, null, " "); + var dataJson = JSON.stringify(data, null, ' '); var ok = window.confirm( 'Please verify that the data entered is correct: ' + @@ -261,11 +268,11 @@ function treatmentSubmit(event) { if (ok) { var xhr = new XMLHttpRequest(); - xhr.open("POST", "/api/v1/treatments/", true); + xhr.open('POST', '/api/v1/treatments/', true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(dataJson); - browserStorage.set("enteredBy", data.enteredBy); + browserStorage.set('enteredBy', data.enteredBy); closeTreatmentDrawer(); } @@ -279,121 +286,121 @@ function treatmentSubmit(event) { 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) { +$('#drawerToggle').click(function(event) { //close other drawers if(treatmentDrawerIsOpen) { - closeTreatmentDrawer(); - treatmentDrawerIsOpen = false; - } - - if(drawerIsOpen) { - closeDrawer(); - drawerIsOpen = false; - } else { - openDrawer(); - drawerIsOpen = true; - } - event.preventDefault(); + closeTreatmentDrawer(); + treatmentDrawerIsOpen = false; + } + + if(drawerIsOpen) { + closeDrawer(); + drawerIsOpen = false; + } else { + openDrawer(); + drawerIsOpen = true; + } + event.preventDefault(); }); -$("#treatmentDrawerToggle").click(function(event) { +$('#treatmentDrawerToggle').click(function(event) { //close other drawers if(drawerIsOpen) { - closeDrawer(); - drawerIsOpen = false; - } - - if(treatmentDrawerIsOpen) { - closeTreatmentDrawer(); - treatmentDrawerIsOpen = false; - } else { - openTreatmentDrawer(); - treatmentDrawerIsOpen = true; - } - event.preventDefault(); + closeDrawer(); + drawerIsOpen = false; + } + + if(treatmentDrawerIsOpen) { + closeTreatmentDrawer(); + treatmentDrawerIsOpen = false; + } else { + openTreatmentDrawer(); + treatmentDrawerIsOpen = true; + } + event.preventDefault(); }); -$("#treatmentDrawer").find("button").click(treatmentSubmit); +$('#treatmentDrawer').find('button').click(treatmentSubmit); -$("#eventTime input:radio").change(function (){ - if ($("#othertime").attr("checked")) { - $("#eventTimeValue").focus(); - } +$('#eventTime input:radio').change(function (){ + if ($('#othertime').attr('checked')) { + $('#eventTimeValue').focus(); + } }); -$("#eventTimeValue").focus(function () { - $("#othertime").attr("checked", "checked"); +$('#eventTimeValue').focus(function () { + $('#othertime').attr('checked', 'checked'); }); -$("#notification").click(function(event) { - closeNotification(); - event.preventDefault(); +$('#notification').click(function(event) { + closeNotification(); + event.preventDefault(); }); -$("input#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"), - "nightMode": $("#nightmode-browser").prop("checked"), - "showRawbg": $("#show-rawbg").prop("checked"), - "customTitle": $("input#customTitle").prop("value"), - "theme": $("input:radio[name=theme-browser]:checked").val(), - "timeFormat": $("input:radio[name=timeformat-browser]:checked").val() - }); - - event.preventDefault(); - - // reload for changes to take effect - // -- strip '#' so form submission does not fail - var url = window.location.href; - url = url.replace(/#$/, ""); - window.location = url; +$('input#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'), + '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() + }); + + event.preventDefault(); + + // 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(); - } + // 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(); + } }); From 55e3451184fbb7080787fde6e6115d6309ed4866 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 1 Feb 2015 19:26:58 -0800 Subject: [PATCH 067/714] support for 3-way raw display: never, always, and only when there is noise (default) --- lib/websocket.js | 1 + static/index.html | 4 +++- static/js/client.js | 42 ++++++++++++++++++++++++------------------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index bba9704c6b6..49d10bedbc4 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -167,6 +167,7 @@ function update() { obj.direction = directionToChar(element.direction); obj.filtered = element.filtered; obj.unfiltered = element.unfiltered; + obj.noise = element.noise; obj.rssi = element.rssi; cgmData.push(obj); } else if (element.slope) { diff --git a/static/index.html b/static/index.html index 6a07f4fcfde..64a94203b70 100644 --- a/static/index.html +++ b/static/index.html @@ -87,7 +87,9 @@

Nightscout

Show Raw BG Data
-
+
+
+
Custom Title
diff --git a/static/js/client.js b/static/js/client.js index e85571443d1..e569d5388b6 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -104,15 +104,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - function rawIsigToRawBg(unfiltered, scale, intercept, slope, filtered, sgv) { - if (slope == 0 || unfiltered == 0 || scale ==0 || slope == null || unfiltered == null || scale == null) return 0; - else if (filtered == 0 || filtered == null || sgv < 30 || sgv == null) { + function rawIsigToRawBg(entry, cal) { + + var unfiltered = parseInt(entry.unfiltered) || 0 + , filtered = parseInt(entry.filtered) || 0 + , sgv = entry.y + , noise = entry.noise || 0 + , scale = parseFloat(cal.scale) || 0 + , intercept = parseFloat(cal.intercept) || 0 + , slope = parseFloat(cal.slope) || 0; + + if (slope == 0 || unfiltered == 0 || scale == 0) { + return 0; + } else if (noise < 2 && browserSettings.showRawbg != 'always') { + return 0; + } else if (filtered == 0 || sgv < 40) { console.info("Skipping ratio adjustment for SGV " + sgv); - return scale*(unfiltered-intercept)/slope; - } - else { - var ratio = scale*(filtered-intercept)/slope / sgv; - return scale*(unfiltered-intercept)/slope / ratio; + return scale * (unfiltered - intercept) / slope; + } else { + var ratio = scale * (filtered - intercept) / slope / sgv; + return scale * ( unfiltered - intercept) / slope / ratio; } } @@ -1328,16 +1339,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; cal = d[4][d[4].length-1]; var temp1 = [ ]; - if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg == true) { - temp1 = d[0].map(function (obj) { - var rawBg = rawIsigToRawBg(obj.unfiltered - , cal.scale || [ ] - , cal.intercept - , cal.slope || [ ] - , obj.filtered - , obj.y); - return { date: new Date(obj.x-2*1000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg'} - }); + if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && (browserSettings.showRawbg == 'always' || browserSettings.showRawbg == 'noise')) { + temp1 = d[0].map(function (entry) { + var rawBg = rawIsigToRawBg(entry, cal); + return { date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg'} + }).filter(function(entry) { return entry.y > 0}); } 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'} From 21a32d25cc7f78e245a6cd8b49e36c984943c3c3 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 4 Feb 2015 12:44:55 -0800 Subject: [PATCH 068/714] default enabledOptions to an empty string --- 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 e569d5388b6..5fb41d1731b 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1447,7 +1447,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , version: xhr.version , head: xhr.head , apiEnabled: xhr.apiEnabled - , enabledOptions: xhr.enabledOptions + , enabledOptions: xhr.enabledOptions || '' , thresholds: xhr.thresholds , alarm_types: xhr.alarm_types , units: xhr.units From e2e0714aeb927bdd7f5877cb4ebbd6e6f32d5455 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 4 Feb 2015 17:04:55 -0800 Subject: [PATCH 069/714] added Noise level to the SGV tooltip when raw bg can be displayed --- static/js/client.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/static/js/client.js b/static/js/client.js index 5fb41d1731b..9c8eaa3bca1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -246,6 +246,19 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; return errorDisplay; } + function noiseCodeToDisplay(noise) { + var display = 'Not Set'; + switch (parseInt(noise)) { + case 1: display = 'Clean'; break; + case 2: display = 'Light'; break; + case 3: display = 'Medium'; break; + case 4: display = 'Heavy'; break; + case 5: display = 'Unknown'; break; + } + + return display; + } + // function to call when context chart is brushed function brushed(skipTimer) { @@ -468,10 +481,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var device = d.device && d.device.toLowerCase(); var bgType = (d.type == 'sgv' ? 'CGM' : (device == 'dexcom' ? 'Calibration' : 'Meter')); + var noiseLabel = ''; + + if (app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg != 'never') { + noiseLabel = noiseCodeToDisplay(d.noise); + } tooltip.transition().duration(TOOLTIP_TRANS_MS).style('opacity', .9); tooltip.html('' + bgType + ' BG: ' + d.sgv + (d.type == 'mbg' ? '
Device: ' + d.device : '') + + (noiseLabel ? '
Noise: ' + noiseLabel : '') + '
Time: ' + formatTime(d.date)) .style('left', (d3.event.pageX) + 'px') .style('top', (d3.event.pageY - 28) + 'px'); @@ -1338,6 +1357,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; cal = d[4][d[4].length-1]; + var temp1 = [ ]; if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && (browserSettings.showRawbg == 'always' || browserSettings.showRawbg == 'noise')) { temp1 = d[0].map(function (entry) { @@ -1346,7 +1366,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }).filter(function(entry) { return entry.y > 0}); } 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'} + 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} }); data = []; data = data.concat(temp1, temp2); From bebb5f7e1839186beb6ccd5622a0fa68bc45659e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 5 Feb 2015 07:48:13 -0800 Subject: [PATCH 070/714] only show noise for sgv's --- 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 9c8eaa3bca1..8de699cadbe 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -483,7 +483,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var bgType = (d.type == 'sgv' ? 'CGM' : (device == 'dexcom' ? 'Calibration' : 'Meter')); var noiseLabel = ''; - if (app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg != 'never') { + if (d.type == 'sgv' && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg != 'never') { noiseLabel = noiseCodeToDisplay(d.noise); } From e442ecfb81978a89b30a85d70162ed557455cf3b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 6 Feb 2015 22:13:04 -0800 Subject: [PATCH 071/714] simplified css media queries; improved alarm display in portrait mode --- static/css/main.css | 20 +------------------- static/js/client.js | 4 ++-- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 39a356acf2f..2909bc1d778 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -161,7 +161,6 @@ body { cursor: default; font-size: 75%; margin-top: 15px; - margin-right: 37px; width: auto; user-select: none; @@ -246,9 +245,6 @@ div.tooltip { width: 50%; } - #bgButton { - font-size: 120%; - } .dropdown-menu { font-size: 60% !important; } @@ -258,9 +254,6 @@ div.tooltip { } @media (max-width: 750px) { - #bgButton { - font-size: 120%; - } .dropdown-menu { font-size: 60% !important; } @@ -284,10 +277,6 @@ div.tooltip { font-size: 70%; } - #notification { - margin-top: 148px; - } - .status { text-align: center; margin-bottom: 0; @@ -307,10 +296,7 @@ div.tooltip { width: 100vw; } #bgButton { - font-size: 120%; - height: 142px; - margin-top: 1vw; - width: 98vw; + font-size: 100%; } .dropdown-menu { font-size: 60% !important; @@ -365,10 +351,6 @@ div.tooltip { padding-bottom: 20px; } - #bgButton { - font-size: 70% !important; - } - #currentTime { font-size: 85%; } diff --git a/static/js/client.js b/static/js/client.js index 8de699cadbe..b1e7b14b186 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -17,7 +17,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , FORMAT_TIME_12 = '%I:%M' , FORMAT_TIME_24 = '%H:%M%' , FORMAT_TIME_SCALE = '%I %p' - , WIDTH_TIME_HIDDEN = 500 + , WIDTH_TIME_HIDDEN = 400 , WIDTH_SMALL_DOTS = WIDTH_TIME_HIDDEN , WIDTH_BIG_DOTS = 800 , MINUTES_SINCE_LAST_UPDATE_WARN = 10 @@ -982,7 +982,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; if (querystring.mute != 'true') { audio.play(); } else { - showNotification('Alarm is muted per your request. (?mute=true)'); + showNotification('Alarm was muted (?mute=true)'); } } From 8dd460a7413e930698124fa1a9b381f63b7b0aa2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 7 Feb 2015 23:21:23 -0800 Subject: [PATCH 072/714] fixed HIGH/LOW dispay on phablets; moved time hide/show from js to css --- static/css/main.css | 6 ++++++ static/js/client.js | 17 +++-------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 2909bc1d778..77be14605ae 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -243,6 +243,7 @@ div.tooltip { .bgStatus { width: 50%; + font-size: 650%; } .dropdown-menu { @@ -313,6 +314,11 @@ div.tooltip { margin-left: 0; width: 100%; } + + #container.alarming .time { + display: none; + } + .timebox { text-align: none; width: auto; diff --git a/static/js/client.js b/static/js/client.js index b1e7b14b186..c9b773d67ea 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -17,8 +17,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , FORMAT_TIME_12 = '%I:%M' , FORMAT_TIME_24 = '%H:%M%' , FORMAT_TIME_SCALE = '%I %p' - , WIDTH_TIME_HIDDEN = 400 - , WIDTH_SMALL_DOTS = WIDTH_TIME_HIDDEN + , WIDTH_SMALL_DOTS = 400 , WIDTH_BIG_DOTS = 800 , MINUTES_SINCE_LAST_UPDATE_WARN = 10 , MINUTES_SINCE_LAST_UPDATE_URGENT = 20; @@ -289,14 +288,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // get slice of data so that concatenation of predictions do not interfere with subsequent updates var focusData = data.slice(); - if (alarmInProgress) { - if (jqWindow.width() > WIDTH_TIME_HIDDEN) { - $('.time').show(); - } else { - $('.time').hide(); - } - } - var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); // predict for retrospective data @@ -970,11 +961,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.toggleClass('urgent', file == urgentAlarmSound); var noButton = $('#noButton'); noButton.hide(); + $('#container').addClass('alarming'); $('.container .currentBG').text(); - if (jqWindow.width() <= WIDTH_TIME_HIDDEN) { - $('.time').hide(); - } } function playAlarm(audio) { @@ -998,7 +987,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $(this).removeClass('playing'); }); - $('.time').show(); + $('#container').removeClass('alarming'); // only emit ack if client invoke by button press if (isClient) { From 506cdcd28ace4fecadc5e5b1ceca071b517b7a45 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 8 Feb 2015 00:02:53 -0800 Subject: [PATCH 073/714] cleaned up setting checks for showing raw BGs (will make merging with iob-cob cleaner too) --- lib/api/index.js | 2 +- static/js/client.js | 10 ++++++++-- static/js/ui-utils.js | 34 ++++++++++++++++++++++++---------- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/api/index.js b/lib/api/index.js index 4c9f215f706..13ccb4c57ce 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -24,7 +24,7 @@ function create (env, entries, settings, treatments, devicestatus) { } if (env.enable) { - app.enabledOptions = env.enable; + app.enabledOptions = env.enable || ''; env.enable.toLowerCase().split(' ').forEach(function (value) { var enable = value.trim(); console.info("enabling feature:", enable); diff --git a/static/js/client.js b/static/js/client.js index b1e7b14b186..7b47d2d3f6d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -104,6 +104,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } + function showRawBGs() { + return app.enabledOptions + && app.enabledOptions.indexOf('rawbg' > -1) + && (browserSettings.showRawbg == 'always' || browserSettings.showRawbg == 'noise'); + } + function rawIsigToRawBg(entry, cal) { var unfiltered = parseInt(entry.unfiltered) || 0 @@ -483,7 +489,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var bgType = (d.type == 'sgv' ? 'CGM' : (device == 'dexcom' ? 'Calibration' : 'Meter')); var noiseLabel = ''; - if (d.type == 'sgv' && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && browserSettings.showRawbg != 'never') { + if (d.type == 'sgv' && showRawBGs()) { noiseLabel = noiseCodeToDisplay(d.noise); } @@ -1359,7 +1365,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var temp1 = [ ]; - if (cal && app.enabledOptions && app.enabledOptions.indexOf('rawbg' > -1) && (browserSettings.showRawbg == 'always' || browserSettings.showRawbg == 'noise')) { + if (cal && showRawBGs()) { temp1 = d[0].map(function (entry) { var rawBg = rawIsigToRawBg(entry, cal); return { date: new Date(entry.x - 2 * 1000), y: rawBg, sgv: scaleBg(rawBg), color: 'white', type: 'rawbg'} diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 594dc99b730..a99f08a61ab 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -13,6 +13,25 @@ var defaultSettings = { 'timeFormat': '12' }; +function rawBGsEnabled() { + return app.enabledOptions && app.enabledOptions.indexOf('rawbg') > -1; +} + +function initShowRawBG(currentValue) { + + var initValue = 'never'; + + if (currentValue === true) { + initValue = 'noise'; + } else if (currentValue == 'never' || currentValue == 'always' || currentValue == 'noise') { + initValue = currentValue; + } else { + initValue = app.enabledOptions.indexOf('rawbg-on') > -1 ? 'noise' : 'never'; + } + + return initValue; +} + function getBrowserSettings(storage) { var json = {}; @@ -62,18 +81,13 @@ function getBrowserSettings(storage) { json.nightMode = setDefault(json.nightMode, defaultSettings.nightMode); $('#nightmode-browser').prop('checked', json.nightMode); - if (app.enabledOptions.indexOf('rawbg') == -1) { - json.showRawbg = false; - $('#show-rawbg-option').hide(); - } else { + if (rawBGsEnabled()) { $('#show-rawbg-option').show(); - if (json.showRawbg === false) { - json.showRawbg = 'never'; - } else if (json.showRawbg === true) { - json.showRawbg = 'noise'; - } - json.showRawbg = setDefault(json.showRawbg, (app.enabledOptions.indexOf('rawbg-on') > -1 ? 'noise' : 'never')); + json.showRawbg = initShowRawBG(json.showRawbg); $('#show-rawbg-' + json.showRawbg).prop('checked', true); + } else { + json.showRawbg = 'never'; + $('#show-rawbg-option').hide(); } if (json.customTitle) { From d9cdb4d9dac006f97715a5341903f027f5c0ae09 Mon Sep 17 00:00:00 2001 From: Trusties13 Date: Mon, 9 Feb 2015 10:04:03 +1100 Subject: [PATCH 074/714] Update index.html Resolve syntax error in settings panel. --- static/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/index.html b/static/index.html index 934fdf1ff11..c61dbc2697a 100644 --- a/static/index.html +++ b/static/index.html @@ -75,7 +75,7 @@

Nightscout

-
Enable Alarms
+
Enable Alarms
From f41a511ef3978d756b4c842bc71624563fec510e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 8 Feb 2015 17:27:06 -0800 Subject: [PATCH 075/714] don't round the predicted values when mmol display mode --- static/js/client.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index b1e7b14b186..8c84ea64e15 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1169,6 +1169,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var BG_REF = scaleBg(140); var BG_MIN = scaleBg(36); var BG_MAX = scaleBg(400); + + function roundIfMgDl(value) { + if (browserSettings.units == 'mmol') { + return value; + } 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 @@ -1203,13 +1212,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 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, Math.round(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundIfMgDl(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, Math.round(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), + sgv: Math.max(BG_MIN, Math.min(BG_MAX, roundIfMgDl(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), color: predictedColor }; predicted.forEach(function (d) { From 48fab43e10a8e6976522c315c34f61ff15ce1223 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 8 Feb 2015 21:25:31 -0800 Subject: [PATCH 076/714] for mmol round predicted points to a single decimal place --- static/js/client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 8c84ea64e15..b84b5b9f820 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1170,9 +1170,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var BG_MIN = scaleBg(36); var BG_MAX = scaleBg(400); - function roundIfMgDl(value) { + function roundByUnits(value) { if (browserSettings.units == 'mmol') { - return value; + return value.toFixed(1); } else { return Math.round(value); } @@ -1212,13 +1212,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 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, roundIfMgDl(BG_REF * Math.exp((y[1] - 2 * CONE[i]))))), + 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, roundIfMgDl(BG_REF * Math.exp((y[1] + 2 * CONE[i]))))), + 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) { From 99d6fe1a0fe869c98f55aa8754850985831f509c Mon Sep 17 00:00:00 2001 From: Sulka Haro Date: Tue, 10 Feb 2015 09:40:40 +0200 Subject: [PATCH 077/714] Store display units with treatment objects, for unit conversion / checking on load. --- static/js/ui-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index a99f08a61ab..4a5e9fcf8e3 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -253,6 +253,7 @@ function treatmentSubmit(event) { data.insulin = document.getElementById('insulinGiven').value; data.preBolus = document.getElementById('preBolus').value; data.notes = document.getElementById('notes').value; + data.units = browserSettings.units; var eventTimeDisplay = ''; if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { From 600eabcd2c3c339a3ba193646739b2dbbb97addc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 10 Feb 2015 00:23:02 -0800 Subject: [PATCH 078/714] toggle bg-limit and error-code on the currentBG and use that to adjust font size since HIGH/LOW can get too wide --- static/css/main.css | 13 ++- static/index.html | 4 +- static/js/client.js | 212 ++++++++++++++++++++------------------------ 3 files changed, 107 insertions(+), 122 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 77be14605ae..ed5ef975b4c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -57,7 +57,16 @@ body { .bgStatus .currentBG { text-decoration: line-through; } -.bgStatus .currentDelta { + +.bgStatus .currentBG.error-code { + font-size: 80%; +} + +.bgStatus .currentBG.bg-limit { + font-size: 80%; +} + +.bgStatus .currentDetails { font-size: 25%; text-decoration: line-through; display: block; @@ -67,7 +76,7 @@ body { .bgStatus.current .currentBG { text-decoration: none; } -.bgStatus.current .currentDelta { +.bgStatus.current .currentDetails { font-size: 25%; text-decoration: none; display: block; diff --git a/static/index.html b/static/index.html index f0867b97383..50379422e7f 100644 --- a/static/index.html +++ b/static/index.html @@ -30,13 +30,13 @@

Nightscout

--- - - -- + --
-
+
---
- + -
diff --git a/static/js/client.js b/static/js/client.js index d8f7d6b6937..7945d4d43cb 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -19,6 +19,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , FORMAT_TIME_SCALE = '%I %p' , WIDTH_SMALL_DOTS = 400 , WIDTH_BIG_DOTS = 800 + , MINUTE_IN_SECS = 60 + , HOUR_IN_SECS = 3600 + , DAY_IN_SECS = 86400 + , WEEK_IN_SECS = 604800 , MINUTES_SINCE_LAST_UPDATE_WARN = 10 , MINUTES_SINCE_LAST_UPDATE_URGENT = 20; @@ -305,7 +309,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') - , currentDetails = $('.bgStatus .currentDetails'); + , currentDetails = $('.bgStatus .currentDetails') + , lastEntry = $('#lastEntry'); + function updateCurrentSGV(value) { if (value < 39) { @@ -322,40 +328,46 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('bg-limit', value == 39 || value > 400); } - function calcBGDelta(prev, current) { + function updateBGDelta(prev, current) { - var bgDeltaString; + var pill = currentDetails.find('span.pill.bgdelta'); + if (!pill || pill.length == 0) { + pill = $(''); + currentDetails.append(pill); + } - if (prev < 40 || prev > 400 || current < 40 || current > 400) { - bgDeltaString = ''; + if (prev === undefined || current == undefined || prev < 40 || prev > 400 || current < 40 || current > 400) { + pill.children('em').text('#'); } else { var bgDelta = scaleBg(current) - scaleBg(prev); if (browserSettings.units == 'mmol') { bgDelta = bgDelta.toFixed(1); } - bgDeltaString = bgDelta; - if (bgDelta >= 0) { - bgDeltaString = '+' + bgDelta; - } - - bgDeltaString = '' + bgDeltaString + ''; + if (browserSettings.units == 'mmol') { + pill.children('label').text('mmol/L'); + } else { + pill.children('label').text('mg/dL'); + } - return bgDeltaString; } - function buildIOBIndicator(time) { - var iob = Nightscout.iob.calcTotal(treatments, profile, time); - return '' + iob.display + 'U'; + 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'); + } else { + currentDetails.find('.pill.iob').remove(); + } } if (inRetroMode()) { @@ -398,27 +410,23 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateCurrentSGV(focusPoint.y); - var details = calcBGDelta(prevfocusPoint.y, focusPoint.y); - - if (showIOB()) { - details += buildIOBIndicator(time); - } + updateBGDelta(prevfocusPoint.y, focusPoint.y); currentBG.css('text-decoration','line-through'); currentDirection.html(focusPoint.y < 39 ? '✖' : focusPoint.direction); - currentDetails.html(details); } else { + updateBGDelta(); currentBG.text('---'); currentDirection.text('-'); - currentDetails.text(''); } + updateIOBIndicator(time); + $('#currentTime') .text(formatTime(time)) .css('text-decoration','line-through'); - $('#lastEntry').removeClass('current'); - $('#lastEntry label').text('RETRO').removeClass('current'); + 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); @@ -428,15 +436,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClockDisplay(); updateTimeAgo(); - var details = calcBGDelta(prevSGV.y, latestSGV.y); - - if (showIOB()) { - details += buildIOBIndicator(nowDate); - } + updateBGDelta(prevSGV.y, latestSGV.y); + updateIOBIndicator(nowDate); currentBG.css('text-decoration', ''); currentDirection.html(latestSGV.y < 39 ? '✖' : latestSGV.direction); - currentDetails.html(details); } xScale.domain(brush.extent()); @@ -1019,39 +1023,31 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - function timeAgo(offset) { - var parts = {}, - MINUTE = 60, - HOUR = 3600, - DAY = 86400, - WEEK = 604800; - - //offset = (MINUTE * MINUTES_SINCE_LAST_UPDATE_WARN) + 60 - //offset = (MINUTE * MINUTES_SINCE_LAST_UPDATE_URGENT) + 60 - - if (offset <= MINUTE) parts = { label: 'now' }; - if (offset <= MINUTE * 2) parts = { value: 1, label: 'min ago' }; - else if (offset < (MINUTE * 60)) parts = { value: Math.round(Math.abs(offset / MINUTE)), label: 'mins ago' }; - else if (offset < (HOUR * 2)) parts = { value: 1, label: 'hr ago' }; - else if (offset < (HOUR * 24)) parts = { value: Math.round(Math.abs(offset / HOUR)), label: 'hrs ago' }; - else if (offset < DAY) parts = { value: 1, label: 'day ago' }; - else if (offset < (DAY * 7)) parts = { value: Math.round(Math.abs(offset / DAY)), label: 'day ago' }; - else if (offset < (WEEK * 52)) parts = { value: Math.round(Math.abs(offset / WEEK)), label: 'week ago' }; - else parts = { label: 'a long time ago' }; - - if (offset > (MINUTE * MINUTES_SINCE_LAST_UPDATE_URGENT)) { - var lastEntry = $('#lastEntry'); - lastEntry.removeClass('warn'); - lastEntry.addClass('urgent'); - - $('.bgStatus').removeClass('current'); - } else if (offset > (MINUTE * MINUTES_SINCE_LAST_UPDATE_WARN)) { - var lastEntry = $('#lastEntry'); - lastEntry.removeClass('urgent'); - lastEntry.addClass('warn'); + function timeAgo(time) { + var now = Date.now() + , offset = time == -1 ? -1 : (now - time) / 1000 + , parts = {}; + + if (offset < MINUTE_IN_SECS * -5) parts = { label: 'in the future' }; + else if (offset <= 0) 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 if (offset < (WEEK_IN_SECS * 52)) parts = { value: Math.round(Math.abs(offset / WEEK_IN_SECS)), label: 'week ago' }; + else parts = { label: 'a long time ago' }; + + if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_URGENT)) { + parts.removeClass = 'current warn'; + parts.addClass = 'urgent'; + } else if (offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_WARN)) { + parts.removeClass = 'current urgent'; + parts.addClass = 'warn'; } else { - $('.bgStatus').addClass('current'); - $('#lastEntry').removeClass('warn urgent'); + parts.addClass = 'current'; + parts.removeClass = 'warn urgent'; } return parts; @@ -1264,17 +1260,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function updateTimeAgo() { - if (!latestSGV || inRetroMode()) return; + var lastEntry = $('#lastEntry') + , time = latestSGV ? new Date(latestSGV.x).getTime() : -1 + , ago = timeAgo(time) + , retroMode = inRetroMode(); + + lastEntry.addClass(ago.addClass); + lastEntry.removeClass(ago.removeClass); - var secsSinceLast = (Date.now() - new Date(latestSGV.x).getTime()) / 1000; - var ago = timeAgo(secsSinceLast); - $('#lastEntry').toggleClass('current', secsSinceLast < 10 * 60); - if (ago.value === undefined) { - $('#lastEntry em').hide(); + if (retroMode || !ago.value) { + lastEntry.find('em').hide(); } else { - $('#lastEntry em').show().text(ago.value); + lastEntry.find('em').show().text(ago.value); } - $('#lastEntry label').text(ago.label); + + lastEntry.find('label').text(retroMode ? 'RETRO' : ago.label); } function init() { From 758b868ef37af8ae3fd0a29c5ca3280e0130a1f0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 00:29:33 -0700 Subject: [PATCH 126/714] smaller font for chart when screen is shorted than 480px --- static/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index b3aae68485a..e28f2891169 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -422,7 +422,7 @@ div.tooltip { #chartContainer { top: 130px; - font-size: 12px; + font-size: 10px; } #chartContainer svg { height: calc(100vh - 130px); From 967f519c214ea4369d44defe85e0f76ee8427fa6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 00:47:45 -0700 Subject: [PATCH 127/714] added back + in delta --- 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 7945d4d43cb..6196a7d94d2 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -344,7 +344,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgDelta = bgDelta.toFixed(1); } - pill.children('em').text(bgDelta); + pill.children('em').text((bgDelta >= 0 ? '+' : '') + bgDelta); } if (browserSettings.units == 'mmol') { From d08f5f78277eb54c0fa95f393b5409f017347983 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 02:13:22 -0700 Subject: [PATCH 128/714] use async.parallel to make multiple mongo queries at once and get rid of the callback-hell; also use env.DISPLAY_UNITS if units aren't set on request --- lib/pebble.js | 177 ++++++++++++++++++++++++++++------------------- lib/websocket.js | 129 +++++++++++++++++++--------------- package.json | 1 + 3 files changed, 180 insertions(+), 127 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index d918de5649f..1f1bcb0ea0a 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -12,6 +12,7 @@ var DIRECTIONS = { }; var iob = require("./iob")(); +var async = require('async'); function directionToTrend (direction) { var trend = 8; @@ -22,97 +23,126 @@ function directionToTrend (direction) { } function pebble (req, res) { - var ONE_DAY = 24 * 60 * 60 * 1000; - var useMetricBg = (req.query.units === "mmol"); - var uploaderBattery; - var treatmentResults; - var profileResult; + var ONE_DAY = 24 * 60 * 60 * 1000 + , uploaderBattery + , treatmentResults + , profileResult + , sgvData = [ ] + , calData = [ ]; function scaleBg(bg) { - if (useMetricBg) { + if (req.mmol) { return (Math.round((bg / 18) * 10) / 10).toFixed(1); - } else + } else { return bg; + } } - function get_latest (err, results) { + function sendData () { var now = Date.now(); - var sgvData = [ ]; - var calData = [ ]; - - results.forEach(function(element, index, array) { - if (element) { - var obj = {}; - if (element.sgv) { - var next = null; - var sgvs = results.filter(function(d) { - return !!d.sgv; - }); - if (index + 1 < sgvs.length) { - next = sgvs[index + 1]; - } - obj.sgv = scaleBg(element.sgv).toString(); - obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); - if (useMetricBg) { - obj.bgdelta = obj.bgdelta.toFixed(1); - } - if ('direction' in element) { - obj.trend = directionToTrend(element.direction); - obj.direction = element.direction; - } - obj.datetime = element.date; - if (req.rawbg) { - obj.filtered = element.filtered; - obj.unfiltered = element.unfiltered; - obj.noise = element.noise; - obj.rssi = element.rssi; - } - // obj.date = element.date.toString( ); - sgvData.push(obj); - } else if (req.rawbg && element.type == 'cal') { - calData.push(element); - } - } - }); - - var count = parseInt(req.query.count) || 1; - var bgs = sgvData.slice(0, count); //for compatibility we're keeping battery and iob here, but they would be better somewhere else - bgs[0].battery = uploaderBattery ? "" + uploaderBattery : undefined; - if (req.iob) { - bgs[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; + if (sgvData.length > 0) { + sgvData[0].battery = uploaderBattery ? "" + uploaderBattery : undefined; + if (req.iob) { + sgvData[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; + } } - var result = { status: [ {now:now}], bgs: bgs, cals: calData.slice(0, count) }; + var result = { status: [ {now: now} ], bgs: sgvData, cals: calData }; res.setHeader('content-type', 'application/json'); res.write(JSON.stringify(result)); res.end( ); // collection.db.close(); } - req.devicestatus.last(function(err, value) { - if (!err && value) { - uploaderBattery = value.uploaderBattery; - } else { - console.error("req.devicestatus.tail", err); - } - var earliest_data = Date.now() - ONE_DAY; - loadTreatments(req, earliest_data, function (err, trs) { - treatmentResults = trs; - loadProfile(req, function (err, profileResults) { - profileResults.forEach(function(profile) { - if (profile) { - if (profile.dia) { - profileResult = profile; + var earliest_data = Date.now() - ONE_DAY; + + async.parallel({ + devicestatus: function (callback) { + req.devicestatus.last(function (err, value) { + if (!err && value) { + uploaderBattery = value.uploaderBattery; + } else { + console.error("req.devicestatus.tail", err); } - } + callback(); }); - var q = { find: {"date": {"$gte": earliest_data}} }; - req.entries.list(q, get_latest); - }); - }); - }); + } + , treatments: function(callback) { + loadTreatments(req, earliest_data, function (err, trs) { + treatmentResults = trs; + callback() + }); + } + , profile: function(callback) { + loadProfile(req, function (err, profileResults) { + profileResults.forEach(function (profile) { + if (profile) { + if (profile.dia) { + profileResult = profile; + } + } + }); + callback(); + }) + } + , cal: function(callback) { + var cq = { count: req.count, find: {"type": "cal"} }; + req.entries.list(cq, function (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(); + }); + } + , entries: function(callback) { + var q = { count: req.count, find: { "sgv": { $exists: true }} }; + + req.entries.list(q, function(err, results) { + results.forEach(function(element, index) { + if (element) { + var obj = {}; + var next = null; + var sgvs = results.filter(function(d) { + return !!d.sgv; + }); + if (index + 1 < sgvs.length) { + next = sgvs[index + 1]; + } + obj.sgv = scaleBg(element.sgv).toString(); + obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); + if (req.mmol) { + obj.bgdelta = obj.bgdelta.toFixed(1); + } + if ('direction' in element) { + obj.trend = directionToTrend(element.direction); + obj.direction = element.direction; + } + obj.datetime = element.date; + if (req.rawbg) { + obj.filtered = element.filtered; + obj.unfiltered = element.unfiltered; + obj.noise = element.noise; + obj.rssi = element.rssi; + } + // obj.date = element.date.toString( ); + sgvData.push(obj); + } + }); + callback(); + }); + } + }, sendData); + } function loadTreatments(req, earliest_data, fn) { @@ -140,6 +170,9 @@ function configure (entries, treatments, profile, devicestatus, env) { req.devicestatus = devicestatus; 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'; + req.count = parseInt(req.query.count) || 1; + next( ); } return [middle, pebble]; diff --git a/lib/websocket.js b/lib/websocket.js index 6936ef17358..0525a701825 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,3 +1,4 @@ +var async = require('async'); function websocket (env, server, entries, treatments, profiles) { "use strict"; @@ -149,62 +150,80 @@ function update() { mbgData = []; profileData = []; var earliest_data = now - TWO_DAYS; - var q = { find: {"date": {"$gte": earliest_data}} }; - entries.list(q, function (err, results) { - results.forEach(function(element, index, array) { - if (element) { - if (element.mbg) { - var obj = {}; - obj.y = element.mbg; - obj.x = element.date; - obj.d = element.dateString; - obj.device = element.device; - mbgData.push(obj); - } else if (element.sgv) { - var obj = {}; - obj.y = element.sgv; - obj.x = element.date; - obj.d = element.dateString; - obj.device = element.device; - obj.direction = directionToChar(element.direction); - obj.filtered = element.filtered; - obj.unfiltered = element.unfiltered; - obj.noise = element.noise; - obj.rssi = element.rssi; - cgmData.push(obj); - } else if (element.slope) { - var obj = {}; - obj.x = element.date; - obj.d = element.dateString; - obj.scale = element.scale; - obj.intercept = element.intercept; - obj.slope = element.slope; - calData.push(obj); - } - } - }); - var tq = { find: {"created_at": {"$gte": new Date(earliest_data).toISOString()}} }; - 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; - }); - - profiles.list(function (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; + + async.parallel({ + entries: function(callback) { + var q = { find: {"date": {"$gte": earliest_data}} }; + entries.list(q, function (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 + }); + } } - } - }); - // all done, do loadData - loadData( ); - }); - }); - }); + }); + callback(); + }) + } + , cal: function(callback) { + var cq = { count: 1, find: {"type": "cal"} }; + entries.list(cq, function (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(earliest_data).toISOString()}} }; + 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) { + profiles.list(function (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(); + }); + } + }, loadData); return update; } diff --git a/package.json b/package.json index ec203d385e0..929999ff6d8 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "node": "0.10.x" }, "dependencies": { + "async": "^0.9.0", "body-parser": "^1.4.3", "bower": "^1.3.8", "browserify-express": "^0.1.4", From 2dc75369c07b696b1ba87f8214fc1fd62113710e Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 03:06:25 -0700 Subject: [PATCH 129/714] fixed mocks used in pebble test and them bugs found by tests --- lib/pebble.js | 28 ++++++++++++++-------------- tests/pebble.test.js | 32 +++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 1f1bcb0ea0a..8464bb1d69e 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -88,21 +88,21 @@ function pebble (req, res) { }) } , cal: function(callback) { - var cq = { count: req.count, find: {"type": "cal"} }; - req.entries.list(cq, function (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 - }); - } + if (req.rawbg) { + var cq = { count: req.count, find: {type: 'cal'} }; + req.entries.list(cq, function (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(); }); + } else { callback(); - }); + } } , entries: function(callback) { var q = { count: req.count, find: { "sgv": { $exists: true }} }; @@ -170,7 +170,7 @@ function configure (entries, treatments, profile, devicestatus, env) { req.devicestatus = devicestatus; 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'; + req.mmol = false;//(req.query.units || env.DISPLAY_UNITS) === 'mmol'; req.count = parseInt(req.query.count) || 1; next( ); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index d6704ed7928..2edba95ee43 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -4,8 +4,8 @@ var should = require('should'); //Mock entries var entries = { - list: function(q, callback) { - var results = [ + list: function(opts, callback) { + var sgvs = [ { device: 'dexcom', date: 1422727301000, dateString: 'Sat Jan 31 10:01:41 PST 2015', @@ -17,14 +17,6 @@ var entries = { rssi: 179, noise: 1 }, - { device: 'dexcom', - date: 1422647711000, - dateString: 'Fri Jan 30 11:55:11 PST 2015', - slope: 895.8571693029189, - intercept: 34281.06876195567, - scale: 1, - type: 'cal' - }, { device: 'dexcom', date: 1422727001000, dateString: 'Sat Jan 31 09:56:41 PST 2015', @@ -70,7 +62,25 @@ var entries = { noise: 1 } ]; - callback(null, results); + + 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)); + } } }; From 3a22002f64a4bc91dbfa39366a8893a7d0bbc64c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 09:55:40 -0700 Subject: [PATCH 130/714] use full hight of the chart (removed 20px top padding); move x-axis labels to below the line --- 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 6196a7d94d2..4db9acee521 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -34,7 +34,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , treatments , profile , cal - , padding = { top: 20, right: 10, bottom: 30, left: 10 } + , padding = { top: 0, right: 10, bottom: 30, left: 10 } , opacity = {current: 1, DAY: 1, NIGHT: 0.5} , now = Date.now() , data = [] @@ -162,7 +162,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .scale(xScale) .tickFormat(d3.time.format(getTimeFormat(true))) .ticks(4) - .orient('top'); + .orient('bottom'); yAxis = d3.svg.axis() .scale(yScale) @@ -689,7 +689,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; focus.append('line') .attr('class', 'now-line') .attr('x1', xScale(new Date(now))) - .attr('y1', yScale(scaleBg(36))) + .attr('y1', yScale(scaleBg(30))) .attr('x2', xScale(new Date(now))) .attr('y2', yScale(scaleBg(420))) .style('stroke-dasharray', ('3, 3')) From 6357212222eb0a6080f71c34bd76d6e4262dd83d Mon Sep 17 00:00:00 2001 From: Kate Farnsworth Date: Thu, 12 Mar 2015 18:37:03 -0400 Subject: [PATCH 131/714] Update pebble.js --- lib/pebble.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 8464bb1d69e..03633995763 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -93,9 +93,11 @@ function pebble (req, res) { req.entries.list(cq, function (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 - }); + var calobj = {}; + calobj.slope = Math.round(element.slope).toFixed(0); + calobj.intercept = Math.round(element.intercept).toFixed(0); + calobj.scale = Math.round(element.scale).toFixed(0); + calData.push(calobj); } }); callback(); @@ -132,7 +134,7 @@ function pebble (req, res) { obj.filtered = element.filtered; obj.unfiltered = element.unfiltered; obj.noise = element.noise; - obj.rssi = element.rssi; + //obj.rssi = element.rssi; } // obj.date = element.date.toString( ); sgvData.push(obj); From 051f49bac5f62143dd2f1d4be6f6b72b0fcb584a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 15:37:23 -0700 Subject: [PATCH 132/714] uncomment testing hack that shouldn't have been committed --- lib/pebble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pebble.js b/lib/pebble.js index 8464bb1d69e..06b78ca0113 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -170,7 +170,7 @@ function configure (entries, treatments, profile, devicestatus, env) { req.devicestatus = devicestatus; req.rawbg = env.enable && env.enable.indexOf('rawbg') > -1; req.iob = env.enable && env.enable.indexOf('iob') > -1; - req.mmol = false;//(req.query.units || env.DISPLAY_UNITS) === 'mmol'; + req.mmol = (req.query.units || env.DISPLAY_UNITS) === 'mmol'; req.count = parseInt(req.query.count) || 1; next( ); From 7b6f054409632b8f8e30884a991b92f8b6517624 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 15:44:21 -0700 Subject: [PATCH 133/714] removed .toFixed(0)'s and fixed tests --- lib/pebble.js | 12 +++++------- tests/pebble.test.js | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index ab1f22ff7eb..2627b55b2a8 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -93,11 +93,11 @@ function pebble (req, res) { req.entries.list(cq, function (err, results) { results.forEach(function (element) { if (element) { - var calobj = {}; - calobj.slope = Math.round(element.slope).toFixed(0); - calobj.intercept = Math.round(element.intercept).toFixed(0); - calobj.scale = Math.round(element.scale).toFixed(0); - calData.push(calobj); + calData.push({ + slope: Math.round(element.slope) + , intercept: Math.round(element.intercept) + , scale: Math.round(element.scale) + }); } }); callback(); @@ -134,9 +134,7 @@ function pebble (req, res) { obj.filtered = element.filtered; obj.unfiltered = element.unfiltered; obj.noise = element.noise; - //obj.rssi = element.rssi; } - // obj.date = element.date.toString( ); sgvData.push(obj); } }); diff --git a/tests/pebble.test.js b/tests/pebble.test.js index 2edba95ee43..e9dd57d1e7f 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -174,13 +174,12 @@ describe('Pebble Endpoint with Raw', function ( ) { bg.filtered.should.equal(113984); bg.unfiltered.should.equal(111920); bg.noise.should.equal(1); - bg.rssi.should.equal(179); bg.battery.should.equal('100'); res.body.cals.length.should.equal(1); var cal = res.body.cals[0]; - cal.slope.should.equal(895.8571693029189); - cal.intercept.should.equal(34281.06876195567); + cal.slope.should.equal(896); + cal.intercept.should.equal(34281); cal.scale.should.equal(1); done( ); }); From 50dd57dd72b15cad4f88eb1a379809334827e522 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 17:48:44 -0700 Subject: [PATCH 134/714] made direction arrow a little smaller for the max-width: 750px break point --- static/css/main.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index e28f2891169..83004ec5111 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -298,8 +298,8 @@ div.tooltip { } .bgStatus .currentDirection { - font-size: 60px; - line-height: 60px; + font-size: 50px; + line-height: 50px; } .bgStatus .currentDetails { From ebcb49c0af7d491fa8d509bca4f84511c6dfb579 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 17:56:26 -0700 Subject: [PATCH 135/714] bumped version to 0.7.0-dev --- bower.json | 2 +- package.json | 2 +- static/index.html | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bower.json b/bower.json index 9de3a9bc428..ddd8e5756f2 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "nightscout", - "version": "0.6.4", + "version": "0.7.0-dev", "dependencies": { "angularjs": "1.3.0-beta.19", "bootstrap": "~3.2.0", diff --git a/package.json b/package.json index ec203d385e0..b7ac8480f8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Nightscout", - "version": "0.6.4", + "version": "0.7.0-dev", "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", diff --git a/static/index.html b/static/index.html index f12f8a41ac8..8efb378d2ce 100644 --- a/static/index.html +++ b/static/index.html @@ -6,9 +6,9 @@ Nightscout - + - + @@ -221,8 +221,8 @@

Nightscout

- - + + From 31624221c9211200d5e81089a890d04eef899490 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 12 Mar 2015 18:00:39 -0700 Subject: [PATCH 136/714] a little clean up --- lib/pebble.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index 2627b55b2a8..fac7b3cfb32 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -1,3 +1,5 @@ +'use strict'; + var DIRECTIONS = { NONE: 0 , DoubleUp: 1 @@ -53,7 +55,6 @@ function pebble (req, res) { res.setHeader('content-type', 'application/json'); res.write(JSON.stringify(result)); res.end( ); - // collection.db.close(); } var earliest_data = Date.now() - ONE_DAY; @@ -72,7 +73,7 @@ function pebble (req, res) { , treatments: function(callback) { loadTreatments(req, earliest_data, function (err, trs) { treatmentResults = trs; - callback() + callback(); }); } , profile: function(callback) { @@ -85,7 +86,7 @@ function pebble (req, res) { } }); callback(); - }) + }); } , cal: function(callback) { if (req.rawbg) { From b7fc6b932bec2eb37f8267fe0440e2a086f97061 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 13 Mar 2015 17:29:02 -0700 Subject: [PATCH 137/714] added initial compression of everything so we can start testing and see if there are any issues --- package.json | 1 + server.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 9b5b6aed992..92b50a630df 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "body-parser": "^1.4.3", "bower": "^1.3.8", "browserify-express": "^0.1.4", + "compression": "^1.4.2", "errorhandler": "^1.1.1", "event-stream": "~3.1.5", "express": "^4.6.1", diff --git a/server.js b/server.js index 2f56c759794..e427926d673 100644 --- a/server.js +++ b/server.js @@ -43,6 +43,7 @@ var store = require('./lib/storage')(env, function() { var express = require('express'); +var compression = require('compression'); /////////////////////////////////////////////////// // api and json object variables @@ -66,9 +67,14 @@ var appInfo = software.name + ' ' + software.version; app.set('title', appInfo); app.enable('trust proxy'); // Allows req.secure test on heroku https connections. -//if (env.api_secret) { -// console.log("API_SECRET", env.api_secret); -//} +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('/api/v1', api); // pebble data From 206a8fd61cd97bcf16eae330e3ae08103fbd2826 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 13 Mar 2015 20:03:05 -0700 Subject: [PATCH 138/714] fixed bug that caused BG color not to change on new data --- static/js/client.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 4db9acee521..32cabd8817e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -307,7 +307,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var nowDate = new Date(brushExtent[1] - THIRTY_MINS_IN_MS); - var currentBG = $('.bgStatus .currentBG') + var bgButton = $('.bgButton') + , currentBG = $('.bgStatus .currentBG') , currentDirection = $('.bgStatus .currentDirection') , currentDetails = $('.bgStatus .currentDetails') , lastEntry = $('#lastEntry'); @@ -324,6 +325,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.text(scaleBg(value)); } + bgButton.removeClass('urgent warning inrange'); + if (!inRetroMode()) { + bgButton.addClass(sgvToColoredRange(value)); + } + currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); } @@ -370,12 +376,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } } - if (inRetroMode()) { - $('.bgButton').removeClass('urgent warning inrange'); - } else { - $('.bgButton').addClass(sgvToColoredRange(latestSGV.y)); - } - // 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 @@ -1093,6 +1093,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; R2 = Math.sqrt(Math.max(carbs, insulin * CR)) / scale, R3 = R2 + 8 / scale; + if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { + console.warn("Found NaN for 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 }, From bcd3fa021dd0ed888201e159ddf53c5f91079292 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 13 Mar 2015 23:07:01 -0700 Subject: [PATCH 139/714] make sure the socket.io.js file that is sent to the client gets gzipped, etc --- lib/websocket.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 0525a701825..161d239f8b4 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -34,7 +34,12 @@ var dir2Char = { patientData = []; function start ( ) { - io = require('socket.io').listen(server); + 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': true + }); } // get data from database and setup to update every minute function kickstart (fn) { From a1f963a351f19b1dab427bfa9dce6b119bffccea Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 01:49:32 -0700 Subject: [PATCH 140/714] make bad data easier to find and prevent errors --- static/js/client.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 32cabd8817e..f4956a9c8fa 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -464,7 +464,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function prepareFocusCircles(sel) { sel.attr('cx', function (d) { return xScale(d.date); }) - .attr('cy', function (d) { return yScale(d.sgv); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + console.warn("Bad Data: isNaN(sgv)", d); + return yScale(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; }) @@ -919,7 +926,14 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function prepareContextCircles(sel) { sel.attr('cx', function (d) { return xScale2(d.date); }) - .attr('cy', function (d) { return yScale2(d.sgv); }) + .attr('cy', function (d) { + if (isNaN(d.sgv)) { + console.warn("Bad Data: isNaN(sgv)", d); + return yScale2(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; }) @@ -1094,7 +1108,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; R3 = R2 + 8 / scale; if (isNaN(R1) || isNaN(R3) || isNaN(R3)) { - console.warn("Found NaN for treatment:", treatment); + console.warn("Bad Data: Found isNaN value in treatment", treatment); return; } From b2782fc8823895c5c11dd4edca8170bae61e9eb7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 02:02:43 -0700 Subject: [PATCH 141/714] group bad data warnings --- static/js/client.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index f4956a9c8fa..2d5e06c5e3d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -463,11 +463,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }; function prepareFocusCircles(sel) { + var badData = []; sel.attr('cx', function (d) { return xScale(d.date); }) .attr('cy', function (d) { if (isNaN(d.sgv)) { - console.warn("Bad Data: isNaN(sgv)", d); - return yScale(450); + badData.push(d); + return yScale(scaleBg(450)); } else { return yScale(d.sgv); } @@ -481,6 +482,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }) .attr('r', function (d) { return dotRadius(d.type); }); + if (badData.length > 0) { + console.warn("Bad Data: isNaN(sgv)", badData); + } + return sel; } @@ -925,11 +930,12 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .data(data); function prepareContextCircles(sel) { + var badData = []; sel.attr('cx', function (d) { return xScale2(d.date); }) .attr('cy', function (d) { if (isNaN(d.sgv)) { - console.warn("Bad Data: isNaN(sgv)", d); - return yScale2(450); + badData.push(d); + return yScale2(scaleBg(450)); } else { return yScale2(d.sgv); } @@ -940,6 +946,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; .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; } From 1fb79230ac9e6e49df4f33fe70f3d47d29cb69a5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 10:13:58 -0700 Subject: [PATCH 142/714] fixed issue with delta always being 0 in /pebble --- lib/pebble.js | 4 ++-- tests/pebble.test.js | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index fac7b3cfb32..f8c31bf54dc 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -51,7 +51,7 @@ function pebble (req, res) { } } - var result = { status: [ {now: now} ], bgs: sgvData, cals: calData }; + var result = { status: [ {now: now} ], bgs: sgvData.slice(0, req.count), cals: calData }; res.setHeader('content-type', 'application/json'); res.write(JSON.stringify(result)); res.end( ); @@ -108,7 +108,7 @@ function pebble (req, res) { } } , entries: function(callback) { - var q = { count: req.count, find: { "sgv": { $exists: true }} }; + var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; req.entries.list(q, function(err, results) { results.forEach(function(element, index) { diff --git a/tests/pebble.test.js b/tests/pebble.test.js index e9dd57d1e7f..030be48a222 100644 --- a/tests/pebble.test.js +++ b/tests/pebble.test.js @@ -117,7 +117,31 @@ describe('Pebble Endpoint without Raw', function ( ) { pebble.should.be.ok; }); - it('/pebble', function (done) { + it('/pebble default(1) count', function (done) { + request(this.app) + .get('/pebble') + .expect(200) + .end(function (err, res) { + 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.trend.should.equal(4); + bg.direction.should.equal('Flat'); + bg.datetime.should.equal(1422727301000); + should.not.exist(bg.filtered); + should.not.exist(bg.unfiltered); + should.not.exist(bg.noise); + should.not.exist(bg.rssi); + bg.battery.should.equal('100'); + + res.body.cals.length.should.equal(0); + done( ); + }); + }); + + it('/pebble?count=2', function (done) { request(this.app) .get('/pebble?count=2') .expect(200) From 5b526a1f9286aa33485f33edcc38fd308b4c5d48 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 10:33:07 -0700 Subject: [PATCH 143/714] don't removed urgent/warning when based on BG range when there is an alarm, since it could be a predictive alarm --- static/js/client.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 2d5e06c5e3d..2e302388d4c 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -325,9 +325,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.text(scaleBg(value)); } - bgButton.removeClass('urgent warning inrange'); - if (!inRetroMode()) { + if (!alarmingNow()) { + bgButton.removeClass('urgent warning inrange'); + if (!inRetroMode()) { bgButton.addClass(sgvToColoredRange(value)); + } } currentBG.toggleClass('error-code', value < 39); From 86e2493cb2671f3591efbe669520b3f8f0ce979c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 10:55:23 -0700 Subject: [PATCH 144/714] do everything we can to find a BG for a treatment; better unit conversion --- static/js/client.js | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 2d5e06c5e3d..af4dc7fd82d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1078,28 +1078,43 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } - function calcBGByTime(time) { + function scaledTreatmentBG(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; - } + if (!d.y) { + return false; + } else { + return Math.abs((new Date(d.date)).getTime() - time) <= SIX_MINS_IN_MS; + } }); var totalBG = 0; closeBGs.forEach(function(d) { - totalBG += d.y; + totalBG += d.y; }); - return totalBG ? (totalBG / closeBGs.length) : 450; - } + return totalBG > 0 && closeBGs.length > 0 ? (totalBG / closeBGs.length) : 450; + } - function scaledTreatmentBG(treatment) { - //TODO: store units in db per treatment, and use that for conversion, until then assume glucose doesn't need to be scaled - // Care Portal treatment form does ask for the display units to be used - // other option is to convert on entry, but then need to correctly identify/handel old data - return treatment.glucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); + var treatmentGlucose = null; + + if (isNaN(treatment.glucose)) { + if (treatment.glucose && treatment.units) { + if (treatment.units != browserSettings.units && treatment.units != 'mmol') { + //BG is in mg/dl and display in mmol + treatmentGlucose = scaleBg(treatment.glucose); + } else if (treatment.units != browserSettings.units && treatment.units == 'mmol') { + //BG is in mmol and display in mg/dl + treatmentGlucose = Math.round(treatment.glucose * 18) + } + } else { + //no units, assume everything is the same + treatmentGlucose = treatment.glucose; + } + } + + return treatmentGlucose || scaleBg(calcBGByTime(treatment.created_at.getTime())); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From d2be75a59a4b1ba223f8039f648bffbe7c29fee0 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 12:05:22 -0700 Subject: [PATCH 145/714] use an updated fontello font to include an hourglass glyph instead of using the unicode hourglass and hoping it works out --- static/css/main.css | 24 +++++++++++++++++++++++ static/glyphs/config.json | 6 ++++++ static/glyphs/css/fontello-codes.css | 3 ++- static/glyphs/css/fontello-embedded.css | 15 +++++++------- static/glyphs/css/fontello-ie7-codes.css | 3 ++- static/glyphs/css/fontello-ie7.css | 3 ++- static/glyphs/css/fontello.css | 15 +++++++------- static/glyphs/demo.html | 3 +++ static/glyphs/font/fontello.eot | Bin 7240 -> 7368 bytes static/glyphs/font/fontello.svg | 7 ++++--- static/glyphs/font/fontello.ttf | Bin 7072 -> 7200 bytes static/glyphs/font/fontello.woff | Bin 4120 -> 4076 bytes static/js/client.js | 7 +++++-- 13 files changed, 64 insertions(+), 22 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 83004ec5111..ccd193525ce 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -59,6 +59,10 @@ body { vertical-align: middle; } +.bgStatus .currentBG.icon-hourglass-1 { + font-size: 90px; +} + .bgStatus .currentDirection { font-weight: normal; text-decoration: none; @@ -259,6 +263,10 @@ div.tooltip { line-height: 80px; } + .bgStatus .currentBG.icon-hourglass-1 { + font-size: 70px; + } + .bgStatus .currentDirection { font-size: 70px; line-height: 70px; @@ -297,6 +305,10 @@ div.tooltip { line-height: 60px; } + .bgStatus .currentBG.icon-hourglass-1 { + font-size: 50px; + } + .bgStatus .currentDirection { font-size: 50px; line-height: 50px; @@ -345,6 +357,10 @@ div.tooltip { line-height: 70px; } + .bgStatus .currentBG.icon-hourglass-1 { + font-size: 60px; + } + .bgStatus .currentDirection { font-size: 60px; line-height: 60px; @@ -435,6 +451,10 @@ div.tooltip { line-height: 80px; } + .bgStatus .currentBG.icon-hourglass-1 { + font-size: 70px; + } + .bgStatus .currentDirection { font-size: 70px; line-height: 70px; @@ -462,6 +482,10 @@ div.tooltip { line-height: 60px; } + .bgStatus .currentBG.icon-hourglass-1 { + font-size: 60px; + } + .bgStatus .currentDirection { font-size: 50px; line-height: 50px; diff --git a/static/glyphs/config.json b/static/glyphs/config.json index ebfdc8a85a4..c146c6b72ef 100644 --- a/static/glyphs/config.json +++ b/static/glyphs/config.json @@ -60,6 +60,12 @@ "code": 59392, "src": "mfglabs" }, + { + "uid": "35ce53990ec0892d7dcea2d0159f8dca", + "css": "hourglass-1", + "code": 59405, + "src": "mfglabs" + }, { "uid": "55e2ff85b1c459c383f46da6e96014b0", "css": "plus", diff --git a/static/glyphs/css/fontello-codes.css b/static/glyphs/css/fontello-codes.css index d38f9fe8e1e..016749baf91 100644 --- a/static/glyphs/css/fontello-codes.css +++ b/static/glyphs/css/fontello-codes.css @@ -10,4 +10,5 @@ .icon-battery-100:before { content: '\e808'; } /* '' */ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ -.icon-plus:before { content: '\e80b'; } /* '' */ \ No newline at end of file +.icon-plus:before { content: '\e80b'; } /* '' */ +.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-embedded.css b/static/glyphs/css/fontello-embedded.css index c280a635980..8b461774fe8 100644 --- a/static/glyphs/css/fontello-embedded.css +++ b/static/glyphs/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?87362083'); - src: url('../font/fontello.eot?87362083#iefix') format('embedded-opentype'), - url('../font/fontello.svg?87362083#fontello') format('svg'); + src: url('../font/fontello.eot?87913446'); + src: url('../font/fontello.eot?87913446#iefix') format('embedded-opentype'), + url('../font/fontello.svg?87913446#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,d09GRgABAAAAABAYAA4AAAAAG6AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJMmNtYXAAAAGIAAAAOgAAAUrQHBm3Y3Z0IAAAAcQAAAAUAAAAHAbX/wRmcGdtAAAB2AAABPkAAAmRigp4O2dhc3AAAAbUAAAACAAAAAgAAAAQZ2x5ZgAABtwAAAYuAAAKpDGZAM9oZWFkAAANDAAAADYAAAA2AyAbomhoZWEAAA1EAAAAHgAAACQHlwNeaG10eAAADWQAAAAhAAAANC63AABsb2NhAAANiAAAABwAAAAcEegULm1heHAAAA2kAAAAIAAAACABFAoebmFtZQAADcQAAAF3AAACzcydGhxwb3N0AAAPPAAAAIEAAAC+QU1rOHByZXAAAA/AAAAAVgAAAFaSoZr/eJxjYGSewTiBgZWBg6mKaQ8DA0MPhGZ8wGDIyMTAwMTAysyAFQSkuaYwOLxgeMHNHPQ/iyGKOYhhOlCYESQHAPOwC9l4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF5w//8PUvCCAURLMELVAwEjG8OIBwBwBga5AAB4nGNgQANGDEbMQf8zQRgAEcoD33icnVXZdtNWFJU8ZHASOmSgoA7X3DhQ68qEKRgwaSrFdiEdHAitBB2kDHTkncc+62uOQrtWH/m07n09JLR0rbYsls++R1tn2DrnRhwjKn0aiGvUoZKXA6msPZZK90lc13Uvj5UMBnFdthJPSZuonSRKat3sUC7xWOsqWSdYJ+PlIFZPVZ5noAziFB5lSUQbRBuplyZJ4onjJ4kWZxAfJUkgJaMQp9LIUEI1GsRS1aFM6dCr1xNx00DKRqMedVhU90PFJ8c1p9SsA0YqVznCFevVRr4bpwMve5DEOsGzrYcxHnisfpQqkIqR6cg/dkpOlIaBVHHUoVbi6DCTX/eRTCrNQKaMYkWl7oG43f102xYxPXQ6vi5KlUaqurnOKJrt0fGogygP2cbppNzQ2fbw5RlTVKtdcbPtQGYNXErJbHSfRAAdJlLj6QFONZwCqRn1R8XZ588BEslclKo8VTKHegOZMzt7cTHtbiersnCknwcyb3Z2452HQ6dXh3/R+hdM4cxHj+Jifj5C+lBqfiJOJKVGWMzyp4YfcVcgQrkxiAsXyuBThDl0RdrZZl3jtTH2hs/5SqlhPQna6KP4fgr9TiQrHGdRo/VInM1j13Wt3GdQS7W7Fzsyr0OVIu7vCwuuM+eEYZ4WC1VfnvneBTT/Bohn/EDeNIVL+5YpSrRvm6JMu2iKCu0SVKVdNsUU7YoppmnPmmKG9h1TzNKeMzLj/8vc55H7HN7xkJv2XeSmfQ+5ad9HbtoPkJtWITdtHblpLyA3rUZu2lWjOnYEGgZpF1IVQdA0svph3Fab9UDWjDR8aWDyLmLI+upER521tcofxX914gsHcmmip7siF5viLq/bFj483e6rj5pG3bDV+MaR8jAeRnocmtBZ+c3hv+1N3S6a7jKqMugBFUwKwABl7UAC0zrbCaT1mqf48gdgXIZ4zkpDtVSfO4am7+V5X/exOfG+x+3GLrdcd3kJWdYNcmP28N9SZKrrH+UtrVQnR6wrJ49VaxhDKrwour6SlHu0tRu/KKmy8l6U1srnk5CbPYMbQlu27mGwI0xpyiUeXlOlKD3UUo6yQyxvKco84JSLC1qGxLgOdQ9qa8TpoXoYGwshhqG0vRBwSCldFd+0ynfxHqtr2Oj4xRXh6XpyEhGf4ir7UfBU10b96A7avGbdMoMpVaqn+4xPsa/b9lFZaaSOsxe3VAfXNOsaORXTT+Rr4HRvOGjdAz1UfDRBI1U1x+jGKGM0ljXl3wR0MVZ+w2jVYvs93E+dpFWsuUuY7JsT9+C0u/0q+7WcW0bW/dcGvW3kip8jMb8tCvw7B2K3ZA3UO5OBGAvIWdAYxhYmdxiug23EbfY/Jqf/34aFRXJXOxq7eerD1ZNRJXfZ8rjLTXZZ16M2R9VOGvsIjS0PN+bY4XIstsRgQbb+wf8x7gF3aVEC4NDIZZiI2nShnurh6h6rsW04VxIBds2x43QAegAuQd8cu9bzCYD13CPnLsB9cgh2yCH4lByCz8i5BfA5OQRfkEMwIIdgl5w7AA/IIXhIDsEeOQSPyNkE+JIcgq/IIYjJIUjIuQ3wmByCJ+QQfE0OwTdGrk5k/pYH2QD6zqKbQKmdGhzaOGRGrk3Y+zxY9oFFZB9aROqRkesT6lMeLPV7i0j9wSJSfzRyY0L9iQdL/dkiUn+xiNRnxpeZIymvDp7zjg7+BJfqrV4AAAAAAQAB//8AD3icrVZNbBvHFX5vZnZm+U+ay12KpGj+SEtFlCiHpLhKTNMryalsWYGiSBFkiZXlxn9Ra6CHxHbhwgiKAkUPtVugQB2iTVzDuQSFkfbWHtogRpAccnEuBWqnxyKHnnWKqb4VaztNhQZKQxIzb9/ycb/vez9DENvb2z/l7/AaxCAP0zAHn7jBAgrZeKrINcGmjr/re2HFHQfJBZcbIDQutLOgdMbUBgBwCXwNNNCVpreBIbJ5YAyXABkeTR9/10/BjV6wOL/HaNfZJVBX7PxXRZ444Qbi8bFMwkpaA75kOVZvMauaZWFeqDDHyLIWd+oVLITRsRrVhCEjSJ8w9mNtvN5wWngYvdWp28UCN8xqo8Lk7NXfnTp554dzSMZ7tF+cWP/Rz1/faOD6rT/fbv91/+gpgVJjE2bwNcnRUPGgjBu45C7GhqeHmf2tIVx/FP6XO1dnZ6/eOTnz4/azrLHx+rGTt9bXby2c6beEjprA/mAqOZjikqEuAkrGhopPH+n+LFcouAUcIO4MvGWFvQwJKMOwWxpE4BEUwKaAA98EELCpoUCxScLgKcNKGpYhE2Wn7mCjaqFpEOHCGCpvscd39ZYlCi1Mi5QZ2h/c16JaWu7qvKg8t9b9XIbJrd2/r2kZ+ana+doj54MHnvMJ9lcfYe9HDRQyjU0J9MAjUlY3QWPaJiX0lGUkLcPDHq9TUqr70fEW01AEteRh381blml66v0HWlRkPAi0E45dnfh99QRe2IPrwf70S06N1KSCA/h3rwQhCQMw6bZ0Ul1jQlsjCnSf8TWJQBTmaQNcBLqYSfWFQwi5/r6BVNGIhZJhS9cgiAFfpDxYNan6ivmCXW9YWDUNWdqxnR2b/BUqRD413GqOPLRHmq1h9reRZs9ujnh2a/ihXW5ic2S1OTrcai23EL/XWmphc7mJeKH1Erma6EnOd5a/s18R9gJUoek+QzpL1OQa1QjXGSUB1kAJoeZBKbEIQokZhKcrT9n5bJ9pxMMhJQl2kGA7BfsQ1htVsx8NSVAtIlHqYS/F6o1avmryWN2ma2nFDJOuG/cSudxwNsuujzSxVW67D+9OrmLbZYcmV89lzYd3EznMmuxQIvd+1ryXyGYTFIDPNMvnR1o42cbVKZyYbLe7P8EJM4u5RPcj+o5HSXyBVx+MwWGYcY+AAh2VviY4Qy3A/AJ8HjmflL558PnkIkifnPGy0jrYqFXKdjGfS6eMeKgv3NcjGaTcFGxn3CO5w27vjPGCx2kkg9nffx3ue5CBemr7Ff4ZX4ASHIBp1x0cKBZSfUnFMGEgkArRSFhJTSjgfCqGHCe9WblAheqfJhcskIYBODJWKQ/vz6ZEtIwJJZWlSjQViyW7pEqOPYbjTsMp0cA8jDXLtBxLmZaq2zREaUDyz/7oD1w++5axP3s9nTB/e+YHAf3DD1Xw8pmbkVzmesZMvXn2UkjdWr68xFZeXcW3r6XS1m9OX/QH3v9ABS6dvpnoy1zLJJJvnb6kB+/eVcHXTv86nXi5ubR0eWnJS7H2H3m2oUkn1YsuZRP86POvSY1zVGERYkEJAS/ZAV0PzEMgoC+CHtBn0ikv3cePTrsHJ2oHKqNDpXwuZaftLyY98r+S/vjO/1EBWNxxkLH8NUthT80hv6RZDY7BCfi2u0rJpvkTXKPDkwv0xWRURFhYh5AnXMjvD81DKORfBH/IPzNU6km38tILzx99zj108NnxemW0VBuq/beA+75CwNJjAUvfhJrveWzJ+MM3qOpeBPb67k/8KGeggwEH3FEjvi9GrRYKBvy+II3+KdY7DNiTw4BON0HDSeeRMtGOypwdbaBDHUbtRj3l4O07W1vd21tbKDrLnRsrnc7Kjc4yZz1fe6vb7izfuEF3vLU317cvUO/PU4eEwYSqO+aT3mOn4vv8XIBrRsJC4GHwgDxPAbhA8x7wuZ2e0mI8Wh6P1TCGg416yTEtQ1mNKBlR9U/8TvdNvHns2Mwr7LtjV67MdTrX8CbmP89ivjj78dzs5i9/cWbsEm5cme105zreTxOWTcLyItWX7RYJBf0hIgz8RG/SCBEQR3ZGTSDmvZRBz84/fuM5TOG57hvdf9x7ZHTfwHPwL3qsxksAAAABAAAAAQAAiGqKpl8PPPUACwPoAAAAANANCCEAAAAA0AzP4f///2kD6ANSAAAACAACAAAAAAAAeJxjYGRgYA76n8UQxfyCgeH/PyAJFEEBvACRPwX7AAB4nGN+wcDAvJKBgakJgplXAfE9KH6BxPaA8oEYAAMcCd0AAAAAAAAAANoBOAGWAfQCYALiA1QD7gSeBOAFJgVSAAEAAAANAFoABgAAAAAAAgAkADEAbgAAAG4JkQAAAAB4nHWQy2rCQBSG//HSi0JbWui2sypKabxgN4IgWHTTbqS4LTHGJBIzMhkFX6Pv0IfpS/RZ+puMpShNmMx3vjlz5mQAXOMbAvnzxJGzwBmjnAs4Rc9ykf7Zcon8YrmMKt4sn9C/W67gAYHlKm7wwQqidM5ogU/LAlfi0nIBF+LOcpH+0XKJ3LNcxq14tXxC71muYCJSy1Xci6+BWm11FIRG1gZ12W62OnK6lYoqStxYumsTKp3KvpyrxPhxrBxPLfc89oN17Op9uJ8nvk4jlciW09yrkZ/42jX+bFc93QRtY+ZyrtVSDm2GXGm18D3jhMasuo3G3/MwgMIKW2hEvKoQBhI12jrnNppooUOaMkMyM8+KkMBFTONizR1htpIy7nPMGSW0PjNisgOP3+WRH5MC7o9ZRR+tHsYT0u6MKPOSfTns7jBrREqyTDezs9/eU2x4WpvWcNeuS511JTE8qCF5H7u1BY1H72S3Ymi7aPD95/9+AN1fhEsAeJxtjVEKwjAQBXfbWDVtLR4kEIXgedJkqcI2CbVRvL2CtCA4P2/m60EBXyT8pwXAAksUuMEKt7jDPUqsscG2uRIn5W6TY/KdDQOT8jH3n8np+NM+PoMYKWTZ23mm6aXOZlWjSxeHNS+mXvSk9cHZ4IiXm+oROY8kEuc7wBvsNS0RAAAAS7gAyFJYsQEBjlm5CAAIAGMgsAEjRLADI3CyBCgJRVJEsgoCByqxBgFEsSQBiFFYsECIWLEGA0SxJgGIUVi4BACIWLEGAURZWVlZuAH/hbAEjbEFAEQAAA==') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSTIAAADsAAAAVmNtYXDQHBm3AAABRAAAAUpjdnQgBtf/BAAAEZgAAAAcZnBnbYoKeDsAABG0AAAJkWdhc3AAAAAQAAARkAAAAAhnbHlmMZkAzwAAApAAAAqkaGVhZAMgG6IAAA00AAAANmhoZWEHlwNeAAANbAAAACRobXR4LrcAAAAADZAAAAA0bG9jYRHoFC4AAA3EAAAAHG1heHABFAoeAAAN4AAAACBuYW1lzJ0aHAAADgAAAALNcG9zdEFNazgAABDQAAAAvnByZXCSoZr/AAAbSAAAAFYAAQOYAZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoCwNS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoC///AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABP///4kDqgMzABEAIQBDAEwA0kAMIgEEBjYpIwMFBAJCS7AJUFhANQAGAwQDBmAABAUDBAVmAAcIAgIHYAAAAAMGAANbAAUACAcFCFwAAgEBAk8AAgIBVAABAgFIG0uwClBYQDYABgMEAwYEaAAEBQMEBWYABwgCAgdgAAAAAwYAA1sABQAIBwUIXAACAQECTwACAgFUAAECAUgbQDcABgMEAwYEaAAEBQMEBWYABwgCCAcCaAAAAAMGAANbAAUACAcFCFwAAgEBAk8AAgIBVAABAgFIWVlACxMTLxwVFxgXJAkYKxE0PgIXMh4CDgMiLgI3FB4CPgM3NC4BIg4BNxc2MhUUBg8BBg8BDgEdATM1NDY3PgE/ATY3PgE3NCYjIgMUFjI2LgIGSn6sYV+ufEwBSn6swK58THY4XoKQgGA2AV6ivqRc1x8tYQQBBgUCOBYMdQYDARQHEwwGExQBVEBTESpDKgImRigBXl+ufEwBSn6sv65+Skp+rl9HhFw6AjZggElfol5eolFlHRcECAEFBAEdDBoYJRoDBgIBCAQLBwYRKCMxRP6NICIiQCIBJAAAAAIAAAAAAlgCYwAVACsAKkAnJQEAAw8BBAACQgADAANqAAAEAGoFAQQBBGoCAQEBYRQXGBQXFAYVKzc0NwE2MhcBFhQPAQYiLwEHBiIvASY1NDcBNjIXARYUDwEGIi8BBwYiLwEmKwYBBAUOBgEEBgYcBQ4G3NsFEAUbBgYBBAUOBgEEBgYcBQ4G3NsFEAUbBnYHBgEEBQX+/AYOBhwFBdvbBQUcBt0HBgEEBgb+/AYOBhwFBdzcBQUcBgAAAgAAAAACWAJ0ABUAKwAqQCcdAQUABwECBQJCBAEDAANqAQEABQBqAAUCBWoAAgJhFxQYFxQUBhUrEzQ/ATYyHwE3NjIfARYUBwEGIicBJjU0PwE2Mh8BNzYyHwEWFAcBBiInASYrBhsGDgbb3AUQBBwGBv78BRAE/vwGBhsGDgbb3AUQBBwGBv78BRAE/vwGAXAHBhwFBdzcBQUcBg4G/vwGBgEEBt0HBhwFBdzcBQUcBg4G/vwFBQEEBgADAAD/iQOqAzMADAAYACQAQUA+CAEEAAUCBAVbBwECAAMAAgNbBgEAAQEATwYBAAABUwABAAFHGhkODQEAIB0ZJBojFBENGA4XCAUADAELCQ8rJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAAMAAAAAA94ClwAMACIAMgA8QDkABQAGAQUGWwIBAQMIAgAHAQBbAAcEBAdPAAcHBFMABAcERwEAMS4pJiEeGRYUEw4NBwYADAEMCQ8rNyImPQE0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI20RUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAAAAQAAAAAA94ClwAMABkALwA/AEdARAAHAAgBBwhbBAMCAQULAgoEAAkBAFsACQYGCU8ACQkGUwAGCQZHDg0BAD47NjMuKyYjISAbGhQTDRkOGQcGAAwBDAwPKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAgAA/2kD6ANRACcAMABDQEAlJCMiGhkYBwIBFRQBAAQDAhAPDgcGBQQHAAMDQhEBAwFBAAICAVEAAQEKQwADAwBRAAAACwBELy4rKh8eGgQQKwEVBwYHFwcnBg8BIycmJwcnNyYvATU3NjcnNxc2PwEzFxYXNxcHFhcHNCYiDgEWMjYD6LkKC3hmnxQfHo8bFRahZXkLCMfHBwx4ZaAPIByPHBYanmZ3DQeiVnhUAlh0WgGljhobF51kdgoLwsUHC3dkoBUZHI4cFRifZHcIDMPDBwx1ZJwbFWM8VFR4VFQAAAAABQAAAAAD3gKXAAwAGQAmADwATABSQE8ACQAKAQkKWwYFAwMBBw4EDQIMBgALAQBbAAsICAtPAAsLCFMACAsIRxsaDg0BAEtIQ0A7ODMwLi0oJyEgGiYbJhQTDRkOGQcGAAwBDA8PKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BIxUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAAGAAAAAAPeApcADAAZACYAMwBJAFkAXUBaAAsADAELDFsIBwUDBAEJEQYQBA8CDggADQEAWwANCgoNTwANDQpTAAoNCkcoJxsaDg0BAFhVUE1IRUA9Ozo1NC4tJzMoMyEgGiYbJhQTDRkOGQcGAAwBDBIPKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImJzU0NjIWHQEUBiciJjc1NDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eAcAWHgEgKh4eshUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABOTwraCw+AUFcAVpCAThBXFz+hwE4Fh4BIBX+yBUeHgAAAgAA/7oDSAMCAAgAFAAwQC0UExIREA8ODQwLCgkMAQABQgIBAAEBAE8CAQAAAVMAAQABRwEABQQACAEIAw8rATIWEAYgJhA2ATcnBycHFwcXNxc3AaSu9vb+pPb2AQSaVpqYWJqaWJiaVgMC9v6k9vYBXPb+XJpWmJhWmphWmJhWAAAAAwAA/20D6ANPAAUADgAWADJALwkGAgEAAUITEgoDBABAFg8OBAQBPwAAAQEATQAAAAFRAgEBAAFFAAAABQAFEQMQKzURMwERASU2NCc3FhcUBxc2ECc3FhAH7AFi/p4BoElJR2kCay97e0yamo4BoAEh/B4BISNKzExKapSRZS93AWB7Spr+TJoAAAABAAD/agPoA1IACwAmQCMCAQAGBQIDBAADWQABAQpDAAQECwREAAAACwALEREREREHFCs1ESERIREhESERIREBZwEaAWf+mf7m0QEaAWf+mf7m/pkBZwAAAQAAAAEAAIhqiqZfDzz1AAsD6AAAAADQDQghAAAAANAMz+H///9pA+gDUgAAAAgAAgAAAAAAAAABAAADUv9qAFoD6AAA//4D6AABAAAAAAAAAAAAAAAAAAAADQPoAAADqQAAAoIAAAKCAAADqgAAA94AAAPeAAAD6AAAA94AAAPeAAADSAAAA+gAAAPoAAAAAAAAANoBOAGWAfQCYALiA1QD7gSeBOAFJgVSAAEAAAANAFoABgAAAAAAAgAkADEAbgAAAG4JkQAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTQgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADQAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0MaGVscC1jaXJjbGVkD2FuZ2xlLWRvdWJsZS11cBFhbmdsZS1kb3VibGUtZG93bgRtZW51CmJhdHRlcnktMjUKYmF0dGVyeS01MANjb2cKYmF0dGVyeS03NQtiYXR0ZXJ5LTEwMA5jYW5jZWwtY2lyY2xlZAZ2b2x1bWUEcGx1cwAAAAAAAQAB//8ADwAAAAAAAAAAAAAAAAAAAAAAMgAyA1L/aQNS/2mwACywIGBmLbABLCBkILDAULAEJlqwBEVbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILAKRWFksChQWCGwCkUgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7AAK1lZI7AAUFhlWVktsAIsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAMsIyEjISBksQViQiCwBiNCsgoAAiohILAGQyCKIIqwACuxMAUlilFYYFAbYVJZWCNZISCwQFNYsAArGyGwQFkjsABQWGVZLbAELLAHQyuyAAIAQ2BCLbAFLLAHI0IjILAAI0JhsIBisAFgsAQqLbAGLCAgRSCwAkVjsAFFYmBEsAFgLbAHLCAgRSCwACsjsQIEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCCyxBQVFsAFhRC2wCSywAWAgILAJQ0qwAFBYILAJI0JZsApDSrAAUlggsAojQlktsAosILgEAGIguAQAY4ojYbALQ2AgimAgsAsjQiMtsAssS1RYsQcBRFkksA1lI3gtsAwsS1FYS1NYsQcBRFkbIVkksBNlI3gtsA0ssQAMQ1VYsQwMQ7ABYUKwCitZsABDsAIlQrEJAiVCsQoCJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsAkqISOwAWEgiiNhsAkqIRuxAQBDYLACJUKwAiVhsAkqIVmwCUNHsApDR2CwgGIgsAJFY7ABRWJgsQAAEyNEsAFDsAA+sgEBAUNgQi2wDiyxAAVFVFgAsAwjQiBgsAFhtQ0NAQALAEJCimCxDQUrsG0rGyJZLbAPLLEADistsBAssQEOKy2wESyxAg4rLbASLLEDDistsBMssQQOKy2wFCyxBQ4rLbAVLLEGDistsBYssQcOKy2wFyyxCA4rLbAYLLEJDistsBkssAgrsQAFRVRYALAMI0IgYLABYbUNDQEACwBCQopgsQ0FK7BtKxsiWS2wGiyxABkrLbAbLLEBGSstsBwssQIZKy2wHSyxAxkrLbAeLLEEGSstsB8ssQUZKy2wICyxBhkrLbAhLLEHGSstsCIssQgZKy2wIyyxCRkrLbAkLCA8sAFgLbAlLCBgsA1gIEMjsAFgQ7ACJWGwAWCwJCohLbAmLLAlK7AlKi2wJywgIEcgILACRWOwAUViYCNhOCMgilVYIEcgILACRWOwAUViYCNhOBshWS2wKCyxAAVFVFgAsAEWsCcqsAEVMBsiWS2wKSywCCuxAAVFVFgAsAEWsCcqsAEVMBsiWS2wKiwgNbABYC2wKywAsANFY7ABRWKwACuwAkVjsAFFYrAAK7AAFrQAAAAAAEQ+IzixKgEVKi2wLCwgPCBHILACRWOwAUViYLAAQ2E4LbAtLC4XPC2wLiwgPCBHILACRWOwAUViYLAAQ2GwAUNjOC2wLyyxAgAWJSAuIEewACNCsAIlSYqKRyNHI2EgWGIbIVmwASNCsi4BARUUKi2wMCywABawBCWwBCVHI0cjYbAGRStlii4jICA8ijgtsDEssAAWsAQlsAQlIC5HI0cjYSCwBCNCsAZFKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgsAhDIIojRyNHI2EjRmCwBEOwgGJgILAAKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwgGJhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwgGJgIyCwACsjsARDYLAAK7AFJWGwBSWwgGKwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbAyLLAAFiAgILAFJiAuRyNHI2EjPDgtsDMssAAWILAII0IgICBGI0ewACsjYTgtsDQssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbABRWMjIFhiGyFZY7ABRWJgIy4jICA8ijgjIVktsDUssAAWILAIQyAuRyNHI2EgYLAgYGawgGIjICA8ijgtsDYsIyAuRrACJUZSWCA8WS6xJgEUKy2wNywjIC5GsAIlRlBYIDxZLrEmARQrLbA4LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrEmARQrLbA5LLAwKyMgLkawAiVGUlggPFkusSYBFCstsDossDEriiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSYBFCuwBEMusCYrLbA7LLAAFrAEJbAEJiAuRyNHI2GwBkUrIyA8IC4jOLEmARQrLbA8LLEIBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAZFKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7CAYmAgsAArIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbCAYmGwAiVGYTgjIDwjOBshICBGI0ewACsjYTghWbEmARQrLbA9LLAwKy6xJgEUKy2wPiywMSshIyAgPLAEI0IjOLEmARQrsARDLrAmKy2wPyywABUgR7AAI0KyAAEBFRQTLrAsKi2wQCywABUgR7AAI0KyAAEBFRQTLrAsKi2wQSyxAAEUE7AtKi2wQiywLyotsEMssAAWRSMgLiBGiiNhOLEmARQrLbBELLAII0KwQystsEUssgAAPCstsEYssgABPCstsEcssgEAPCstsEgssgEBPCstsEkssgAAPSstsEossgABPSstsEsssgEAPSstsEwssgEBPSstsE0ssgAAOSstsE4ssgABOSstsE8ssgEAOSstsFAssgEBOSstsFEssgAAOystsFIssgABOystsFMssgEAOystsFQssgEBOystsFUssgAAPistsFYssgABPistsFcssgEAPistsFgssgEBPistsFkssgAAOistsFossgABOistsFsssgEAOistsFwssgEBOistsF0ssDIrLrEmARQrLbBeLLAyK7A2Ky2wXyywMiuwNystsGAssAAWsDIrsDgrLbBhLLAzKy6xJgEUKy2wYiywMyuwNistsGMssDMrsDcrLbBkLLAzK7A4Ky2wZSywNCsusSYBFCstsGYssDQrsDYrLbBnLLA0K7A3Ky2waCywNCuwOCstsGkssDUrLrEmARQrLbBqLLA1K7A2Ky2wayywNSuwNystsGwssDUrsDgrLbBtLCuwCGWwAyRQeLABFTAtAAAAS7gAyFJYsQEBjlm5CAAIAGMgsAEjRLADI3CyBCgJRVJEsgoCByqxBgFEsSQBiFFYsECIWLEGA0SxJgGIUVi4BACIWLEGAURZWVlZuAH/hbAEjbEFAEQAAA==') format('truetype'); + src: url('data:application/octet-stream;base64,d09GRgABAAAAAA/sAA4AAAAAHCAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJHGNtYXAAAAGIAAAAOwAAAVLoKenZY3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAAAVWAAAJFvIYbztoZWFkAAAMxAAAADQAAAA2BVrRQGhoZWEAAAz4AAAAHgAAACQHlwNfaG10eAAADRgAAAAlAAAAODEAAABsb2NhAAANQAAAAB4AAAAeEUkOdm1heHAAAA1gAAAAIAAAACAAqgv6bmFtZQAADYAAAAF3AAACzcydGx1wb3N0AAAO+AAAAIoAAADMdW0ycXByZXAAAA+EAAAAZQAAAHvdawOFeJxjYGRuYJzAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvOBlDvqfxRDFHMQwHSjMCJIDAOwOC8N4nGNgYGBmgGAZBkYGEPAB8hjBfBYGAyDNAYRMIIkX3C94//8HsxggLAlGCQaoLjBgZGMY8QAA7+0I0AAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icrVVLbBtVFH13Pm8c/2I384vjTOyxMxM5zs+fmVCcqdNP2qRWoZBGITJpSpOGFiqxaJugoqhCSIgFLUhIpBa0IZQNQpEAIQQLqBqhskCqukIiEUvUBeusGoc7TltaFIFS6mc933fGc9+59517H+E2NjbeYT9nsyRM4mQPKZFA0XtwoL9HkwMc19EezjmMktGYIKt3MraoMQ5r5zpBD4KtWBlJpPWA3yA0Qzafs2wHdoE72zkjobOinLE6GTo098Wxo0tvlACN6/h7rnf8zfcuTFgwvvjDtfKvLR3HOKA80yv7z1IWRKHBTxtEGC4+H07tSTHGQBuM33/9x6W5oaG5paP73yrvZKyJC4NHF8fHFw9PNSucB3gOmv0RtTXCUgY8nE+g4bZEz97quzFdL+qQJPhhatMoc5xIpJ14vjEjYY7paG8VMQ69CwR3MnI2WBkF5PyWKDNKo3yQrq7wIb6JUuBwARzdEjwnIMyvrPB8lLoAz1fv0ls1cHX1Pkgpgg9xO3OPmx7xu9wwI8jCRAKY20wL2O6E3LZCkVv1Lh/i0CFtQucrq7iI0i1BeE1wN3YJBP8mdMsFef5RkBCWkHs68ROVJIn32+bGcMDHs5i7jIw6SMR1I2cpkJFFatZsu2Yj3omSYHennEJ63UgXnBTzW7qwaRfSru2k1o32AhTSY4WOlOOMOACvOsMOFEYKAKedIwgVgNQ44PQ78yFy0EkGOXSamuynyMHWjT7IWRm5GUSK+yrIyNwkYoZzVjaekdlwzsA1VcKijGvrthSLpTSNuZQugNNeLq4v949Bucj09Y9Na/L6shQDTWb6pNgNTb4taZqEL8BThfaX0w70l2FsN/T2l8vVt6FX1iAmVX/G/xDCPcSxkXSRXW49PZ1rTzSiqrGeWnXDzrtEawy3zxpOu7zSUdC+fBz+2wnF1ePGSfYOe5iYpBv12JnSvK4eJYEKimBi3SdMwxRM2+iCvG3ZJraEXZBVZMVWBFkRcga2CWwB7J3vvL7ZE1fFFu1SkyR/MvW6z3PzpuCfnVqoj0UvReXIlRMzAWFxZHaYGT0zBp9djDQpH0+e8/pu/CT4ZiYXpMboxaikXp2c8fiXlwX/2cmPmqTjheHh2eFh5Mk/knODFLCL1Rf9B/p7u42Ym3f+3/L+4Mn/OARI1AA0Rh7zNLZzMPQf8WbJIHmBhIvBI4f29WU7NmOm/xGz+SBm80kk4LpLEI2vnmAitpMTV6vfswdYhniIiFoN+zjiajUjh2jMCFlgo1BRtShNG64tra1Vr62tAVcZqcyPViqj85URltnEymvVcmVkfh6fuPNm39k4jXXwDCotSGTsO3JD0Mux2Hfy4SyEodXKmbasiIJihdAICX/CS9UrsDA4uP8k80rX+fOlSuUiLED8rgbxxNAvpaFTH7w/1TUDE+eHKtVSBffA/rZxCvd4jvgI/bqOBdd3/MGAaYjAdPVy9Y/b943qZZjGuN37mxliMyRPZjDuCatHxrilfK52FdPaSJidjHv47lHi6QYBr7XNR3YLaKDca992NucItqJRBe9yrGwc6IZxb3Ss63xckpXaQA9MPUMlKlBJVSOZiCetq4WQrkZUtTsBnya6AeFskzcdEy1vTDUCMmT06ot6ZqqNC3rVoBbShcgOURN34PXXFvTo+R16MKmqQryOsnx1DvzeOELoUeUY0OsCrQOyZST3JWkpVKIHYq3JXmlA7+7Wa/DeJD0kPustQcxusGQX31cXEv2qJ0I1piHgj7hDSAhqsOdgvJiN9O7EPtYVrvMEEgmPHO46GN+VbbKcpM6xDPQEVYH8BUfov3MAAHicY2BkYGAA4sc6/E/i+W2+MnAzvwCKMFzUiobShjv+//+fyfyCOQjI5WBgAokCAGveDWx4nGNgZGBgDvqfxRDF/IKB4f8/IAkUQQF8AJFABfwAAHicY37BwMC8koGBqQmCmVcB8T0ofoHE9oDygZjJk4EBACtzCigAAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBIsAAAABAAAADgB4AAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAeJx1kc1Kw0AURr9pa9UWVBTceldSEdMf6EYQCpW60U2RbiWNaZKSZspkWuhr+A4+jC/hs/g1nYq0mJDMuWfu3LmZADjHNxQ2V5fPhhWOGG24hEM8OC7TPzqukJ8dH6COV8dV+jfHNdwiclzHBT5YQVWOGU3x6VjhTJ06LuFEXTku0985rpAfHB/gUr04rtIHjmsYqdxxHdfqq6/nK5NEsZVG/0Y6rXZXxivRVEnmp+IvbKxNLj2Z6MyGaaq9QM+2PAyjReqbbbgdR6HJE51J22tt1VOYhca34fu6er6MOtZOZGL0TAYuQ+ZGT8PAerG18/tm8+9+6ENjjhUMEh5VDAtBg/aGYwcttPkjBGNmCDM3WQky+EhpfCy4Ii5mcsY9PhNGGW3IjJTsIeB7tueHpIjrU1Yxe7O78Yi03iMpvLAvj93tZj2RsiLTL+z7b+85ltytQ2u5at2lKboSDHZqCM9jPTelCei94lQs7T2avP/5vh/gZIRNAHicbY3RCoJAEEXn6malZvQhCxpI37Ougwbjrqhb9PcFoRB0Xu45T5ci+pLSfwoiRIihsEOCPQ44IkWGHCcUec8yanufrHB7Nq4T1q0PzWfCePnp1j+dGtiFtDHLwtNLX+tN6zK2vtvyVmerVmVZWOMsy3qTPLyEgdUoYc56H6ZOzDzriugNN6oxYgAAeJxj8N7BcCIoYiMjY1/kBsadHAwcDMkFGxlYnTYyMGhBaA4UeicDAwMnMouZwWWjCmNHYMQGh46IjcwpLhvVQLxdHA0MjCwOHckhESAlkUCwkYFHawfj/9YNLL0bmRhcAAfTIrgAAAA=') format('woff'), + url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSRwAAADsAAAAVmNtYXDoKenZAAABRAAAAVJjdnQgAAAAAAAAECgAAAAKZnBnbYiQkFkAABA0AAALcGdhc3AAAAAQAAAQIAAAAAhnbHlm8hhvOwAAApgAAAkWaGVhZAVa0UAAAAuwAAAANmhoZWEHlwNfAAAL6AAAACRobXR4MQAAAAAADAwAAAA4bG9jYRFJDnYAAAxEAAAAHm1heHAAqgv6AAAMZAAAACBuYW1lzJ0bHQAADIQAAALNcG9zdHVtMnEAAA9UAAAAzHByZXDdawOFAAAbpAAAAHsAAQOAAZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoDQNS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEwAAwABAAAAHAAEADAAAAAIAAgAAgAAAADoC+gN//8AAAAA6ADoDf//AAAYARgAAAEAAAAAAAAAAAAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE////iQOqAzMAEQAhAEMATAANQApLRkExHhYNBAQtKxE0PgIXMh4CDgMiLgI3FB4CPgM3NC4BIg4BNxc2MhUUBg8BBg8BDgEdATM1NDY3PgE/ATY3PgE3NCYjIgMUFjI2LgIGSn6sYV+ufEwBSn6swK58THY4XoKQgGA2AV6ivqRc1x8tYQQBBgUCOBYMdQYDARQHEwwGExQBVEBTESpDKgImRigBXl+ufEwBSn6sv65+Skp+rl9HhFw6AjZggElfol5eolFlHRcECAEFBAEdDBoYJRoDBgIBCAQLBwYRKCMxRP6NICIiQCIBJAAAAAACAAAAAAJYAmMAFQArAAi1JxoRBAItKyUUDwEGIi8BBwYiLwEmNDcBNjIXARY1FA8BBiIvAQcGIi8BJjQ3ATYyFwEWAlgGHAUOBtzbBRAFGwYGAQQFDgYBBAYGHAUOBtzbBRAFGwYGAQQFDgYBBAZ2BwYcBQXb2wUFHAYOBgEEBQX+/AbPBwYcBQXc3AUFHAYOBgEEBgb+/AYAAAACAAAAAAJYAnQAFQArAAi1IhoMBAItKwEUBwEGIicBJjQ/ATYyHwE3NjIfARY1FAcBBiInASY0PwE2Mh8BNzYyHwEWAlgG/vwFEAT+/AYGGwYOBtvcBRAEHAYG/vwFEAT+/AYGGwYOBtvcBRAEHAYBcAcG/vwGBgEEBg4GHAUF3NwFBRwGzwcG/vwFBQEEBg4GHAUF3NwFBRwGAAADAAD/iQOqAzMADAAYACQACrcdGRENCwUDLSslMhYVFAYjISImNDYXATIWFAYnISImNDY3ATIWFAYjISIuATY3A0IqPjws/SYsPD4qAtosPDws/SYsPDwsAtosPD4q/SYrPAE8LFo8LSo+PlY+AQFsPlQ+ATxWPAEBbT5VPj5WPAEAAAADAAAAAAPeApcADAAiADIACrcuJx4WDAYDLSs3IiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjbRFSAgKh4eAo8sPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAQAAAAAA94ClwAMABkALwA/AA1ACjs0KyMZEwwGBC0rJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eAo8sPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABOTwraCw+AUFcAVpCAThBXFz+hwE4Fh4BIBX+yBUeHgAAAAIAAP9pA+gDUQAnADAACLUuKh4KAi0rARUHBgcXBycGDwEjJyYnByc3Ji8BNTc2Nyc3FzY/ATMXFhc3FwcWFwc0JiIOARYyNgPouQoLeGafFB8ejxsVFqFleQsIx8cHDHhloA8gHI8cFhqeZncNB6JWeFQCWHRaAaWOGhsXnWR2CgvCxQcLd2SgFRkcjhwVGJ9kdwgMw8MHDHVknBsVYzxUVHhUVAAAAAUAAAAAA94ClwAMABkAJgA8AEwAD0AMSEE4MCYgGRMMBgUtKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BIxUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAYAAAAAA94ClwAMABkAJgAzAEkAWQARQA5VTkU9My0mIBkTDAYGLSslIiY3NTQ2MhYXFRQGJyImPQE0NjIWHQEUBiUiJic1NDYyFh0BFAYnIiY3NTQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgHAFh4BICoeHrIVIAEeLB4eAVYsPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAIAAP+6A0gDAgAIABQACLURCwQAAi0rATIWEAYgJhA2ATcnBycHFwcXNxc3AaSu9vb+pPb2AQSaVpqYWJqaWJiaVgMC9v6k9vYBXPb+XJpWmJhWmphWmJhWAAAAAwAA/20D6ANPAAUADgAWAAq3FhMOCgQDAy0rNREzAREBJTY0JzcWFxQHFzYQJzcWEAfsAWL+ngGgSUlHaQJrL3t7TJqajgGgASH8HgEhI0rMTEpqlJFlL3cBYHtKmv5MmgAAAAEAAP9qA+gDUgALAAazCQMBLSs1ESERIREhESERIREBZwEaAWf+mf7m0QEaAWf+mf7m/pkBZwAAAv///4kCSgMyADUAdwAItWA2MRYCLSsVNTQ+AT8BNgY2BjYGNiMnLgI9ATQ2MyEyFhcVFA4BDwEGNgY2BjYGNx8BHgEXFRQGIyEiJjczND4HNxceBhczNTQnJicmJyY1ND4CPwE2NzY/ATUhFRYXFhcWFxYVFA4CDwIGFQYHBhUYGBoyGggsIhg8ECIYGhgYMCMBoyMwARgYGjMbCiwgFDYKIBgmDRYBMiL+XSIyZSgEDgoYDh4QIgcaEhQeFBIOBgEoDggiNRIiDiQYGAchCQYDBf5+AQwKITUSIhAiGBgEAgEiCQ0lRhY2JiRFJAZMEEwGSCAlJDgVRiIwMCJGFjYmJEQkBk4UUApMASA3EzYWRiIwMEUJEBQMGAgaBh4CEw0MGgwaDBoHIwcYDjFLIUAzGjg6Kh4KLxEJCA0jIwgWES9LIT8zGzY+JCIEAwIBMQ4YBwAAAAABAAAAAQAA4ywP5F8PPPUACwPoAAAAANEqW+gAAAAA0SoxuP///2kD6ANSAAAACAACAAAAAAAAAAEAAANS/2oAWgPoAAD//gPoAAEAAAAAAAAAAAAAAAAAAAAOA+gAAAOpAAACggAAAoIAAAOqAAAD3gAAA94AAAPoAAAD3gAAA94AAANIAAAD6AAAA+gAAAJJAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBIsAAAABAAAADgB4AAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE1IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA1ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4MaGVscC1jaXJjbGVkD2FuZ2xlLWRvdWJsZS11cBFhbmdsZS1kb3VibGUtZG93bgRtZW51CmJhdHRlcnktMjUKYmF0dGVyeS01MANjb2cKYmF0dGVyeS03NQtiYXR0ZXJ5LTEwMA5jYW5jZWwtY2lyY2xlZAZ2b2x1bWUEcGx1cwtob3VyZ2xhc3MtMQAAAAEAAf//AA8AAAAAAAAAAAAAAACwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7AAYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7AAYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsQAAKrEABUKxAAgqsQAFQrEACCqxAAVCuQAAAAkqsQAFQrkAAAAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmxAAwquAH/hbAEjbECAEQA') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?87362083#fontello') format('svg'); + src: url('../font/fontello.svg?87913446#fontello') format('svg'); } } */ @@ -63,4 +63,5 @@ .icon-battery-100:before { content: '\e808'; } /* '' */ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ -.icon-plus:before { content: '\e80b'; } /* '' */ \ No newline at end of file +.icon-plus:before { content: '\e80b'; } /* '' */ +.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7-codes.css b/static/glyphs/css/fontello-ie7-codes.css index c50afefb973..0ac7ae656c9 100644 --- a/static/glyphs/css/fontello-ie7-codes.css +++ b/static/glyphs/css/fontello-ie7-codes.css @@ -10,4 +10,5 @@ .icon-battery-100 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-hourglass-1 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7.css b/static/glyphs/css/fontello-ie7.css index 79ca9ab5894..a30561edd64 100644 --- a/static/glyphs/css/fontello-ie7.css +++ b/static/glyphs/css/fontello-ie7.css @@ -21,4 +21,5 @@ .icon-battery-100 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } +.icon-hourglass-1 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello.css b/static/glyphs/css/fontello.css index 8920cc801a7..0682d327830 100644 --- a/static/glyphs/css/fontello.css +++ b/static/glyphs/css/fontello.css @@ -1,10 +1,10 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?48374311'); - src: url('../font/fontello.eot?48374311#iefix') format('embedded-opentype'), - url('../font/fontello.woff?48374311') format('woff'), - url('../font/fontello.ttf?48374311') format('truetype'), - url('../font/fontello.svg?48374311#fontello') format('svg'); + src: url('../font/fontello.eot?73606746'); + src: url('../font/fontello.eot?73606746#iefix') format('embedded-opentype'), + url('../font/fontello.woff?73606746') format('woff'), + url('../font/fontello.ttf?73606746') format('truetype'), + url('../font/fontello.svg?73606746#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -14,7 +14,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?48374311#fontello') format('svg'); + src: url('../font/fontello.svg?73606746#fontello') format('svg'); } } */ @@ -61,4 +61,5 @@ .icon-battery-100:before { content: '\e808'; } /* '' */ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ -.icon-plus:before { content: '\e80b'; } /* '' */ \ No newline at end of file +.icon-plus:before { content: '\e80b'; } /* '' */ +.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/demo.html b/static/glyphs/demo.html index cd689710d4a..77275cf1b47 100644 --- a/static/glyphs/demo.html +++ b/static/glyphs/demo.html @@ -272,6 +272,9 @@

icon-volume0xe80a
icon-plus0xe80b
+
+
icon-hourglass-10xe80d
+
diff --git a/static/glyphs/font/fontello.eot b/static/glyphs/font/fontello.eot index 25039a0de45c4c8a0b9d4f375828c4bb0a09e27c..7f16444450abb13e452fde359ccb148c33efc846 100644 GIT binary patch literal 7368 zcmd^DZERcDc|PY}@{%GcO0r0b5oP+4SE6L`l9DKiR2^EDNqzW3O=MZtNXNBFk(6Xd z5>=Xt7LNldKq!)L7drOMxvIfX;3G7hRH1V_}RYL=;4!K&3=vFd|@@ee*5sh{|k)9@tZAN-BO{)r@03Y+v57t z>a#DrkcB+Nn0V-23=Qod+D|G^k+1?uC=P*`4+ zFG0QyxpR4S>-qoyV-5!%BP-=XzU8FzDrAh${Hyuv>+CiAe?cxnR@d^YrT4$*I|%uk z7~>z;%bQ!;L+s{4K_OuR?x`arqhk0y8tK^xM*3LGuUIlK+wo za4gf^HA3CK&&=XPk&WGEw;SczJ6G6wd6vBhgZhFoc(3~FsOhGmq1p~+GMsN^)@mT! zw%=~NbB8*o-UxZQ_uifuSE%;?{l|3Y&YjL-E8YKgQ{q7z$}Y+2x7F zqmpv!>A%Qddi{5%Ilwz8uZ~>&#tYBn;{4((fBou(e?An+o48_@Mm&yfMdmI`t3zpZ z@wu_HExqYpDKy!`FV>s?&FfE}I`#DHm!`gX;fNH^J#+HXD;F=ma;9{!&1B_f6F=x^ z_ja|*io~rZyG3c~(FVqMzpVy?V?o|Yi*2L{DI*n_n}wP6&F=OVlN1SexiF7lAGgpW zG?d`+SR3~YKU6Daln%2~`RGG)v)QL8+ysG}l!q#>TA((6_@UX{p^%l?y!(mrPlocN zkLpTAf$|=1TQzRMb_a2TN5HKcwBXy=A)cTIxIIh@Zo8kDn@#9Y@nOUdKZ4Lf13akY z>lPZAxI2kul@Q3m*t}n9?7CWRKwyp6{^E~VSR z2?IkPMM%i0-qED~X-H3w_DVn3lU1D536J)E8VVQ^b7(zLVbKV5l?g@B^={}ZJxG7o5j;+844jyAO*;`-?rJWFaE%F$p5_0 z?fH|^4ZHQHKeafnmtJmCJD%_Gw10o`TBGHa`Rj91X6rov;dAZ2w(k|M+U!61JB$5V z@n!cT9nW>Ry+0^kvpU{=*W%bN{;|(pNY2e&pQ97VywB$$mP8h78graDKGGjj$?xXh z7LcN2Yzmh87J3XjD^@BJIS)FWz2c*v5BLDk?Ne4 z&zW<#5rs78?s`c6ZyfLrZ31kkTmOfS_#!@`SpBhlLY8m?a3NM(>?TIB8uK(OYN$ER z69`m9EkZNFUw!@Kk9S}F_+xImIe+t|%*~sbmu}9>QdM{12-om@$L){5ao6FJ`Zw6?#{F*Q9exJX5^5oQI>B+vQ zo|?XS^Ev)94}9Y10qxZL)2FWd(I1xjuJPPcr*7^}-^3WmZ+EU>j9F%9${P(br@jMw zU%tfK`O@xpcmM5!I@tX#U&47t=eKl9j$2RxX!dw9q=ij_EhweF}y>5*r7p`fR%DirZ9gsL{h?!QHO~W2GLG)8=*hn}e42 z11`Vo0M5i7r!_cyAn5G$dM$wlMKhVtHG1rW3nyZP{xQFx;4b3ix*V?Z5rPfAAJAEyh#p@h69T*#IA31_N z*4NTtZPYZYr={<7;MkxqKH3>H$r2xMdM%Vr=)A%2SNdZ=yVR8YPiB|t?#w>ueWF$l zyamrwcASN}cI&LRsoh;+cV`!^sHb~BPU=bib1dCAsz3QPIx%WLs;lD@z$D`*ovd|n zo&7y`@p=Adl2clcel8c~@5p~E|I+mPsHc&f>^iI7Z0V-cj29Hff_8depw9vJi#~u6Zfd|dSJ)40 zn6qZyRKpT;@8fAa=x^& zdAYo%4n+Iws;SaiX(PW?DpK#8SC?X2TZ`)AMtN18to5s|ZZ>v#OK>g|i`u%UP_qV!Ve)!w%{Z>df9&OtC;73^GQBCD#W-(t=)r-gF%d`1lUL{B!isP3H02&pUoHof!~ z3fh7%5D+XYSXvvuh5pjHR77xHsJX?6kaV@Eig#v&DRll;54WW!3KO$WWCB{ix06xD z%uFUA#frPgEH_aQa&G|ow7R42pzlh*xeL=elgZ8a^0V2DmPPH@*$k?DG^yIi5nB3tkwg3~*9Lr_NBsXr5wCcAkc9xz< z^#=Bw-JtJxKF&Ytg@-{QjTz_Ei5)FZ3t@PQ`N*3>^?_uarI5R{{CIT;`)4#4ooI!| zdsDl&k%MAJOWm^DkfT$FrTHv7}v(h!*|ynat_4Ri!Tg zeXF5ApjTK^`dp^c)RY!HpAt=B3Q~k_s^XwWBOZc#uo80DOr}DS3umQvu)H*EZ$Lw{ zI`&oTsR?4AYO|Qh6!=f&u!#GX>N6Iw!dR;Y7bBWRZ*k6zXlT`0g-H{08PTMr)Cpln zP};C=xs;m2@NYIZbDXWI)K0E)KnaVDurCNVxiGiZa74KE3a8QoKU3MJS7a)^dc{QL zBYMS5WxHNcsPyR-3zZ#u#Y*Ktz0yFXUuU8*{3VDS0uettP(g&s0Ekc-1Q9AV5TUXY zM5ycn5h_CSoC7M=!n!Q zgw#X#i@=(h4F`xLOh6m{23;qAssI5L^V}S?x3&u-D)*j?L(N* zC#+PFqs=^aCi8}*%Bt@TDI~XNQZICg?Gb z#$5&pJaLRr3cEWNq9j2+P%*kWRP%wBglk3&wXkCMs_LXRMT605eZ_DYO`zscHkXO2 zNgRB{xTZvBcU`V@K{|=5y`#0dP!9%IPOGg9I7Y;g8iRCwX>xS--ap0qQa-9{YLvV- ziL9H6sH~*7+)s($ZwQalG=zbaL;-N zvNet=az@>$X_|k4JWmvB^Vpm6IHv5bZcZHK*oUoAF@U`|@frF_0$`Q z3G&(m{Hjjk%&Pn8q)y=|PQb0F^jnM_2RIGD2~6v^xS{zffT4MeY*MItoNNfpkPU$| zWJ6$wvLTQo8v=QZ1@AbuqGMy+ndxy=)+|zHA^e zy`qb+?3q4Ef?>L1ATnJw5Sgy&;>ez9nFPah-9Tjebpw&NtexD#h9%+dQ6zw<3&`Zi97arWOXzguen delta 3896 zcma)8TWnir9shsl>bv8{iIX^Ok8=_yi;tZ&_cm$k+Huo0E4j7BZg#PgrtPw(i&!;v z0?CySs6+zM9f=Jj5I6C_9{3zuHiQH`@UREaDtG|Vfd)@~KnMi9AVm26?I!D3xmfo3 zU%ub>zklEH+lSwtFiv$6eKmgD7}Jcq>$h%aKVBUoqCenwes!a=d9SnmO(HRk-|5wt zuUp11cQm5N>sar+w0U`B%XecE@*N`Y?aP&yHlc5V{CQB%<*Tn;I`l<)he(0!`{tFk z%0<sDgGU?KmTEbu&&>~ zuj`%0Z&+y59INl2>BUwpf(oZ5}y} zcn^wkezZnS1l`N=9v96YR;S#!o~XjP-lQP{v1xK(jTk~46UT-KKcLi6CNz%d;phaY zjk@VX_Lv9d)D=x%Mx0nz)MW!>Cuk0Y#1bY@%yhJef`Y95?cE)AG!kqJ#V|V*5Pomi z{iMD=$96GSzg1qN0Cmw2O=S-wQA6m)G8&_L6jXth$<#D+j!aWqBF)rh1RcsekxU$D zZ;dvGf~EnB0dF`p?*7Oaawm=R%iQ1h4Y$((9jBS>G@0ZPre|4G6`}hTpGID`?llbW z9C^LQ5*c1&hC@C!IX0Y0CGCV2?}#=B+e7UQFM+TtjW-t1ubt=B2kHX#(jeut*`98@ ztE0WmR76Wu5bQUFL#Cl?CaG#JB2;mLPs#%7*tpdUj!u!2K+v60F#qb8nXnlJ4-kL8rtVk!|p-f`JtigfY)GDu6dtN z)hOE<4r!V=4!oc<$V)6>f=Cp=>ZC{1Cb#N4pG;fZMyZN6ynyS0ATcrHBkH}b0wprLy~Poho(@BGgFL||M5-8+F1+~D~9T1<#9h`%aj z<@f4Y^^e+DwDQO_r%C|e-Sz@VDCUv zhkK;yW|mFzIl`SDuJeyRhGpt`U;hc zvTweakUsZXWIRl%QpuJyUtBAd(o%8nM69an*a-~##hSj7v!r3?wtb}ZC&1>dEo)0s zsy3+iY%LVa^YO}Jsc4t5mR%}hA)b)gU3+P%InvChwvl7LoJ&g`qMfrXNp`LxZ>~sD zmRf&W8jfWZEU6S$r6{bF_f2CVEzO3OQg%&o-`IDk>TzniUXY?Pot9n)7|W90{Amsz zjGZejldF08;lo3UR-~r(%ol3{`AV%P zw}ooKueU#8W7{qtZ>;kcrKi4HLLM`Sex{77ykCeKQL~L=PZj zGGOPdG7R4h;wbtlm)k1Wf_h4Bq~cvjKMY_fm6lCTO)!l(HHGOur=~J(c4`{aDC)|z z#ioy?{?Vny;vSV6ES=M(g3E9Fl5daxwqA4wlZBu%W&L9R-KG5sx{xl{txSp2Yc`DrIU(5k=d__tZG# z@2e;F)O9qrx_@sAbT{@KcI05{vDS__s7!x*#{>hBdqyVVV{)&SyXkT@ZFDMw#x-nC zVH|Ow-9NXsHp&`zztgTiQrhaxl?q4Cb2ep9#1oH{C{cQ75{2RaPyBMf3@ zjzP@KGl-c5HZ%!lkul6HF@~A5jA7;+V~&A2&lqMt${1#fjA5q4m?K~=Fov0BELH2O zry0bmXB;`SSLtV2kYi9kSLepTmFrE1dICblk;8lF6&C8~)jG%Mi*=5nYmOY*gI;2x z4!vBhvy8n`=NS9EBS-hJ>nzl zSNgt(JhW;_fwzHbZ*FUS-ooPawoftpYlfJvb&Fg53q_f|Rje86G=}ZgLJ==L7V&z+ R=Pj}GDXD$7rW5|!{0m^CP8a|H diff --git a/static/glyphs/font/fontello.svg b/static/glyphs/font/fontello.svg index 177af8dc21d..db5f72745d8 100644 --- a/static/glyphs/font/fontello.svg +++ b/static/glyphs/font/fontello.svg @@ -1,14 +1,14 @@ -Copyright (C) 2014 by original authors @ fontello.com +Copyright (C) 2015 by original authors @ fontello.com - - + + @@ -18,6 +18,7 @@ + \ No newline at end of file diff --git a/static/glyphs/font/fontello.ttf b/static/glyphs/font/fontello.ttf index fb8152f39f6e4db8d5621f32a0daf767cacfdc84..58c50077e546bf405ee8adfb50543e1e2bc98015 100644 GIT binary patch literal 7200 zcmd^DYiwIbcAmMHyrf8qk}Q&3D5YBqiC= zYxVG>DS}|Ji%r#Tk}eiV*4RgyW`Q;+f}zu($)ae3P21f9DT@4RyGa)d6ewI^e-)^I z6p7sLT$1uLnr9zHRa}Qp&h4sai=U;j$hx!m>_VvZW<~lXvPeo~6T)w{WAKtac z8IxXNtiiKXE|kpYJ{*OvKzn=%0?W(tMbvMh-nq20b!`BEF^2=Mk>#~wq2+}03hJ1j z`Bw_p*4Z2O|3bZly1H6eDSz+--vQLWfjRzueQk4VdnNWY#+qi){=xc2dHr9WmcPlE z?{(ClVO)NWzr@VUD*d+f7m)mZwdB8I16;xg%q&Cth|l!Gc!`bPVz(OQnLC%+IeCV? z3^lS{^+WyW>8Q!3P@~okW@7y)t@vUF#%|efHQu>nAdE)H%f0uG#JEDO|L;GhJ9qB< zuKb2P$XZx{W!MyJ9JQUAI5yz-G@4A2aLZ6iYK!?LryPt*3720=$%&yT4?20GEgo~b zlqQZpCqKXkhlk>c6hF%8ofryfLD}Vr#iNpP^4UKvTzvC)rZ~WRD6foM`1(uF<>UOq zYk&Fr`F}hZDVVromPR~|ZAIoTORGa^b@AEJGcCQDUMV!u!!Ojk|J9q%o;>;Nn-?d) zasIFr&p&tK;%gT!ymq>Lpv`3EW)nZ)X!mxt%ZkLUCc8yx>CpzpcE7C#gQG#-Ntr+@X+`*}VIS@(+gaA4 z7q~r23U0ffn43))Q1M~Lk3L4FgC=-b$k#12F>!Yi%PJv|gR=R4p%Ha8KXEWG>twb+ zKd`^0(QcMuyO;-lpap`Vp?DjQd0a}jffELXJPMbP)4i#r{%J^0rh27c=*cQh>V#9h zpN7Iop487JBfY8AT#EDM)NG0;=aQVSq|QQ=q$oGChWtzEW#(W(76XT9x8LJXz#$O~ z9pOXqnCAd@DVVnnjJm5tx?6_ggMpYwZW#)Jn9|nb@}M67(5&$fLc6c+2c;`E`%nMIV!v8?)xE#tg$}p(N2RM)$NTSF9NVSu``pFk?CiB! zI)Tjh**wIO$YM>Sj^oEh`a>$&-Ta&EzPBU)bJuxj6S!vVir>%YzKA#7vm5uo&-F3m za0p6n^Ksep}KQzP_u?hL= zPvqmWgd2bhzS?3pG4j=zr&&=$&2gT9qrz+9nhE~;o40T8zJB{QH{F=K@k;i_jqEEo z=47cVJAZrk{EfL+UO@}JDV#C(3gX~Nc$$-Wkas+-PMb+a-WhHg|scmJT=ca`U#IeBAu z>IUXOe!FuSbIdS1Q{HNjIgK6I`|?HJ&KGyTxBG7&*1_)g_#)0TI=`ipa*PeLt5|nF zKH$N+-NQr1QLM!2t93^uN{^HVkr|yFhn>-&Bo1;vr({X#J25zvvLxF4N*m5{L^i${ zMxqlPaXlPxdl1_AVg#v4QrwC~aeKY(v39E-^d_5w-gd9IU*mtI^>c^^eKuWn#cirL z)ac={;O+(Yv5hGv(;Yi(Ej zQfs55oxT>$;&l$33XBf6j~qrG>uYJSHfoyH)6#b;aCFcYPjv=Ovcw0RUJIoYI&bj& ztNz44U2IDJC$r0RcV-{6SX0(vA^XmKF9x9a!T{kFXWQ^9r>^2UzvUn?KG2T-=H-ZEdX|?(3`fr1@~cUSsR*I@@5EV5%jy#Z=Y<=^>Oc9I^x02$ju4 zLxpBl>k?aK1-1-Hfo-G5lF_n>`Y4_S)K(!aLt_~@%8GceJS3li#v=MJW0Z}DS|1!` z4%!=Vo=e8)DtJY~?m?|dXssHW1%p8ee5%|x*%izdgLDhMh~gRu`Ack(>MFcTKYEKrZQd6M2$mBpqmA9f zc$s`UBDgNp{6a)Xx>{1jd(*-cI(M^&+cM+D@tLQx0WIL$$*N*{IvWt9IiD&<2#w@& zYNe`FD2X13YBg2#Q+q#YzBip!L1L$%3fpux4~a@`HbQYi@w_je%jJB6hjTeiu<2|$ zmx~BlSI1Rh>MDScIWwIVW-TogEe-lP!SfMe(lzi>OBM5cTBVli`K463(Gp4%}@IZGr6pmL+j|7ESh|@sM^dCVb+Bu6TS(zF-)VNs--oUSW6d# zG`}Et5%UXkZ$wyhl}Oq%#dl0>9x9+Yn$M9+e#{_g)o)ttEHj?&4eS}aLBHSlIR9uA z9tMRBR-9MIceDa+gkdS>BWnuP2avE$!Y;4)Pf-eba7} z$Fl*S7RdDmBBD{RNYc0{6~-dMsbc}EDjb=Uv@QTGofC~zn?bD+wTNiKFwKTZR9K)0 z+QOO1t2=pBIAOGiXx2~7W-F%BSgunvmbGgU(W0L^ojrA?D)a>)Z#Crm^a^XroXu96 znlgeH(xNF$P72peR~+u4QS|A$G&Pib%F0wYYr=! z1pmoAHu1i#`iu>%FxINU#PFunP0qRD4Xrw>Fll@?E1I;lIxg&RN*neqpH}mj{_W;w zjZ&(QZ9vn2Z)svnZR*M%5$^K4M%GVz9d=SGrI; zfu_BqwYpIcJ6B$-?F=|Z#Nir)OnqzeboM^D#QIh~u4`(PtTutHo6JQk_&*;4FS#ei zZr&9i*%KSu^{)5nq;xSHuD9Gb=uut7!#nUHa+V#0<3kf;TcRS2`9}?NkptGPOU|Vs zDxwIID#v3;7D&*0o?2h;=ey$Mqa5SQcpuq~@=8)0@dfVL??A4`F-^v(8#P1g50K@F zVr?CJOCH0L-PPd4QI0rljfw%p;`nFCCy;Bot5w9HGpUOq6vxSG*Bv9G#Cy0-rvY?BRJ?%xR7}`q)BJE2CBGt>fcx+GgX{s2i%LXFV z6$6p#sxA)isjgAQP+d0=sea8sq`INM)nJn9H=}e|Sj$50oW4d!O@!f+{qq@IK==zt z%GGQ2dWD(OxG?lqOY42*yEr`>3`q^iY^>7BpFfSu;`7;xTpFu{sP+@L ZCaB;}oU1TL?>qd?H-+ijI332=e*@mONofE8 delta 3899 zcma)8TWlO>6+Zu5_r7+#zOIwZdhE61on7BAt=(j^>&>QdgKtULjeFU(lge)c6M0J1=it*{u3*OhNw{k@vML<(IZ#Z-D+Pr02@juU#JcKD|ezK==J*eXV>+ zwW2>J3XWoZe0_bbQZ`>#pM~Kn4BhLS*Kec{4mvv+z53#6x$#~k1^u_sJ2%TWwnW5t z1p3F&t!w4YwLku_qmwH@kaxFUeChhhvoA~{hdVI*dh07|Tesf&!F{6WY3LV7sBef@ zNhgo;b>&yEeEYRehlJl96upTVbbx!&NgDM*8jyk%qI>@PZ~#dx+XRWXt0E>Ue@+Lw zgl3E%vsGPFGPBY zDm7Dr`qRBVg4D3kNXei@>IR`P8@kYhwt+H4Invq|X^j}oiHXq(F+SWXS|VX#bPWiT zZ}G85S`+RE>XC}ksfUb@KGYlZsPVFC2u;^N{lo~ttbh2Su6G)D*=QL1#*aQ?BeL5| z@%ny>hlNh2P;@1u3C>Xnw0eVdMc+W1%B_*M)(EHAGYRcIqilIaXOq9lKYHNM^kXhMEAd?{!|^I0JTv!olGD1pn1BY>B~qF z>x#N;V2T9IL6BI&n2CHxdnhQ#I^5pfVMiK+ZJ}0=P66Tfh7&#RU2Xj=JH{CORe2qb zyJ(oE(~qG@L+Hjb&O!Ak=m0H~scGgMnWnZxnyKXl4W*97V@KLsB2A&7X~1W|8+M=5 ztIK?l4_vm>0G*(GdWKB$2-CBysfy73iccdi`}G=zcaFSXV~Gr}k>_lWPmPbH5^+0b zMLQx*!S+!5uKz&T-8Q4{rXF|S^EA2_jp71M?EdrWJ#~S4X^^t%bWgY4)zRK&Dxx_e z2(cQ%A=A(`lTm_40MPfd;trjmWVF{>jU zjXzvXn5%iWf)~PPV~x<0RHSFp7m*hZDzJ=4Th)ZO(P+@ZO2|X-yi7r#FE~fRpl^wM zL0`VFcQ<6={QTL|x%3lLkB^O}lD#8+BcBg^2uI%)pBNh*A8u)NUoo59Tb{dvgXv_X z=};ru6%6=&-T;OtqwJ0o#Qr#8{xuw694x`HigarZ1V&9BXX<|lt6|ICOx;)~n->cn+( z;~nF<@brJkuik@&?gTxtx(vMcH}^082@!Myfl<7m@%unbitmYkD<$Rc>N)kF+K;r4 z^|-!Re+vic;%>w7tasB8T?7A`_ceDr@GmjyUJw3UjJ}-+oe|L>!YUXSP3Xc9rtk=_ z@Cm;NAkwYyHs;Pf($K=wLwII})kTaUE=i8GN|%#TIMyXg{$^fk@rzQ+Ef->TELtsC za(=!Llj&mAl9P-ki$zQNa^*|1k5!-hyM~D{C5lB`(tKg9SWHUA4K!L6)v@8ix>VCw zGL|&#%#M$g{up4^s#;Y^iQ1svQ(Y*O=A-4sV!H_Jv3DDI5l0* zNl~6jO0NTsWl3-L3?~m@XNuCt+9EU`w50So)@_Ye__kWL%hI1MS=Evy{YWAy{m$&U zLd_I2#cml~vu`A2z?oer%r5O3qcPZ<>h_>hqd>Ove4!QyWD!3jeTkx^tWopc#piq1+pzJu>ky;&gy)pO?x zBm;KFD#7nNK|GFr%4DjgT2N2O%|x^dRfa(fC6cnC;?x9#MyIB*Jml0=mQ7AgV;MpJ zST;K~gJp|TGg-DeH4n=+r{-nZ?nqDK3kL5%@OJEpB6fvACxWv)jNmMfAUMmT2+q<% zaF#IyXW50|ENuj5+3i@<_2chxP;{_lWzo_Sx4H}|yZd8F8Fyq)LiS*CdNCt;j6r>L zDwrL+JZW3i^Mwc2T_a92Df7HymT-l{O=E%XsBR4)4|2{9(KNs*0Kp*d?1;MMG(g?*By*m?(i!G3m}L%wr!uowVfsH4@t zFjL~yVYG9Dd7`x#G?z!WMw`4_b9R0{tmwE0tr7kU0PgANhStUa04EIqh&*GGlPw)# z-srw~_Gl0OzW_VBK7*q*SG3Oz00^j$Xg%be9AIb<@!#4#>|97J$x60!4Gye^5?zWRTI}o z7bEvA9sfCc((S6Nv&k{T#6EqVtc3*DZ%ckCpuOu8~nq;Q^Z_h zo17_qIWXd+pN?zsW^`43evBF@{Z|YcvFhRqG3-h8R<`-pqAJRl1$0J_FD2dtN3fu% zg3=}lpAPzEFP(GU-(vduiE}kDuAU3;R@u)UFX%1*CHus8!nM*oSFOU>1E1&IJsdfy z+xfeOk0+g${WMCsNAO>LF;BL*Bycz*9dua<{*s|UKa~=mi*F_DZTsX1^qyG@@)B`x zAZ$PJK}^AZMsDG(-Bt6&+D97!8aC_!nOCQYy3JrsURXmmryI6xOiH~*`nNzWNbZrG z;Hn_((}TTDJ;(PtW%8o0tD+ODa?5@ONW7bbls?!iULQ}xHPn@(P*|VANMjl2tZAyt z?)g&W>?&@@iF#NN>FvxHrpoz07{uA^HLLub$P)@MTsCKcw7<(OcI z9iOzlXgR^!8|}AtuEI_#sr&NSUSvq?PF!+ituGjVUJpN_N|n`>h75n69TV0Tp*$NB zuRJTn0dC4@tjrGKb7qvM7y>k6u!>~&2OQk_%#8<2$gATt-%9=D^{Oa-6JU13k9D(( zlCDHHDz~anU&rcH2b-j?B~?2;o9t=9Of(G*^0v;WAa(d;abbM$>3ep0v3cb)doQYI z9Al|#>NEGKlB&0kTFSL%?&4xW2xJIW37AO3@)b!(R`P-2+sTI{*1y2w4C16Sr>zbn zqbt`rv}{D1A=lEB^h68c4=i;o_nb`0-4T8WDv*8QUa=M7Ri zde%uL!j}%KAhE7m9tk2pPTtZ#f`C6zIfNG{^k=cwA?=t&JUUWYIUzQU$nU(lwa9kp zFz@W|2sIOrv|pYQyk3Pu#7*@ryI{?clTC+7DtNqgZk&Vy{07=eGC>(I!aAHgon3S zg7@Yb(@G#hWjoyy#gUq#^<$1xtlg40q<|PcIbA%L$9tv_IyRu@sHk z5+9Mugle7QY;K4`TB6c6f-)%b-50v&#B6$PD)U#DIx8F3;al{cYLg_H(Un$AtzSrj z-{Q(CPAnvzfDh{S2@e4%MW^k-%YCc9W8N2s!F`M@%iilEPGZqr)ZVnN;*KQHXjx*v ztZrzMEO7_)t6XQsFbpgftgs%S6S|lvg1KbcQ67%l9ORHk>Z4Z&!%^Yj#tu5F% zbjrt3`zC=V>Z(lza?{OPsx&Fw=atp`xN8u4j@IJ z&8B*;45pON{gpfz?`)-ybAVUq|z^9_GHAH27X@R+y~GCg!Z08b!@l=af_M zrc6~k21ig^yO{btTN;Tf3Sh$~Dx)u~S1xZ=Whc}7hO<*iA1(jjkrgGcNgnT9 z?c%(E4p9zR;+B&Kl_=;V5w^dWmy`t^&d=*tJmR`V!O7Lt2W%ZLjg~zz7#GPExLJp5 zDi4CwT7gmX-xH4{J$+A=w0XlwpUQhD~cSw=W`SN^zz0b3=zag_NK3-0{ z$CtyAHyNejGVR(1=lJn51?vAcS2b`^n=17;t@MdlyM~z2`y!|l}>2iLa zRaugTu*R=}bP90{*i(DIOgcqz+XP4U-?$TZ(^ z9_!9r9+&9;Vi!=puEss=851rkG-hePKP8Dpu@TKTWrTCydE4}q@H`;#0{^Bp5ogB# zy6c5=OsCJO*J;xHdBV0sW?%E-pBy?{aZUrc$UZ@ZR$p^TaJ3)R``(!VQ{LKM}w_#Wz*a%9DD zS5-tNS8JLy%46i;YsFY`O_gS%5cGgZVv5ZMzUn5XzBAXdcsTWr)sl>F`8UH;&8 zm@NLlFDMlAB_@FQLE`#vpZ`3?ufvclclTKdC{-|m0hBV%^*>{Vbai&Nry!B<2*R?= zF%A`ZLXbUYFOHCzgQ-XifZv;t=l|c`0YD!HC59PB872tR7V|rnJyr@<57shv9NO^r zZv_GZ0Nj7fnCKb|pyv#*6QTbRfAjdFMs&NCDK$SiBc`ZLC>H|FjmRchHZ{S7>8<1X zD0i*|=adxq7kjHs#05wxUU;S9LNYJ*eP;4-i{t)6OBNv0FHI?Bo-rCll}PK>9HFKl zBQ$wmt~xoNiKQUXyx;Unx?ggE-OIWkN6A9PG?7mKaLZx^k|!s$qHZHAG`S>Ooy>y1 zO^rUvR8Qa~B52<&JY|~MuBh4IPgSo=*Syn|tMW7N;~zKCS%14K>{_WWq?oli9OYd8 zQF*jUfw)LxJIAe=Nvm*>B2Q-?;WlNQ8QJ$?>lc5r-gX-fjJ3vK6Mfo66gxL2Q|5``w6 z+dzEUF57ZY(?)c0%#N{{Xi!7jVa)p<9K7wd!byvJa79!-`=s#hACKR%uNJD*5G^i? zCA_WV#C|ULqjrkYB?OxvS4{-=6tmm!E<0G3?IZNj>+Y5?P8g;P*4WsFm%vZhi0HMK z8mhx_*Q(1Not)zof_)zE%Nk^`U!_ch60)nu6xOPRDS~9!HKG`~DW8psxdi1`dIm{V zQ%qNpjyQe}xZIk)pGQ9%`CfGB^Q7LB9sM1#rjGd|Gsg-u2TO}JfuX=uh4u*K6c6lc zAWP|kVTGO8O%myHQQLp^+QE-5Pj((#4i38~KXLo=7>OB)2XPqaEb^qpM4VKdK$L;p zSXfxxVsTkr4n>4^TF@Tc=){MiSz90T9z58yPDXO1Tz=)I#8`-A$7S Sd}PR?wFdAe*_+XO?f(GqM?)I` literal 4120 zcmY*ccT^MG*PW11LXocYA|0g)k1o~FtMnp-9y&-zg#ZE~RS=YpAczVWsSyl>CLKf} z0--BSy3$L2;l1yl@2s`gJ$ui-cg~ubb?Qy4%l!X`@m&dN z003zb)k|337SWY<@r4HvwIm`}B#wHc9IQ<)p}{=Fyo;zY5y3%*BS72(Jba1T4*-B1 z0{}ELnKE3-1Ad=4m+U*yL-Bv0^YDpqCu(>i!~uY^TYO zmjE$Qa{0l1-H2KjvBod}AQ>7gWa9|%zaI<$w8}&ek_e2tp|^ZMkQ;F>Z99=$UgBA4 z9wEHI#mTh@E007zN5)kpBWS^3P1<<(2klP1y!AvNeQv_#= zn27IM{}#0ovKCtU#baq~QRGoVy~xDnrmMxS#a@*wsJr-NN$AN{4?1oET^@lly2`O$ z6Af9mF0=7cgSF9jIL5$lYW=V>t`KYUL(OiXFS3>2--BVIRtg==zsF%1_DZlsR7RH~ zwyru+iYxs6qioTP;$BF;P+C?#Z}2H$HPnm(IK^p6z4myw~);<=fTrg zrBs3yG2(j9VQ)X7hlTStzwW+?6Q-T)Su4knP9E(1V!dnr&LOaFQ_Y>mUa!w5xL6C| z6f|2}0~yH{kW{hmYj;nsjDQ&W?6co0TSzBHf?wWJiVBq4^b>mbF`&GdT-rZ0bg3{= zv(hRt`s>LxgM~ru^hM`Kw>3^oydGUQ;(raZ9^9);;10Cn(LhTjQC;hwzZL6j{Sg#7M0-ahdJ9;AN6GJNpis=Ir2trj)xZ7HVtRQ^R}elO0R;L4Jg|I zw@o#YZ;I2<^8LKZqdjsb`-JdeJi2aqnLCME^=wM!rfMyM|rk z7W@OB0is`k>)zeh!Ybd*(mrb5^cU$@p3iG?Im#iZ`LO{xe;G zj)(*ETz!AoF4sHnAFD^*%nNIgK@9Z8i=wBdJLWjp`RbLaR`(g{I1!7tm8U7jsM_fR zQyWBiTHUoKb8?)_Z#D(yZ^l2p=Y##_J!ilw<_~1l`7C6=to8`I$mGcWnu7#OSw8CY z9Ctod!5bHrTf9f>zu3rUJe;p@ITt-`D6oS~oa4wo`~FNW=96j@qdZe0cW!c}`L2|O zWTEP{8D;6YDj3F66X)CId!Q;mRvR3L0}uX(^Q)CO1fkM_6` z+bKn=7_Hp$kAmABh-iiSfI)qvZ z1|k7*^*N4~i9gVoeLUZac>BX3iqKx(qil}YkzO?Ej)DTjF+)?5C($XlKnbAj<}gA)f?JhF|5YRT1~w zDS1~|jp2_|W$Odv9anB!t@X^z-;C>bxo+-9J=HnKUN?)D33Aame?qQ*K1n2~r0*U* zxi9%dYOfUiNxO0Z$z89$N8FT&{R>1O0JPx+)s}`n>|dB4`MZa@9~g(O#Dn7x$@ACu{6GTic?+Y4;lj#Aa6o1UIl{k6Tr*2P2n zO_IF08B0Pr8UD6c7F&_l_B#sR%}L%zn#;~3BVJ!u65R8h3_ufc; z>HT;$Y@PXL{8tp)cswtkPO;>Hi*M^}fNeO-=kcoPZW6gcud0@iV-V`_jP)r z>KzNL313GX=RD_6s#WLw2qA=35u}+5qx+6*-&d2}k`(slpW;0Um5DFUt9k9CnJ#jb ztUNqwXB8CwEFILnN9~@VvYIAv{$d4dP4Q`Y?H5j>3NIF)m5P(-^&})4bFW?8v=VM6 z<>1+(ZgqS7nHnnG`N57&Yo4B|y&?uY2ci2=RjP*4?U)zo)MLxxWpu0Daw_vGMbIsD zu!#DjzJ%@P2vCX{Jk4gE;>P1%+1hTcx4uzsnmD&l=&PP-t3|LLS4|yH@vX#>7yW@A zSaHL*wP~27Qw^bi+xg5a<C;HG6ydb@%Ew6G+2P_}q|P4kB{> zoZQyjRhgomG0may=;1!(-jBH7*o(jVMycP+W5GvZwGdt?SaPi~*v!F2!;X(KxOi9g zi5k-~0&EZY^g?(4u_guj0)sUFn)LV?Tp!1i1ad4_wwA3rNj{Es@VB;>rLmbp6>{dE zF^g*5mc@IeDc5V~Rl^UOGTle~(!xW%k4hcAh^|gv z0cdE3lM?AE-2}Tu9XE}xiP&}1b+||omInr$biNN{4-FKE`zf0 zYoXmXs&SrnOBA#kaT`huV^kQ-+c3F)_-LXvx7VGKDP}}wU`5+jezIp=q~L{5Q;RAV zdh01#>yYmxONDb{{Q@_@oNpabTu1V~c6+vb^We-BkN}Y{K8j@h|x-L5)DmJ(e=R*h(3MP0e3`WsjkrYQ*k%Ov(-Pfx~ccg;q=bx zP>{qV$boiHlcz)?mTS~wOhjVlOS(}DxsVsRXqo2g2jXs(n-YR5du`7yj)n=QAZvVJ z2I@>!A8tIv+g5HvlZJ&JHMZ#WRU9+gmvv@Q{q1%&*Rr-w%n-8g-KHJV6#nT5iSAXs zWxlT4v^PdI!u@bqC3t6YH`1-%oc-;2Q_LwwqkYT;q(2v*w;kcrcho8OX8j)I;AG4A zOxU9&4n&SO<-0E zZz|c@%}m?b2`dg1vfn5i?oS<-_E2$*dN;jwbFNuEthGT7*Cb)GJ#sfO9vlBwX*p}w zrE>A~%s$2&)}b*E--%H+1~sew;o3nL7L7S8+j6CkOD4pnM)paGY1*U;vzqQ3pBWt* zwnl9H7`(C3;$l@Z7@~;V5nNUsamL7>?&3F=C87h}HyHfvUv37kD9#!)<`{PDT_4Zg zyb2{f=e9qBN6$&NB`fx zaoIb7B(LNOM_T25fC{{N*-l1jD0u%JsYezF1OfuQ3N|Bh3V`HN{qOwk>g06gq_~@| z`z#i@Kv3m@GO2@FflO7>(|@l&!nU`!K0_j_ps_ElK)Wi!kC0slF?&cB0L%fM`EMBj zm;%XzazKAcoJbbI=HM+z5oDfJkkst5CV&R81<3w2k^uaG6yQh9{h*nbN_as?#(-Cc z8dCycckHQ#o$YF4ov0q7)k!Xv$2$0%UwN~bR1ZJHEooL#$Fl9w#5MiDHYfQhuZ0a? z42EG8?k~Fn_Dk#}$A7vq>)Kh%3aI4oj09y~Oa?tUb4I6#&fY8@ZL_PH2O)on`t4vT zUj+|)B8>+~tQNY!IYMic{4Orlx)*@|vLzv=W6J#Ni zevOJjpPna+>fx_6u13EwL_kXU4Q~W@O9N@D_O@%2vuBIv&iM-A z5isKzuTOmLzNw~^9g=jUVbj*KzG7VbbdTpF#$2m9{(&}_wtkMS=aE-)`TIV-azkoQ|kidp+!K zQKo+_QC6bj-rw?l?Bsk>ZvHyGLOMUgic&lKhO8Jp@!jh;1HER}C=e*k22DZyBwctu zfcUgNfcXMGLWH4r%(OmIl8}p{$Us^seo&IB^#`>iYZMt+3nh5Tnjyd?1!>t3qZvea N`~i}RBE9^({~zL6P0|1W diff --git a/static/js/client.js b/static/js/client.js index 2d5e06c5e3d..a7bbe06651a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -315,8 +315,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updateCurrentSGV(value) { - if (value < 39) { - currentBG.html(errorCodeToDisplay(value)).toggleClass('error-code'); + if (value == 9) { + currentBG.html(' '); + } else if (value < 39) { + currentBG.html(errorCodeToDisplay(value)); } else if (value < 40) { currentBG.text('LOW'); } else if (value > 400) { @@ -330,6 +332,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; bgButton.addClass(sgvToColoredRange(value)); } + currentBG.toggleClass('icon-hourglass-1', value == 9); currentBG.toggleClass('error-code', value < 39); currentBG.toggleClass('bg-limit', value == 39 || value > 400); } From e6c1c3d0e6243214b0ad0621c64f74fdbc601612 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 12:21:33 -0700 Subject: [PATCH 146/714] use icon-hourglass instead of icon-hourglass-1 --- static/css/main.css | 12 ++++++------ static/glyphs/config.json | 12 ++++++------ static/glyphs/css/fontello-codes.css | 2 +- static/glyphs/css/fontello-embedded.css | 14 +++++++------- static/glyphs/css/fontello-ie7-codes.css | 2 +- static/glyphs/css/fontello-ie7.css | 2 +- static/glyphs/css/fontello.css | 14 +++++++------- static/glyphs/demo.html | 2 +- static/glyphs/font/fontello.eot | Bin 7368 -> 7304 bytes static/glyphs/font/fontello.svg | 2 +- static/glyphs/font/fontello.ttf | Bin 7200 -> 7136 bytes static/glyphs/font/fontello.woff | Bin 4076 -> 4064 bytes static/js/client.js | 2 +- 13 files changed, 32 insertions(+), 32 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index ccd193525ce..8332c965c5c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -59,7 +59,7 @@ body { vertical-align: middle; } -.bgStatus .currentBG.icon-hourglass-1 { +.bgStatus .currentBG.icon-hourglass { font-size: 90px; } @@ -263,7 +263,7 @@ div.tooltip { line-height: 80px; } - .bgStatus .currentBG.icon-hourglass-1 { + .bgStatus .currentBG.icon-hourglass { font-size: 70px; } @@ -305,7 +305,7 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentBG.icon-hourglass-1 { + .bgStatus .currentBG.icon-hourglass { font-size: 50px; } @@ -357,7 +357,7 @@ div.tooltip { line-height: 70px; } - .bgStatus .currentBG.icon-hourglass-1 { + .bgStatus .currentBG.icon-hourglass { font-size: 60px; } @@ -451,7 +451,7 @@ div.tooltip { line-height: 80px; } - .bgStatus .currentBG.icon-hourglass-1 { + .bgStatus .currentBG.icon-hourglass { font-size: 70px; } @@ -482,7 +482,7 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentBG.icon-hourglass-1 { + .bgStatus .currentBG.icon-hourglass { font-size: 60px; } diff --git a/static/glyphs/config.json b/static/glyphs/config.json index c146c6b72ef..e72e5aff933 100644 --- a/static/glyphs/config.json +++ b/static/glyphs/config.json @@ -24,6 +24,12 @@ "code": 59401, "src": "entypo" }, + { + "uid": "7f6916533c0842b6cec699fd773693d3", + "css": "hourglass", + "code": 59404, + "src": "entypo" + }, { "uid": "jh3jpcb1t1bcm80gidkadilh080aq79h", "css": "menu", @@ -60,12 +66,6 @@ "code": 59392, "src": "mfglabs" }, - { - "uid": "35ce53990ec0892d7dcea2d0159f8dca", - "css": "hourglass-1", - "code": 59405, - "src": "mfglabs" - }, { "uid": "55e2ff85b1c459c383f46da6e96014b0", "css": "plus", diff --git a/static/glyphs/css/fontello-codes.css b/static/glyphs/css/fontello-codes.css index 016749baf91..d06e8a87fa7 100644 --- a/static/glyphs/css/fontello-codes.css +++ b/static/glyphs/css/fontello-codes.css @@ -11,4 +11,4 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-embedded.css b/static/glyphs/css/fontello-embedded.css index 8b461774fe8..2f21144d71d 100644 --- a/static/glyphs/css/fontello-embedded.css +++ b/static/glyphs/css/fontello-embedded.css @@ -1,15 +1,15 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?87913446'); - src: url('../font/fontello.eot?87913446#iefix') format('embedded-opentype'), - url('../font/fontello.svg?87913446#fontello') format('svg'); + src: url('../font/fontello.eot?94033511'); + src: url('../font/fontello.eot?94033511#iefix') format('embedded-opentype'), + url('../font/fontello.svg?94033511#fontello') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'fontello'; - src: url('data:application/octet-stream;base64,d09GRgABAAAAAA/sAA4AAAAAHCAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJHGNtYXAAAAGIAAAAOwAAAVLoKenZY3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAAAVWAAAJFvIYbztoZWFkAAAMxAAAADQAAAA2BVrRQGhoZWEAAAz4AAAAHgAAACQHlwNfaG10eAAADRgAAAAlAAAAODEAAABsb2NhAAANQAAAAB4AAAAeEUkOdm1heHAAAA1gAAAAIAAAACAAqgv6bmFtZQAADYAAAAF3AAACzcydGx1wb3N0AAAO+AAAAIoAAADMdW0ycXByZXAAAA+EAAAAZQAAAHvdawOFeJxjYGRuYJzAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvOBlDvqfxRDFHMQwHSjMCJIDAOwOC8N4nGNgYGBmgGAZBkYGEPAB8hjBfBYGAyDNAYRMIIkX3C94//8HsxggLAlGCQaoLjBgZGMY8QAA7+0I0AAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icrVVLbBtVFH13Pm8c/2I384vjTOyxMxM5zs+fmVCcqdNP2qRWoZBGITJpSpOGFiqxaJugoqhCSIgFLUhIpBa0IZQNQpEAIQQLqBqhskCqukIiEUvUBeusGoc7TltaFIFS6mc933fGc9+59517H+E2NjbeYT9nsyRM4mQPKZFA0XtwoL9HkwMc19EezjmMktGYIKt3MraoMQ5r5zpBD4KtWBlJpPWA3yA0Qzafs2wHdoE72zkjobOinLE6GTo098Wxo0tvlACN6/h7rnf8zfcuTFgwvvjDtfKvLR3HOKA80yv7z1IWRKHBTxtEGC4+H07tSTHGQBuM33/9x6W5oaG5paP73yrvZKyJC4NHF8fHFw9PNSucB3gOmv0RtTXCUgY8nE+g4bZEz97quzFdL+qQJPhhatMoc5xIpJ14vjEjYY7paG8VMQ69CwR3MnI2WBkF5PyWKDNKo3yQrq7wIb6JUuBwARzdEjwnIMyvrPB8lLoAz1fv0ls1cHX1Pkgpgg9xO3OPmx7xu9wwI8jCRAKY20wL2O6E3LZCkVv1Lh/i0CFtQucrq7iI0i1BeE1wN3YJBP8mdMsFef5RkBCWkHs68ROVJIn32+bGcMDHs5i7jIw6SMR1I2cpkJFFatZsu2Yj3omSYHennEJ63UgXnBTzW7qwaRfSru2k1o32AhTSY4WOlOOMOACvOsMOFEYKAKedIwgVgNQ44PQ78yFy0EkGOXSamuynyMHWjT7IWRm5GUSK+yrIyNwkYoZzVjaekdlwzsA1VcKijGvrthSLpTSNuZQugNNeLq4v949Bucj09Y9Na/L6shQDTWb6pNgNTb4taZqEL8BThfaX0w70l2FsN/T2l8vVt6FX1iAmVX/G/xDCPcSxkXSRXW49PZ1rTzSiqrGeWnXDzrtEawy3zxpOu7zSUdC+fBz+2wnF1ePGSfYOe5iYpBv12JnSvK4eJYEKimBi3SdMwxRM2+iCvG3ZJraEXZBVZMVWBFkRcga2CWwB7J3vvL7ZE1fFFu1SkyR/MvW6z3PzpuCfnVqoj0UvReXIlRMzAWFxZHaYGT0zBp9djDQpH0+e8/pu/CT4ZiYXpMboxaikXp2c8fiXlwX/2cmPmqTjheHh2eFh5Mk/knODFLCL1Rf9B/p7u42Ym3f+3/L+4Mn/OARI1AA0Rh7zNLZzMPQf8WbJIHmBhIvBI4f29WU7NmOm/xGz+SBm80kk4LpLEI2vnmAitpMTV6vfswdYhniIiFoN+zjiajUjh2jMCFlgo1BRtShNG64tra1Vr62tAVcZqcyPViqj85URltnEymvVcmVkfh6fuPNm39k4jXXwDCotSGTsO3JD0Mux2Hfy4SyEodXKmbasiIJihdAICX/CS9UrsDA4uP8k80rX+fOlSuUiLED8rgbxxNAvpaFTH7w/1TUDE+eHKtVSBffA/rZxCvd4jvgI/bqOBdd3/MGAaYjAdPVy9Y/b943qZZjGuN37mxliMyRPZjDuCatHxrilfK52FdPaSJidjHv47lHi6QYBr7XNR3YLaKDca992NucItqJRBe9yrGwc6IZxb3Ss63xckpXaQA9MPUMlKlBJVSOZiCetq4WQrkZUtTsBnya6AeFskzcdEy1vTDUCMmT06ot6ZqqNC3rVoBbShcgOURN34PXXFvTo+R16MKmqQryOsnx1DvzeOELoUeUY0OsCrQOyZST3JWkpVKIHYq3JXmlA7+7Wa/DeJD0kPustQcxusGQX31cXEv2qJ0I1piHgj7hDSAhqsOdgvJiN9O7EPtYVrvMEEgmPHO46GN+VbbKcpM6xDPQEVYH8BUfov3MAAHicY2BkYGAA4sc6/E/i+W2+MnAzvwCKMFzUiobShjv+//+fyfyCOQjI5WBgAokCAGveDWx4nGNgZGBgDvqfxRDF/IKB4f8/IAkUQQF8AJFABfwAAHicY37BwMC8koGBqQmCmVcB8T0ofoHE9oDygZjJk4EBACtzCigAAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBIsAAAABAAAADgB4AAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAeJx1kc1Kw0AURr9pa9UWVBTceldSEdMf6EYQCpW60U2RbiWNaZKSZspkWuhr+A4+jC/hs/g1nYq0mJDMuWfu3LmZADjHNxQ2V5fPhhWOGG24hEM8OC7TPzqukJ8dH6COV8dV+jfHNdwiclzHBT5YQVWOGU3x6VjhTJ06LuFEXTku0985rpAfHB/gUr04rtIHjmsYqdxxHdfqq6/nK5NEsZVG/0Y6rXZXxivRVEnmp+IvbKxNLj2Z6MyGaaq9QM+2PAyjReqbbbgdR6HJE51J22tt1VOYhca34fu6er6MOtZOZGL0TAYuQ+ZGT8PAerG18/tm8+9+6ENjjhUMEh5VDAtBg/aGYwcttPkjBGNmCDM3WQky+EhpfCy4Ii5mcsY9PhNGGW3IjJTsIeB7tueHpIjrU1Yxe7O78Yi03iMpvLAvj93tZj2RsiLTL+z7b+85ltytQ2u5at2lKboSDHZqCM9jPTelCei94lQs7T2avP/5vh/gZIRNAHicbY3RCoJAEEXn6malZvQhCxpI37Ougwbjrqhb9PcFoRB0Xu45T5ci+pLSfwoiRIihsEOCPQ44IkWGHCcUec8yanufrHB7Nq4T1q0PzWfCePnp1j+dGtiFtDHLwtNLX+tN6zK2vtvyVmerVmVZWOMsy3qTPLyEgdUoYc56H6ZOzDzriugNN6oxYgAAeJxj8N7BcCIoYiMjY1/kBsadHAwcDMkFGxlYnTYyMGhBaA4UeicDAwMnMouZwWWjCmNHYMQGh46IjcwpLhvVQLxdHA0MjCwOHckhESAlkUCwkYFHawfj/9YNLL0bmRhcAAfTIrgAAAA=') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSRwAAADsAAAAVmNtYXDoKenZAAABRAAAAVJjdnQgAAAAAAAAECgAAAAKZnBnbYiQkFkAABA0AAALcGdhc3AAAAAQAAAQIAAAAAhnbHlm8hhvOwAAApgAAAkWaGVhZAVa0UAAAAuwAAAANmhoZWEHlwNfAAAL6AAAACRobXR4MQAAAAAADAwAAAA4bG9jYRFJDnYAAAxEAAAAHm1heHAAqgv6AAAMZAAAACBuYW1lzJ0bHQAADIQAAALNcG9zdHVtMnEAAA9UAAAAzHByZXDdawOFAAAbpAAAAHsAAQOAAZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoDQNS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEwAAwABAAAAHAAEADAAAAAIAAgAAgAAAADoC+gN//8AAAAA6ADoDf//AAAYARgAAAEAAAAAAAAAAAAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE////iQOqAzMAEQAhAEMATAANQApLRkExHhYNBAQtKxE0PgIXMh4CDgMiLgI3FB4CPgM3NC4BIg4BNxc2MhUUBg8BBg8BDgEdATM1NDY3PgE/ATY3PgE3NCYjIgMUFjI2LgIGSn6sYV+ufEwBSn6swK58THY4XoKQgGA2AV6ivqRc1x8tYQQBBgUCOBYMdQYDARQHEwwGExQBVEBTESpDKgImRigBXl+ufEwBSn6sv65+Skp+rl9HhFw6AjZggElfol5eolFlHRcECAEFBAEdDBoYJRoDBgIBCAQLBwYRKCMxRP6NICIiQCIBJAAAAAACAAAAAAJYAmMAFQArAAi1JxoRBAItKyUUDwEGIi8BBwYiLwEmNDcBNjIXARY1FA8BBiIvAQcGIi8BJjQ3ATYyFwEWAlgGHAUOBtzbBRAFGwYGAQQFDgYBBAYGHAUOBtzbBRAFGwYGAQQFDgYBBAZ2BwYcBQXb2wUFHAYOBgEEBQX+/AbPBwYcBQXc3AUFHAYOBgEEBgb+/AYAAAACAAAAAAJYAnQAFQArAAi1IhoMBAItKwEUBwEGIicBJjQ/ATYyHwE3NjIfARY1FAcBBiInASY0PwE2Mh8BNzYyHwEWAlgG/vwFEAT+/AYGGwYOBtvcBRAEHAYG/vwFEAT+/AYGGwYOBtvcBRAEHAYBcAcG/vwGBgEEBg4GHAUF3NwFBRwGzwcG/vwFBQEEBg4GHAUF3NwFBRwGAAADAAD/iQOqAzMADAAYACQACrcdGRENCwUDLSslMhYVFAYjISImNDYXATIWFAYnISImNDY3ATIWFAYjISIuATY3A0IqPjws/SYsPD4qAtosPDws/SYsPDwsAtosPD4q/SYrPAE8LFo8LSo+PlY+AQFsPlQ+ATxWPAEBbT5VPj5WPAEAAAADAAAAAAPeApcADAAiADIACrcuJx4WDAYDLSs3IiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjbRFSAgKh4eAo8sPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAQAAAAAA94ClwAMABkALwA/AA1ACjs0KyMZEwwGBC0rJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eAo8sPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABOTwraCw+AUFcAVpCAThBXFz+hwE4Fh4BIBX+yBUeHgAAAAIAAP9pA+gDUQAnADAACLUuKh4KAi0rARUHBgcXBycGDwEjJyYnByc3Ji8BNTc2Nyc3FzY/ATMXFhc3FwcWFwc0JiIOARYyNgPouQoLeGafFB8ejxsVFqFleQsIx8cHDHhloA8gHI8cFhqeZncNB6JWeFQCWHRaAaWOGhsXnWR2CgvCxQcLd2SgFRkcjhwVGJ9kdwgMw8MHDHVknBsVYzxUVHhUVAAAAAUAAAAAA94ClwAMABkAJgA8AEwAD0AMSEE4MCYgGRMMBgUtKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGJSImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BIxUgAR4sHh4BViw8AT4rXED9w0FaAVxAAj1BWmceFv3DFSABHhYCPRUgwh4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAYAAAAAA94ClwAMABkAJgAzAEkAWQARQA5VTkU9My0mIBkTDAYGLSslIiY3NTQ2MhYXFRQGJyImPQE0NjIWHQEUBiUiJic1NDYyFh0BFAYnIiY3NTQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgHAFh4BICoeHrIVIAEeLB4eAVYsPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAIAAP+6A0gDAgAIABQACLURCwQAAi0rATIWEAYgJhA2ATcnBycHFwcXNxc3AaSu9vb+pPb2AQSaVpqYWJqaWJiaVgMC9v6k9vYBXPb+XJpWmJhWmphWmJhWAAAAAwAA/20D6ANPAAUADgAWAAq3FhMOCgQDAy0rNREzAREBJTY0JzcWFxQHFzYQJzcWEAfsAWL+ngGgSUlHaQJrL3t7TJqajgGgASH8HgEhI0rMTEpqlJFlL3cBYHtKmv5MmgAAAAEAAP9qA+gDUgALAAazCQMBLSs1ESERIREhESERIREBZwEaAWf+mf7m0QEaAWf+mf7m/pkBZwAAAv///4kCSgMyADUAdwAItWA2MRYCLSsVNTQ+AT8BNgY2BjYGNiMnLgI9ATQ2MyEyFhcVFA4BDwEGNgY2BjYGNx8BHgEXFRQGIyEiJjczND4HNxceBhczNTQnJicmJyY1ND4CPwE2NzY/ATUhFRYXFhcWFxYVFA4CDwIGFQYHBhUYGBoyGggsIhg8ECIYGhgYMCMBoyMwARgYGjMbCiwgFDYKIBgmDRYBMiL+XSIyZSgEDgoYDh4QIgcaEhQeFBIOBgEoDggiNRIiDiQYGAchCQYDBf5+AQwKITUSIhAiGBgEAgEiCQ0lRhY2JiRFJAZMEEwGSCAlJDgVRiIwMCJGFjYmJEQkBk4UUApMASA3EzYWRiIwMEUJEBQMGAgaBh4CEw0MGgwaDBoHIwcYDjFLIUAzGjg6Kh4KLxEJCA0jIwgWES9LIT8zGzY+JCIEAwIBMQ4YBwAAAAABAAAAAQAA4ywP5F8PPPUACwPoAAAAANEqW+gAAAAA0SoxuP///2kD6ANSAAAACAACAAAAAAAAAAEAAANS/2oAWgPoAAD//gPoAAEAAAAAAAAAAAAAAAAAAAAOA+gAAAOpAAACggAAAoIAAAOqAAAD3gAAA94AAAPoAAAD3gAAA94AAANIAAAD6AAAA+gAAAJJAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBIsAAAABAAAADgB4AAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEACAA1AAEAAAAAAAIABwA9AAEAAAAAAAMACABEAAEAAAAAAAQACABMAAEAAAAAAAUACwBUAAEAAAAAAAYACABfAAEAAAAAAAoAKwBnAAEAAAAAAAsAEwCSAAMAAQQJAAAAagClAAMAAQQJAAEAEAEPAAMAAQQJAAIADgEfAAMAAQQJAAMAEAEtAAMAAQQJAAQAEAE9AAMAAQQJAAUAFgFNAAMAAQQJAAYAEAFjAAMAAQQJAAoAVgFzAAMAAQQJAAsAJgHJQ29weXJpZ2h0IChDKSAyMDE1IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21mb250ZWxsb1JlZ3VsYXJmb250ZWxsb2ZvbnRlbGxvVmVyc2lvbiAxLjBmb250ZWxsb0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAEMAbwBwAHkAcgBpAGcAaAB0ACAAKABDACkAIAAyADAAMQA1ACAAYgB5ACAAbwByAGkAZwBpAG4AYQBsACAAYQB1AHQAaABvAHIAcwAgAEAAIABmAG8AbgB0AGUAbABsAG8ALgBjAG8AbQBmAG8AbgB0AGUAbABsAG8AUgBlAGcAdQBsAGEAcgBmAG8AbgB0AGUAbABsAG8AZgBvAG4AdABlAGwAbABvAFYAZQByAHMAaQBvAG4AIAAxAC4AMABmAG8AbgB0AGUAbABsAG8ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAQIBAwEEAQUBBgEHAQgBCQEKAQsBDAENAQ4MaGVscC1jaXJjbGVkD2FuZ2xlLWRvdWJsZS11cBFhbmdsZS1kb3VibGUtZG93bgRtZW51CmJhdHRlcnktMjUKYmF0dGVyeS01MANjb2cKYmF0dGVyeS03NQtiYXR0ZXJ5LTEwMA5jYW5jZWwtY2lyY2xlZAZ2b2x1bWUEcGx1cwtob3VyZ2xhc3MtMQAAAAEAAf//AA8AAAAAAAAAAAAAAACwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7AAYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7AAYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsQAAKrEABUKxAAgqsQAFQrEACCqxAAVCuQAAAAkqsQAFQrkAAAAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmxAAwquAH/hbAEjbECAEQA') format('truetype'); + src: url('data:application/octet-stream;base64,d09GRgABAAAAAA/gAA4AAAAAG+AAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABRAAAAEQAAABWPilJGWNtYXAAAAGIAAAAOgAAAUrQHRm3Y3Z0IAAAAcQAAAAKAAAACgAAAABmcGdtAAAB0AAABZQAAAtwiJCQWWdhc3AAAAdkAAAACAAAAAgAAAAQZ2x5ZgAAB2wAAAVGAAAI4MiMbSloZWFkAAAMtAAAADUAAAA2BVrhJmhoZWEAAAzsAAAAHgAAACQHlwNfaG10eAAADQwAAAAlAAAAODDnAABsb2NhAAANNAAAAB4AAAAeES4Odm1heHAAAA1UAAAAIAAAACAAqgvlbmFtZQAADXQAAAF3AAACzcydGx1wb3N0AAAO7AAAAIkAAADKSDwwcXByZXAAAA94AAAAZQAAAHvdawOFeJxjYGSuY5zAwMrAwVTFtIeBgaEHQjM+YDBkZGJgYGJgZWbACgLSXFMYHF4wvOBhDvqfxRDFHMQwHSjMCJIDAOtVC8B4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGF7w/P8PUvCCAURLMELVAwEjG8OIBwBxIwa6AAAAAAAAAAAAAAAAAAB4nK1WaXMTRxCd1WHLNj6CDxI2gVnGcox2VpjLCBDG7EoW4BzylexCjl1Ldu6LT/wG/ZpekVSRb/y0vB4d2GAnVVQoSv2m9+1M9+ueXpPQksReWI+k3HwpprY2aWTnSUg3bFqO4kPZ2QspU0z+LoiCaLXUvu04JCISgap1hSWC2PfI0iTjQ48yWrYlvWpSbulJd9kaD+qt+vbT0FGO3QklNZuhQ+uRLanCqBJFMu2RkjYtw9VfSVrh5yvMfNUMJYLoJJLGm2EMj+Rn44xWGa3GdhxFkU2WG0WKRDM8iCKPslpin1wxQUD5oBlSXvk0onyEH5EVe5TTCnHJdprf9yU/6R3OvyTieouyJQf+QHZkB3unK/ki0toK46adbEehivB0fSfEI5uT6p/sUV7TaOB2RaYnzQiWyleQWPkJZfYPyWrhfMqXPBrVkoOcCFovc2Jf8g60HkdMiWsmyILujk6IoO6XnKHYY/q4+OO9XSwXIQTIOJb1jkq4EEYpYbOaJG0EOYiSskWV1HpHTJzyOi3iLWG/Tu3oS2e0Sag7MZ6th46tnKjkeDSp00ymTu2k5tGUBlFKOhM85tcBlB/RJK+2sZrEyqNpbDNjJJFQoIVzaSqIZSeWNAXRPJrRm7thmmvXokWaPFDPPXpPb26Fmzs9p+3AP2v8Z3UqpoO9MJ2eDshKfJp2uUnRun56hn8m8UPWAiqRLTbDlMVDtn4H5eVjS47CawNs957zK+h99kTIpIH4G/AeL9UpBUyFmFVQC9201rUsy9RqVotUZOq7IU0rX9ZpAk05Dn1jX8Y4/q+ZGUtMCd/vxOnZEZeeufYlyDSH3GZdj+Z1arFdgM5sz+k0y/Z9nebYfqDTPNvzOh1ha+t0lO2HOi2w/UinY2wvaEGT7jsEchGBXMAGEoGwdRAI20sIhK1CIGwXEQjbIgJhu4RA2H6MQNguIxC2l7Wsmn4qaRw7E8sARYgDoznuyGVuKldTyaUSrotGpzbkKXKrpKJ4Vv0rA/3ikTesgbVAukTW/IpJrnxUleOPrmh508S5Ao5Vf3tzXJ8TD2W/WPhT8L/amqqkV6x5ZHIVeSPQk+NE1yYVj67p8rmqR9f/i4oOa4F+A6UQC0VZlg2+mZDwUafTUA1c5RAzGzMP1/W6Zc3P4fybGCEL6H78NxQaC9yDTllJWe1gr9XXj2W5twflsCdYkmK+zOtb4YuMzEr7RWYpez7yecAVMCqVYasNXK3gzXsS85DpTfJMELcVZYOkjceZILGBYx4wb76TICRMXbWB2imcsIG8YMwp2O+EQ1RvlOVwe6F9Ho2Uf2tX7MgZFU0Q+G32Rtjrs1DyW6yBhCe/1NdAVSFNxbipgEsj5YZq8GFcrdtGMk6gr6jYDcuyig8fR9x3So5lIPlIEatHRz+tvUKd1Ln9yihu3zv9CIJBaWL+9r6Z4qCUd7WSZVZtA1O3GpVT15rDxasO3c2j7nvH2Sdy1jTddE/c9L6mVbeDg7lZEO3bHJSlTC6o68MOG6jLzaXQ6mVckt52DzAsMKDfoRUb/1f3cfg8V6oKo+NIvZ2oH6PPYgzyDzh/R/UF6OcxTLmGlOd7lxOfbtzD2TJdxV2sn+LfwKy15mbpGnBD0w2Yh6xaHbrKDXynBjo90tyO9BDwse4K8QBgE8Bi8InuWsbzKYDxfMYcH+Bz5jBoMofBFnMYbDNnDWCHOQx2mcNgjzkMvmDOOsCXzGEQModBxBwGT5gTADxlDoOvmMPga+Yw+IY59wG+ZQ6DmDkMEuYw2Nd0ayhzixd0F6htUBXowPQTFvewONRUGbK/44Vhf28Qs38wiKk/aro9pP7EC0P92SCm/mIQU3/VdGdI/Y0Xhvq7QUz9wyCmPtMvxnKZwV9GvkuFA8ouNp/z98T7B8IaQLYAAQAB//8AD3icrVVNbBtFFJ43uztr7I1jJ+vd/DgbZ92si+04qb3eDeC4SVraJoSgQhoFa5WmaX7aQBGHNgm0qipUCXGgLRISqQVtiMoFVZEAIQQHqBqhckCqKiGBSMQR9cAR5dQ4vHXa0qIIlFLbevvmW8/M99588x7h19fX3+E+5TIkSJrILtJHKjq9z+3p2qEpFTzfkgiaeaqmNern9BS1ZY3mOdtMge4HW7XSIZlVAv780ACZrGnZedgJrrVNI6pzspK2UpT1nr566ODiqT5A5xo+p9uH37pwZsSC4YVvrzg/N7Yc4oEJtF2RTjAOZLFaYtUyDHS+FIzvilNjz3YYvjf9u8XTvb2nFw/uPes8Ta2RMz0HF4aHF/aPN6i8BwQeGqS6muY6jlHw8D6RBbdHd+wuvRvR9U4dthH80LIZoodJiCSI58tYXZCnLYlmGePQW0F0jWHaYKVVULKbonSIhQU/W1kWAkI9Y8DjAHi2KTgtIiwsLwtCmLmAIJTusJtlcGXlHsgYgg9wO36Xm14nudwwI8gihgQwt+lGsF2D3DZDkVvpjhDgcUFWj4svr+AgzDYF4XXR3dgl4P+b0E0XFISHQUI4Qu7qRCI1ZBvxftVQG6zwCRzmLq2gDqJNumFaKqQVmcXKvl32EU+hJLjueD6XXDOSuXyc/prMbfi5pOvn42tGIge5ZCHXEs/nB/MAr+YH8pAbzAEcyx9AKAekzAHNb/QD5KCTNHJIxTRFYsjB1o0OMK200gAyw31VZBTbIBILmlamKa1wQdPAMVODsoJj61YoEolrGj2fzEE+4XSuLXUVwOmkHV2FSU1ZWwpFQFNoRyhyXVNuhTQthBPgqVziSDIPXQ4UuqG9y3FKb0O7okEkVPoB/0MI/wDHWtJKdrr36RkzEa1FVeN9atYNO+sSLTPcOms45vJKhkH77FH4byUUV4/rR7nb3H4SI22ox1Rc87p6DIlMVMUY3vtozIiJMdtohaxt2TEsCTshoyqqrYqKKpoGlgksAdztr72+2YnLcqN2vj6kfDz+hs9z44YozY7PV0bC58NK3aWJmQpxYXB2gA4dL8An5+rq1Y/Gpr2+69+Lvpmx+VBt+Fw4VHN5bMYjLS2J0omxD+tDh3MDA7MDA8hTeCjnBslhFavslPZ1tbcZETfvwr/l/f6b/3EIEC0D6Aw+4mls5WDYP+LNkB7yMgl2+g/0P9uRadmImf1HzLH7McceRwKuuQTR+fwxJmIrOXG1+g23j6PEQ2TUatDHE1eraSXAIkbAAhuFiqpFadpwZXF1tXRldRX44mBxbqhYHJorDnJ0A3NWS05xcG4O37h2o+6sH8N78AIqzU8UrDtKtd/Lc1h3ssEMBKHZMmO2osqiagXQCYh/wGjpEsz39Ow9Sl9pPXmyr1g8B/PQdEeDpmjvj329U++/N946AyMne4ulviLugfVtfQr3eJH4CPviCQ7ctZvuf2ES6mCydLH0+617TukiTG5wm6JtOK+ebCejyO35tBGp8XJu3/CDrIF7quWzzwM27g0NhASRqUombVsx7NPV2OOxiWRt7Ceq3MgphhKV/bSSMoNlWQPkadbEVq+BmjVbKV72FNC2kdHRkau/XHUfiz8tlrQq3uOcOjvk10amZi94wlJtg0PH/hzjCy3VwaCkSdTLPFJlJdULnuO7ze43mZOoqhAkkaevJY8M55yJ1LTe369Ppyac3PCR5HSkvz/C/B5mpr2yVeqt0rb5a8KOlrasdKSQqA6FaJu3qsrvrQwEqA6FSDdvtOmaprcZfLfmPFnt98iSQv4C8vjDpQAAeJxjYGRgYADig+qzBeL5bb4ycDO/AIowXNRKvg2hLVf///8/k/kFcxCQy8HABBIFAFh/DPMAAAB4nGNgZGBgDvqfxRDF/IKB4f8/IAkUQQF8AJFABfwAAHicY37BwMC8koGBqQmCmVcB8T0ofoHE9oDygZjJgIEBACsoCg8AAAAAAAAAAHgAxgEUAVYBqAIOAmIC2gNkA5IDxAPgBHAAAAABAAAADgBjAAYAAAAAAAIAAAAQAHMAAAAiC3AAAAAAeJx1kc1Kw0AURr9pa9UWVBTceldSEdMf6EYQCpW60U2RbiWNaZKSZspkWuhr+A4+jC/hs/g1nYq0mJDMuWfu3LmZADjHNxQ2V5fPhhWOGG24hEM8OC7TPzqukJ8dH6COV8dV+jfHNdwiclzHBT5YQVWOGU3x6VjhTJ06LuFEXTku0985rpAfHB/gUr04rtIHjmsYqdxxHdfqq6/nK5NEsZVG/0Y6rXZXxivRVEnmp+IvbKxNLj2Z6MyGaaq9QM+2PAyjReqbbbgdR6HJE51J22tt1VOYhca34fu6er6MOtZOZGL0TAYuQ+ZGT8PAerG18/tm8+9+6ENjjhUMEh5VDAtBg/aGYwcttPkjBGNmCDM3WQky+EhpfCy4Ii5mcsY9PhNGGW3IjJTsIeB7tueHpIjrU1Yxe7O78Yi03iMpvLAvj93tZj2RsiLTL+z7b+85ltytQ2u5at2lKboSDHZqCM9jPTelCei94lQs7T2avP/5vh/gZIRNAHicbY3bCoJAFEXPVrO8ZPQhAxZI3zOOBw2OM+I4RX9fEApB62Wv9bQpoi85/aciQoQYCXZIsccBGXIUKHFEVQ4skzL32Qh3J217YdW50H4mTOef7tzTJiPbkLd6WXh+qWuzaVPHxvVb3ppi1UtdV0Zbw7LepA8nYeRkkuCzwYW5F+090RvUNDECAAAAeJxj8N7BcCIoYiMjY1/kBsadHAwcDMkFGxlYnTYyMGhBaA4UeicDAwMnMouZwWWjCmNHYMQGh46IjcwpLhvVQLxdHA0MjCwOHckhESAlkUCwkYFHawfj/9YNLL0bmRhcAAfTIrgAAAA=') format('woff'), + url('data:application/octet-stream;base64,AAEAAAAOAIAAAwBgT1MvMj4pSRkAAADsAAAAVmNtYXDQHRm3AAABRAAAAUpjdnQgAAAAAAAAD+gAAAAKZnBnbYiQkFkAAA/0AAALcGdhc3AAAAAQAAAP4AAAAAhnbHlmyIxtKQAAApAAAAjgaGVhZAVa4SYAAAtwAAAANmhoZWEHlwNfAAALqAAAACRobXR4MOcAAAAAC8wAAAA4bG9jYREuDnYAAAwEAAAAHm1heHAAqgvlAAAMJAAAACBuYW1lzJ0bHQAADEQAAALNcG9zdEg8MHEAAA8UAAAAynByZXDdawOFAAAbZAAAAHsAAQN+AZAABQAIAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA6ADoDANS/2oAWgNSAJcAAAABAAAAAAAAAAAAAwAAAAMAAAAcAAEAAAAAAEQAAwABAAAAHAAEACgAAAAGAAQAAQACAADoDP//AAAAAOgA//8AABgBAAEAAAAAAAAAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABP///4kDqgMzABEAIQBDAEwADUAKS0ZBMR4WDQQELSsRND4CFzIeAg4DIi4CNxQeAj4DNzQuASIOATcXNjIVFAYPAQYPAQ4BHQEzNTQ2Nz4BPwE2Nz4BNzQmIyIDFBYyNi4CBkp+rGFfrnxMAUp+rMCufEx2OF6CkIBgNgFeor6kXNcfLWEEAQYFAjgWDHUGAwEUBxMMBhMUAVRAUxEqQyoCJkYoAV5frnxMAUp+rL+ufkpKfq5fR4RcOgI2YIBJX6JeXqJRZR0XBAgBBQQBHQwaGCUaAwYCAQgECwcGESgjMUT+jSAiIkAiASQAAAAAAgAAAAACWAJjABUAKwAItScaEQQCLSslFA8BBiIvAQcGIi8BJjQ3ATYyFwEWNRQPAQYiLwEHBiIvASY0NwE2MhcBFgJYBhwFDgbc2wUQBRsGBgEEBQ4GAQQGBhwFDgbc2wUQBRsGBgEEBQ4GAQQGdgcGHAUF29sFBRwGDgYBBAUF/vwGzwcGHAUF3NwFBRwGDgYBBAYG/vwGAAAAAgAAAAACWAJ0ABUAKwAItSIaDAQCLSsBFAcBBiInASY0PwE2Mh8BNzYyHwEWNRQHAQYiJwEmND8BNjIfATc2Mh8BFgJYBv78BRAE/vwGBhsGDgbb3AUQBBwGBv78BRAE/vwGBhsGDgbb3AUQBBwGAXAHBv78BgYBBAYOBhwFBdzcBQUcBs8HBv78BQUBBAYOBhwFBdzcBQUcBgAAAwAA/4kDqgMzAAwAGAAkAAq3HRkRDQsFAy0rJTIWFRQGIyEiJjQ2FwEyFhQGJyEiJjQ2NwEyFhQGIyEiLgE2NwNCKj48LP0mLDw+KgLaLDw8LP0mLDw8LALaLDw+Kv0mKzwBPCxaPC0qPj5WPgEBbD5UPgE8VjwBAW0+VT4+VjwBAAAAAwAAAAAD3gKXAAwAIgAyAAq3LiceFgwGAy0rNyImPQE0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI20RUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAEAAAAAAPeApcADAAZAC8APwANQAo7NCsjGRMMBgQtKyUiJjc1NDYyFhcVFAYnIiY9ATQ2MhYdARQGATIWFxUUBicUBichIiYnETQ2MyEyFgMRNCYnISIGFxEUFjMhMjYBbRUgAR4sHAEesRUgICoeHgKPLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgATk8K2gsPgFBXAFaQgE4QVxc/ocBOBYeASAV/sgVHh4AAAACAAD/aQPoA1EAJwAwAAi1LioeCgItKwEVBwYHFwcnBg8BIycmJwcnNyYvATU3NjcnNxc2PwEzFxYXNxcHFhcHNCYiDgEWMjYD6LkKC3hmnxQfHo8bFRahZXkLCMfHBwx4ZaAPIByPHBYanmZ3DQeiVnhUAlh0WgGljhobF51kdgoLwsUHC3dkoBUZHI4cFRifZHcIDMPDBwx1ZJwbFWM8VFR4VFQAAAAFAAAAAAPeApcADAAZACYAPABMAA9ADEhBODAmIBkTDAYFLSslIiY3NTQ2MhYXFRQGJyImPQE0NjIWHQEUBiUiJjc1NDYyFh0BFAYBMhYXFRQGJxQGJyEiJicRNDYzITIWAxE0JichIgYXERQWMyEyNgFtFSABHiwcAR6xFSAgKh4eASMVIAEeLB4eAVYsPAE+K1xA/cNBWgFcQAI9QVpnHhb9wxUgAR4WAj0VIMIeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAAGAAAAAAPeApcADAAZACYAMwBJAFkAEUAOVU5FPTMtJiAZEwwGBi0rJSImNzU0NjIWFxUUBiciJj0BNDYyFh0BFAYlIiYnNTQ2MhYdARQGJyImNzU0NjIWHQEUBgEyFhcVFAYnFAYnISImJxE0NjMhMhYDETQmJyEiBhcRFBYzITI2AW0VIAEeLBwBHrEVICAqHh4BwBYeASAqHh6yFSABHiweHgFWLDwBPitcQP3DQVoBXEACPUFaZx4W/cMVIAEeFgI9FSDCHhbRFR4eFdEVIAEeFtEVHh4V0RUgAR4W0RUeHhXRFSABHhbRFR4eFdEVIAE5PCtoLD4BQVwBWkIBOEFcXP6HATgWHgEgFf7IFR4eAAACAAD/ugNIAwIACAAUAAi1EQsEAAItKwEyFhAGICYQNgE3JwcnBxcHFzcXNwGkrvb2/qT29gEEmlaamFiamliYmlYDAvb+pPb2AVz2/lyaVpiYVpqYVpiYVgAAAAMAAP9tA+gDTwAFAA4AFgAKtxYTDgoEAwMtKzURMwERASU2NCc3FhcUBxc2ECc3FhAH7AFi/p4BoElJR2kCay97e0yamo4BoAEh/B4BISNKzExKapSRZS93AWB7Spr+TJoAAAABAAD/agPoA1IACwAGswkDAS0rNREhESERIREhESERAWcBGgFn/pn+5tEBGgFn/pn+5v6ZAWcAAAMAAP9qAjADUgAbACgAYgAKt00yJiAYCgMtKwEUDgEUHgEdARQGIiY9ATQ+ATQuAT0BNDYyFhUFBwYXFjMyNzYnJiMiEzQ+Aj8BNjU3BiInFxQfAxYmFiMUDgIPAgYmBjUGHQE+AjU0MhUUHgEXNTQvAiYvAS4BAjBgYmJgrNisYGJiYK7Urv4eEgQIXHyEWA4eYGp4kAgcDBkdXAJk9GQEWi0TEREMHgwCCgYIDA8PAiJaCHRENEJ6BlwrEg0FDAcEAm4saF48XGYudiJOTiJ2LmZcPF5oLHYgTk4gBg4IBjQyChQ2/koSHiQOGBxcHjI2NjIgWisTFRUCMAoSEg4KDxAQAiIBWiBCBCYwIh4eIjAmBEIeXCkTDggUDBYAAAEAAAABAADBJ5sQXw889QALA+gAAAAA0Spj2wAAAADRKjmr////aQPoA1IAAAAIAAIAAAAAAAAAAQAAA1L/agBaA+gAAP/+A+gAAQAAAAAAAAAAAAAAAAAAAA4D6AAAA6kAAAKCAAACggAAA6oAAAPeAAAD3gAAA+gAAAPeAAAD3gAAA0gAAAPoAAAD6AAAAjAAAAAAAAAAeADGARQBVgGoAg4CYgLaA2QDkgPEA+AEcAAAAAEAAAAOAGMABgAAAAAAAgAAABAAcwAAACILcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTUgYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADUAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgxoZWxwLWNpcmNsZWQPYW5nbGUtZG91YmxlLXVwEWFuZ2xlLWRvdWJsZS1kb3duBG1lbnUKYmF0dGVyeS0yNQpiYXR0ZXJ5LTUwA2NvZwpiYXR0ZXJ5LTc1C2JhdHRlcnktMTAwDmNhbmNlbC1jaXJjbGVkBnZvbHVtZQRwbHVzCWhvdXJnbGFzcwAAAAAAAQAB//8ADwAAAAAAAAAAAAAAALAALCCwAFVYRVkgIEu4AA5RS7AGU1pYsDQbsChZYGYgilVYsAIlYbkIAAgAY2MjYhshIbAAWbAAQyNEsgABAENgQi2wASywIGBmLbACLCBkILDAULAEJlqyKAEKQ0VjRVJbWCEjIRuKWCCwUFBYIbBAWRsgsDhQWCGwOFlZILEBCkNFY0VhZLAoUFghsQEKQ0VjRSCwMFBYIbAwWRsgsMBQWCBmIIqKYSCwClBYYBsgsCBQWCGwCmAbILA2UFghsDZgG2BZWVkbsAErWVkjsABQWGVZWS2wAywgRSCwBCVhZCCwBUNQWLAFI0KwBiNCGyEhWbABYC2wBCwjISMhIGSxBWJCILAGI0KxAQpDRWOxAQpDsABgRWOwAyohILAGQyCKIIqwASuxMAUlsAQmUVhgUBthUllYI1khILBAU1iwASsbIbBAWSOwAFBYZVktsAUssAdDK7IAAgBDYEItsAYssAcjQiMgsAAjQmGwAmJmsAFjsAFgsAUqLbAHLCAgRSCwC0NjuAQAYiCwAFBYsEBgWWawAWNgRLABYC2wCCyyBwsAQ0VCKiGyAAEAQ2BCLbAJLLAAQyNEsgABAENgQi2wCiwgIEUgsAErI7AAQ7AEJWAgRYojYSBkILAgUFghsAAbsDBQWLAgG7BAWVkjsABQWGVZsAMlI2FERLABYC2wCywgIEUgsAErI7AAQ7AEJWAgRYojYSBksCRQWLAAG7BAWSOwAFBYZVmwAyUjYUREsAFgLbAMLCCwACNCsgsKA0VYIRsjIVkqIS2wDSyxAgJFsGRhRC2wDiywAWAgILAMQ0qwAFBYILAMI0JZsA1DSrAAUlggsA0jQlktsA8sILAQYmawAWMguAQAY4ojYbAOQ2AgimAgsA4jQiMtsBAsS1RYsQRkRFkksA1lI3gtsBEsS1FYS1NYsQRkRFkbIVkksBNlI3gtsBIssQAPQ1VYsQ8PQ7ABYUKwDytZsABDsAIlQrEMAiVCsQ0CJUKwARYjILADJVBYsQEAQ2CwBCVCioogiiNhsA4qISOwAWEgiiNhsA4qIRuxAQBDYLACJUKwAiVhsA4qIVmwDENHsA1DR2CwAmIgsABQWLBAYFlmsAFjILALQ2O4BABiILAAUFiwQGBZZrABY2CxAAATI0SwAUOwAD6yAQEBQ2BCLbATLACxAAJFVFiwDyNCIEWwCyNCsAojsABgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAULLEAEystsBUssQETKy2wFiyxAhMrLbAXLLEDEystsBgssQQTKy2wGSyxBRMrLbAaLLEGEystsBsssQcTKy2wHCyxCBMrLbAdLLEJEystsB4sALANK7EAAkVUWLAPI0IgRbALI0KwCiOwAGBCIGCwAWG1EBABAA4AQkKKYLESBiuwcisbIlktsB8ssQAeKy2wICyxAR4rLbAhLLECHistsCIssQMeKy2wIyyxBB4rLbAkLLEFHistsCUssQYeKy2wJiyxBx4rLbAnLLEIHistsCgssQkeKy2wKSwgPLABYC2wKiwgYLAQYCBDI7ABYEOwAiVhsAFgsCkqIS2wKyywKiuwKiotsCwsICBHICCwC0NjuAQAYiCwAFBYsEBgWWawAWNgI2E4IyCKVVggRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOBshWS2wLSwAsQACRVRYsAEWsCwqsAEVMBsiWS2wLiwAsA0rsQACRVRYsAEWsCwqsAEVMBsiWS2wLywgNbABYC2wMCwAsAFFY7gEAGIgsABQWLBAYFlmsAFjsAErsAtDY7gEAGIgsABQWLBAYFlmsAFjsAErsAAWtAAAAAAARD4jOLEvARUqLbAxLCA8IEcgsAtDY7gEAGIgsABQWLBAYFlmsAFjYLAAQ2E4LbAyLC4XPC2wMywgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhsAFDYzgtsDQssQIAFiUgLiBHsAAjQrACJUmKikcjRyNhIFhiGyFZsAEjQrIzAQEVFCotsDUssAAWsAQlsAQlRyNHI2GwCUMrZYouIyAgPIo4LbA2LLAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjILAIQyCKI0cjRyNhI0ZgsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhIyAgsAQmI0ZhOBsjsAhDRrACJbAIQ0cjRyNhYCCwBEOwAmIgsABQWLBAYFlmsAFjYCMgsAErI7AEQ2CwASuwBSVhsAUlsAJiILAAUFiwQGBZZrABY7AEJmEgsAQlYGQjsAMlYGRQWCEbIyFZIyAgsAQmI0ZhOFktsDcssAAWICAgsAUmIC5HI0cjYSM8OC2wOCywABYgsAgjQiAgIEYjR7ABKyNhOC2wOSywABawAyWwAiVHI0cjYbAAVFguIDwjIRuwAiWwAiVHI0cjYSCwBSWwBCVHI0cjYbAGJbAFJUmwAiVhuQgACABjYyMgWGIbIVljuAQAYiCwAFBYsEBgWWawAWNgIy4jICA8ijgjIVktsDossAAWILAIQyAuRyNHI2EgYLAgYGawAmIgsABQWLBAYFlmsAFjIyAgPIo4LbA7LCMgLkawAiVGUlggPFkusSsBFCstsDwsIyAuRrACJUZQWCA8WS6xKwEUKy2wPSwjIC5GsAIlRlJYIDxZIyAuRrACJUZQWCA8WS6xKwEUKy2wPiywNSsjIC5GsAIlRlJYIDxZLrErARQrLbA/LLA2K4ogIDywBCNCijgjIC5GsAIlRlJYIDxZLrErARQrsARDLrArKy2wQCywABawBCWwBCYgLkcjRyNhsAlDKyMgPCAuIzixKwEUKy2wQSyxCAQlQrAAFrAEJbAEJSAuRyNHI2EgsAQjQrAJQysgsGBQWCCwQFFYswIgAyAbswImAxpZQkIjIEewBEOwAmIgsABQWLBAYFlmsAFjYCCwASsgiophILACQ2BkI7ADQ2FkUFiwAkNhG7ADQ2BZsAMlsAJiILAAUFiwQGBZZrABY2GwAiVGYTgjIDwjOBshICBGI0ewASsjYTghWbErARQrLbBCLLA1Ky6xKwEUKy2wQyywNishIyAgPLAEI0IjOLErARQrsARDLrArKy2wRCywABUgR7AAI0KyAAEBFRQTLrAxKi2wRSywABUgR7AAI0KyAAEBFRQTLrAxKi2wRiyxAAEUE7AyKi2wRyywNCotsEgssAAWRSMgLiBGiiNhOLErARQrLbBJLLAII0KwSCstsEossgAAQSstsEsssgABQSstsEwssgEAQSstsE0ssgEBQSstsE4ssgAAQistsE8ssgABQistsFAssgEAQistsFEssgEBQistsFIssgAAPistsFMssgABPistsFQssgEAPistsFUssgEBPistsFYssgAAQCstsFcssgABQCstsFgssgEAQCstsFkssgEBQCstsFossgAAQystsFsssgABQystsFwssgEAQystsF0ssgEBQystsF4ssgAAPystsF8ssgABPystsGAssgEAPystsGEssgEBPystsGIssDcrLrErARQrLbBjLLA3K7A7Ky2wZCywNyuwPCstsGUssAAWsDcrsD0rLbBmLLA4Ky6xKwEUKy2wZyywOCuwOystsGgssDgrsDwrLbBpLLA4K7A9Ky2waiywOSsusSsBFCstsGsssDkrsDsrLbBsLLA5K7A8Ky2wbSywOSuwPSstsG4ssDorLrErARQrLbBvLLA6K7A7Ky2wcCywOiuwPCstsHEssDorsD0rLbByLLMJBAIDRVghGyMhWUIrsAhlsAMkUHiwARUwLQBLuADIUlixAQGOWbABuQgACABjcLEABUKxAAAqsQAFQrEACCqxAAVCsQAIKrEABUK5AAAACSqxAAVCuQAAAAkqsQMARLEkAYhRWLBAiFixA2REsSYBiFFYugiAAAEEQIhjVFixAwBEWVlZWbEADCq4Af+FsASNsQIARAA=') format('truetype'); } /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ @@ -17,7 +17,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?87913446#fontello') format('svg'); + src: url('../font/fontello.svg?94033511#fontello') format('svg'); } } */ @@ -64,4 +64,4 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7-codes.css b/static/glyphs/css/fontello-ie7-codes.css index 0ac7ae656c9..f12e517b8fe 100644 --- a/static/glyphs/css/fontello-ie7-codes.css +++ b/static/glyphs/css/fontello-ie7-codes.css @@ -11,4 +11,4 @@ .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-hourglass-1 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello-ie7.css b/static/glyphs/css/fontello-ie7.css index a30561edd64..f57862786bf 100644 --- a/static/glyphs/css/fontello-ie7.css +++ b/static/glyphs/css/fontello-ie7.css @@ -22,4 +22,4 @@ .icon-cancel-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-volume { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } .icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-hourglass-1 { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file +.icon-hourglass { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/static/glyphs/css/fontello.css b/static/glyphs/css/fontello.css index 0682d327830..dcef60596fe 100644 --- a/static/glyphs/css/fontello.css +++ b/static/glyphs/css/fontello.css @@ -1,10 +1,10 @@ @font-face { font-family: 'fontello'; - src: url('../font/fontello.eot?73606746'); - src: url('../font/fontello.eot?73606746#iefix') format('embedded-opentype'), - url('../font/fontello.woff?73606746') format('woff'), - url('../font/fontello.ttf?73606746') format('truetype'), - url('../font/fontello.svg?73606746#fontello') format('svg'); + src: url('../font/fontello.eot?46427953'); + src: url('../font/fontello.eot?46427953#iefix') format('embedded-opentype'), + url('../font/fontello.woff?46427953') format('woff'), + url('../font/fontello.ttf?46427953') format('truetype'), + url('../font/fontello.svg?46427953#fontello') format('svg'); font-weight: normal; font-style: normal; } @@ -14,7 +14,7 @@ @media screen and (-webkit-min-device-pixel-ratio:0) { @font-face { font-family: 'fontello'; - src: url('../font/fontello.svg?73606746#fontello') format('svg'); + src: url('../font/fontello.svg?46427953#fontello') format('svg'); } } */ @@ -62,4 +62,4 @@ .icon-cancel-circled:before { content: '\e809'; } /* '' */ .icon-volume:before { content: '\e80a'; } /* '' */ .icon-plus:before { content: '\e80b'; } /* '' */ -.icon-hourglass-1:before { content: '\e80d'; } /* '' */ \ No newline at end of file +.icon-hourglass:before { content: '\e80c'; } /* '' */ \ No newline at end of file diff --git a/static/glyphs/demo.html b/static/glyphs/demo.html index 77275cf1b47..e8078fcc615 100644 --- a/static/glyphs/demo.html +++ b/static/glyphs/demo.html @@ -273,7 +273,7 @@

icon-plus0xe80b
-
icon-hourglass-10xe80d
+
icon-hourglass0xe80c
diff --git a/static/glyphs/font/fontello.eot b/static/glyphs/font/fontello.eot index 7f16444450abb13e452fde359ccb148c33efc846..25e92bfd3131a7217611eae6040100c75f8822f2 100644 GIT binary patch delta 693 zcmXAnK}-`t6o%i-ZntZ@Y)iW>khV*+kgW)m&AMGAsUQRsLtxf6C*=*jC{-N5nm^VvkIA1VR$ZwImOXcda_G=Q4 z;NHjW%KTz3+Zq>Z00N6Fm$ST0yz#q{?QNhFd?B1Ga8^(rxji4iBL zrXCD9oLpP*vOZ|W)Gozu4AT)}CI&er)+Ti_K{ePViPS?ml}m6DGE5VO9>7*@31fh& z10+tUHa#;l-8@)p+Pe95^Hlb+d}j6OT~VH%TdwnAp(B!^`I9`G?)SI03bH_594~l0 zR7vyIabxT;m+AGjI0Pq47y3$*$xI<$Q&K6V7B6Iylcl~|G?j{SBF`DROEOOxG+BFl=VvSV_l$1n1d5X2X=UkzX#e&vHuQyxs?0%Ux(f|R)sRr8o!0k^M7IA5FxiEwY*`33#G+}OU3zYr2??^(MyGY DtzMCw delta 741 zcmXX^&rj1}7=FKYeC@Wb8?bd>HXLm?2I7$Y>_BmF03xywV~mL!#RbNez+7d37!v0} z4aN&(2f29Cc;IA0Nc{8gm?Tb@;dTRv9z)tgNHbR zB;hi&RG!PVPswY@@dY4I%B`EQYyXFQ9=ZBBSIQs0_g_H%5+Cx_ELT=mOYtWF&fDl8 zn#+0f=OX_K!2c2X1`z&5ZHJ5!N9~yOCbGZ-LA?ts5XCMcIP6fj*By;UYl5?h5r#Ve zu-DIh6RTXK(Rjn%;Ny^jry%Y>GGYohaLJQOW-#2KEd+ZnbJs}HNN91z>v7A(NzM$K z?j`|(5)Ep4IGsplgtRw6y@^z^H{1lqa2S&|MoCKZDBcz@fpc=yLj_v(Fy@Q<#E8yD zTsreH77daQ!6-pF;kQLpw_#IR*rAZPe)>R<=ld+OjmZI*F8Dg!0e6Q?NuMn0sSaHZ zF(znM%D0_9Cz4G=2@{!xBf8bmGpQKi&_sx4U0Hfv?FkKeCiQ4kueS8@5S?+~v}K8! z?lhF9F=2JNB_{f4fa`Qfz9xjAz~tDpHkR-WUFi?l2HUNoBN!Bw_Q7dwG~qWgA>G1r zBqlSVx!=vtfprp@Ii7Wn`~^GT{4?OF|NeQ8eQ7jm8EKJB)pn(B``hz-WLV$(E`8!9 Ud$GK_Tv*CgDg&{-*Dg-_4;p}uJ^%m! diff --git a/static/glyphs/font/fontello.svg b/static/glyphs/font/fontello.svg index db5f72745d8..9074ce67bef 100644 --- a/static/glyphs/font/fontello.svg +++ b/static/glyphs/font/fontello.svg @@ -18,7 +18,7 @@ - + \ No newline at end of file diff --git a/static/glyphs/font/fontello.ttf b/static/glyphs/font/fontello.ttf index 58c50077e546bf405ee8adfb50543e1e2bc98015..ab8f230e2efa00d6b8e5910e6acc39137d7a135d 100644 GIT binary patch delta 715 zcmXAnO=uHQ5Xa}e-E7xn-K5DTZE3THZP{4Urt6w)E5rz;f)fn?L|S zga^Pt`pUI2JwAM?51qdlGueu1ef9Tk0gx$->1@3gKp&TL50kxM6)TThtsBVu01m5Y zRxR{h$Zb@qi**2jMV2dO!-Ch2-^fGAfd#XY-+s~WN5%r_XKS%qyBO1MVF41Jf3lYH z*3X;FBY^%K@?9XzebNFO@N}8JMQ7#d!D%9_Y9tt%!VYwLF#s%_MCDKkP=Gz*@DLgH zprgG6$mSFBg>Nzdhx1fpu0lUP$bfz8vaSYt?aXM1NFtGB;wKWPsOLx|P9kA~YUn<% zt&8jNozcgQpgO2{B5``27}0S~3HC@Q7@z7Jltk*JoXSNxKZ(<5MEBxXJ<-S*RmVt} zP%WLwq}RVU*E_ngvvDB1Sw3~|;dN0?&o^59fY9ep(cFHHO^$lHy9HUGc8(XEPO2pN z+Eiq6nM;M-$8ACvOBY5;v$0emTvrkar5-M%VzZ@@dLWSqa3aq|bh~66OuOYFv3DRP z>xQ8Rk|B@ROEtUuf?KpZT`sDSWMGn2HAR*cO=TzL)UZe7CBcUigI&XT7u;Q+bH@IG zgXxS7d^?r>ainM8tnN4}A+1BW8t{QgWQMF!k!I*FlVhGS@0m8+8J43l&2E;3SB!A1 Rw0L`|ST?IwfKAy&h5rnwldk{( delta 799 zcmXX^O-vI}5T5t8-QE6e3$)vp7HhjL1))fPw~a#K07RrgqlQF{feNKfpmwEzRTIlW z6OA4~67^zYf-%OE2{G~D!GrOH2X1PjA@NcZPbOYGP-nrJoj3D+-)466vO+Zn%t<(2Hoi>}zd-Nn1hZI}`@y)avlWA{{E0xTW zL89{e8SFet2S`#)1md#W<&=n>90+{+F!2$j_+UT@rxVGnkaqiccOsSS4C{$&D6UDX zgCrGj$?pBc4b)C~7ccO27h|5dM~o<}-=Q!MW6>bl3`Pmk39mWgcdBMT3)^H8SN1L` z@j{nDGBe5NPy|n_)8}lJc+w?_N~%?nLW~Ik6VDm$`d9k>V NuU30wTi@-}`VU8dl`8-M diff --git a/static/glyphs/font/fontello.woff b/static/glyphs/font/fontello.woff index 545fdac2b135212630cdb037ca178a457d4a9364..af57d2e516340720d1a401d457ce8192448774b4 100644 GIT binary patch delta 1911 zcmV--2Z;FWAK)JpcTYw}00961000l*01f~E000}{krXQx8Dni>Z~y=ShyVZpIsgCx zO3)n{w~s1TDc${NkU|?o|U>OER1`u!o@)*H<76uI<%?6|y8JHLtUhw??4^r@g0YpnMLe&ES zBOAkr2LN#+2D$*V!vU=Ye@$!~MHHSlyE|*_jbkV4-TXKkXWgue?Kr8u-VNZmNm|+_ zgrGti1#6Y2X@1&35r;Mjw5lpp32~q;5)!EeZHUSRRgnN81P7=Zp>jZ~Dj|VL#1Zwt z5#`i4ymi{rq6m~y+j{%vThGk*-hA^O;n&yK-{C&xG9*Zv$S%@He=0h?&+X~jp(zzS z-;xNLd8(#0>gV(nwPlUwIonKu?gv}7(jl2u0{;Ba0GX_5*?HIruC+}gs&lfEwo;V! zp1ykM;Nq!1K<64hr`w0$x^QO1g5ia4uQ)$9wjAPt3AA02PB0E+v07r)G7NOS6pZhR zQ)AC|7``|CyT#MJf4!#{5AHqdJWZ{UGd%|vhKCpSk2k2i2Lv7(q`I2sI*w8B@IH|R zw?}vEF1;Jk^-dkO5&Y1y8KQ?th$M*T%UE5Ir!9$Q8RO7fL3E8_+F+$sP_mn%bclro zKU-NA0zy4wz~caTwkbF*A}lN~3qqK=fFP9au$yIYW#t~oe;9%fa65jNn_efm+%O}^ zB8CJox9LW(U4z^K%mK}B^aJFelQeQj#WY1? z$idc)4lu2>(f~5UeN|)>+aMAPnpUPMt#Copz%f&UvVvplRwxpQYZ^VD%t1ckbgnIR z6~O7F9bJVFYYdGfeN`^uE>{0k{B@|bW>qoiU z+CcD_EOd_4Y!&vWC3D9w(~S4P6;!%E$yiJ1y8?S*gxXUe~=#XG6{D25A;9Vk!jh8nf*u1vHLN{ z9!CPMxdhSq{4pkCD>-qs`VF^_qr^jG)N0Vj6IZKgCBPy^zyceEimF9v+HhrYb+vS5 zbrtyHU~z7!SR9%w4svt@b5=`Ead2)9-?(<8?yMifd+-7(%}*4pJ4&_R%X3(FvcU`l zf1%kjW45BoqG|=u35b8dXz3En_w?*NN?&O`d9trqJO}g8bVq}xXz!1Gy;JYMH{Ln} zBPV-{rM@EWf%R>D3io)C_z3&r2@c$~o9=&M66#>Gbg}gNt$R-CB1~>@o1$$vubylt zqsZ;~v=OQCa&F)7gRFsjs^zoK12*mre+`5Lk*P{1ZCf$y)79AHv591D>?f++$SH;r zmHo7mGJ|DV1LSGe#8$6?nl)P~UiK8wwvo}%k*mL4bsvjAEta$@-s7A)JLK0!re-gA z!cuL6L&yFc;|nd-!Jwo`)XO|lWhK=Mp5wdC?l+l}sHzYok*BXEj|}IWiPW^-f8Vc9 zrzV`-@R8(nq`yDH{2peey|Pv6t9}wybodkf;uYXq&gH%I~cV1gH*$NH=e^ z>6)gu8GN_qJX!7c$dW?-0`mC7r2qhUoMT{QU|;~^gX*&d;`wd9GH^4$0E#eN)Jnb$ zp)FVc|NlRe`2}+jkjufq1QG=R2Uvd$^8f$s!G*aO?L7z48`Ly00sxbO4{F-^Tjc-% delta 1923 zcmV-}2YmS8AM76#cTYw}00961000l{01f~E000~ykrXQx9Aj-^Z~y=ShyVZpI{*Ly zQs^n^*^yL7e^vkh2^R7gZ#!sZVPpUR48#Bc05kvq05%0$(LiWuWnlmS4EO*503HAU z03-*O17B!uba(&&4Hy6b03`qb05~xK003-nV_^UQ4L|?@03HAU03H!Z4t8x}cyIs! z4PXEO03ZMW03ZOW3;J$hZDjxe4S)au0e1iZ0?o{wF&iClZ*z1201o&7004>r007K& zZ8C9ia%FG;01t!!003nG004X4YXgONoMT{QZeWv30Vq&q4*H+P5XBtCFdZn&2$BN; z><$aVc${NkU|?o|U>OER1`zN8@)*H<76t<#%>jf=Ac+^;FL?j|2eTL;ED1&l2B
#Q1w zJ5F0#6oFFeXFcC{#&h4f_nmtm;Wjol-eEsyvm{L7WDhBkK+$_}f1tldlLH*r(H_n@ zjFQoqAggyWwx}@?L?=PRqYh|(=VcujCOb_oRkCUnaMdHzw55SEa8I`I>g zB#HOi=EiWq$FrEbjEp)+#C0QQDUgvxYSwJKW+UE-l3;hIoI-ltNEe(G^IN*GX&2J2 z?WER?_5u{re`AG?l;eyz0Ixd34irWT0H>WJ2o-?TINy5GE`ZZsF0L>4k3qS} z^!1NTYx4TC6a!6W`lQ$mO};H@nuIUFQ-$`I(+>2Pe_?Dl^!As_)wiKn)*vQTf5!h1 z!ac;Xk#vz=ZaqDnYfm(ysOxx&!ylvSgwuD`fRb<6h1X&Ss2DS-o0qmXcyuR5J zSH%|Xe^Qeqe=_yD&vWyp;GdmZsEq25tiA+%4^kfWn_LHpSbg8-`O~gb?^)-El6bSJ90zN%dyyZ^5HCmq1DQn zf3~cMf?|cx2nm0}OVul|FgUpXB=btw`SYbp`fa=Lr4f}u;*aB zn2q%A#6H#)uJZ&E36C7^IvC%TZL*xC&an*i1XY3j1xM(=a{vH%oMT{QU|;~^$2$B^ z;`wd9GH^4$0E#eN)QX1EhCBZM6aSyd{DL_M$mL*Q0*L|uYu*iPlk5yV2$O*U04s9} zD3dV_APb8C00031000gEc(Ys$lmUOy3W7ioMd#{frDpUY3mQn@v#x^%^p|C@OJq|b`MTQ(F6nW1wYI~ooaC 400); } From 686e9af444a0b4aba8c81b0f7b58df0ad769ffcc Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 12:27:04 -0700 Subject: [PATCH 147/714] fix from @sulkaharo, now we should be able to correctly render treatments no mater what BG unit is used --- 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 af4dc7fd82d..5f2f209ac8e 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1091,7 +1091,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var totalBG = 0; closeBGs.forEach(function(d) { - totalBG += d.y; + totalBG += Number(d.y); }); return totalBG > 0 && closeBGs.length > 0 ? (totalBG / closeBGs.length) : 450; From 9228db9a08e517bb57a5499afe7de3cbcaff0359 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 14 Mar 2015 12:36:56 -0700 Subject: [PATCH 148/714] get rid of   to pull the hourglass and X closer together --- 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 ef21efe73b5..6d34db45f11 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -316,7 +316,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; function updateCurrentSGV(value) { if (value == 9) { - currentBG.html(' '); + currentBG.text(''); } else if (value < 39) { currentBG.html(errorCodeToDisplay(value)); } else if (value < 40) { From 2496a131cb2d7881bb3ac2e5a3e36a49fff00d30 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 01:47:26 -0700 Subject: [PATCH 149/714] initial support for an adjustable focus range --- static/css/main.css | 56 +++++++++++++++++++++++++++++++++++++++++++-- static/index.html | 6 +++++ static/js/client.js | 28 ++++++++++++++++------- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 8332c965c5c..6880f6fe1ad 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -249,6 +249,29 @@ div.tooltip { display: none; } +.focus-range { + list-style: none; + margin: 4px; + padding: 0; + width: 250px; + text-align: center; +} + +.focus-range li { + display: inline-block; + font-size: 14px; + white-space: nowrap; + border-radius: 5px; + border: 2px solid #000; + cursor: pointer; +} + +.focus-range li.selected { + border-color: #bdbdbd; + color: #000; + background: #bdbdbd; +} + @media (max-width: 800px) { .bgStatus { width: 40%; @@ -282,6 +305,10 @@ div.tooltip { line-height: 40px; } + .focus-range { + margin: 0; + } + #silenceBtn * { font-size: 30px; } @@ -401,11 +428,23 @@ div.tooltip { font-size: 15px; } + .focus-range { + position: absolute; + top: 70px; + left: 10px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } + #chartContainer { - top: 210px; + top: 190px; } #chartContainer svg { - height: calc(100vh - (210px)); + height: calc(100vh - (190px)); } } @@ -467,6 +506,7 @@ div.tooltip { .time { font-size: 70px; line-height: 60px; + padding-top: 5px; } .timeOther { @@ -495,4 +535,16 @@ div.tooltip { font-size: 15px; } + .focus-range { + position: absolute; + top: 10px; + left: 10px; + margin: 0; + width: auto; + } + + .focus-range li { + display: block; + } + } diff --git a/static/index.html b/static/index.html index 8efb378d2ce..e9cce740f68 100644 --- a/static/index.html +++ b/static/index.html @@ -48,6 +48,12 @@

Nightscout

+
    +
  • 3HR
  • +
  • 6HR
  • +
  • 12HR
  • +
  • 24HR
  • +
diff --git a/static/js/client.js b/static/js/client.js index b090a2771e5..39a970b84d8 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -10,10 +10,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , ONE_MIN_IN_MS = 60000 , FIVE_MINS_IN_MS = 300000 , SIX_MINS_IN_MS = 360000 + , THREE_HOURS_MS = 3 * 60 * 60 * 1000 , TWENTY_FIVE_MINS_IN_MS = 1500000 , THIRTY_MINS_IN_MS = 1800000 , SIXTY_MINS_IN_MS = 3600000 - , FOCUS_DATA_RANGE_MS = 12600000 // 3.5 hours of actual data , FORMAT_TIME_12 = '%I:%M' , FORMAT_TIME_24 = '%H:%M%' , FORMAT_TIME_SCALE = '%I %p' @@ -38,6 +38,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , opacity = {current: 1, DAY: 1, NIGHT: 0.5} , now = Date.now() , data = [] + , foucusRangeMS = THREE_HOURS_MS , audio = document.getElementById('audio') , alarmInProgress = false , currentAlarmType = null @@ -211,7 +212,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; d3.select('.brush') .transition() .duration(UPDATE_TRANS_MS) - .call(brush.extent([new Date(dataRange[1].getTime() - FOCUS_DATA_RANGE_MS), dataRange[1]])); + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); brushed(true); // clear user brush tracking @@ -291,15 +292,15 @@ 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() != FOCUS_DATA_RANGE_MS) { + if (brushExtent[1].getTime() - brushExtent[0].getTime() != foucusRangeMS) { // ensure that brush updating is with the time range - if (brushExtent[0].getTime() + FOCUS_DATA_RANGE_MS > d3.extent(data, dateFn)[1].getTime()) { - brushExtent[0] = new Date(brushExtent[1].getTime() - FOCUS_DATA_RANGE_MS); + 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() + FOCUS_DATA_RANGE_MS); + brushExtent[1] = new Date(brushExtent[0].getTime() + foucusRangeMS); d3.select('.brush') .call(brush.extent([brushExtent[0], brushExtent[1]])); } @@ -533,7 +534,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // 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); + var focusRangeAdjustment = 1 + (foucusRangeMS / THREE_HOURS_MS) / 10; + 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) }); @@ -922,7 +925,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var updateBrush = d3.select('.brush').transition().duration(UPDATE_TRANS_MS); if (!brushInProgress) { updateBrush - .call(brush.extent([new Date(dataRange[1].getTime() - FOCUS_DATA_RANGE_MS), dataRange[1]])); + .call(brush.extent([new Date(dataRange[1].getTime() - foucusRangeMS), dataRange[1]])); brushed(true); } else { updateBrush @@ -1409,6 +1412,15 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; 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; + updateChart(false); + }); + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Client-side code to connect to server and handle incoming data //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From f472ca31bd4e365de9a35b83baae82bccc579176 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 02:00:29 -0700 Subject: [PATCH 150/714] hide focus range selectors when there is an alarm in narrow mode --- static/css/main.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 6880f6fe1ad..8e15c11f37f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -428,10 +428,14 @@ div.tooltip { font-size: 15px; } + .alarming .focus-range { + display: none; + } + .focus-range { position: absolute; top: 70px; - left: 10px; + left: 20px; margin: 0; width: auto; } From c54a3226d52fa3cd4127b08394e1136763a96a26 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 02:20:29 -0700 Subject: [PATCH 151/714] button clean up --- static/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 8e15c11f37f..bec90f9fcdf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -208,7 +208,7 @@ span.pill label { .alarming .bgButton { border-color: #bdbdbd; color: #000; - box-shadow: 2px 4px 6px #ddd; + box-shadow: 2px 2px 0 #ddd; } .alarming .bgButton.urgent { From 45f4979819d2d5deb8550959c69954579fee42c7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 02:40:47 -0700 Subject: [PATCH 152/714] prevent some flashing while data loads --- static/css/main.css | 9 +++++++++ static/index.html | 2 +- static/js/client.js | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index bec90f9fcdf..fe2606b2637 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -118,6 +118,11 @@ span.pill label { font-size: 20px; line-height: 30px; } + +.loading #lastEntry { + display: none; +} + #lastEntry { background: #808080; border-color: #808080; @@ -249,6 +254,10 @@ div.tooltip { display: none; } +.loading .focus-range { + display: none; +} + .focus-range { list-style: none; margin: 4px; diff --git a/static/index.html b/static/index.html index e9cce740f68..5fab9d6b180 100644 --- a/static/index.html +++ b/static/index.html @@ -24,7 +24,7 @@

Nightscout

-
+
diff --git a/static/js/client.js b/static/js/client.js index 39a970b84d8..492225f2ea7 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -973,6 +973,10 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // update x axis domain context.select('.x') .call(xAxis2); + + if (init) { + $('.container').removeClass('loading'); + } } function sgvToColor(sgv) { From 26ba414d82fa110f027a40e9de58511ecfe0ed64 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 11:20:03 -0700 Subject: [PATCH 153/714] better scaling when using bigger ranges; use same color as clock foe rang selectors --- static/css/main.css | 4 ++-- static/js/client.js | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index fe2606b2637..d5be82207c1 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -276,9 +276,9 @@ div.tooltip { } .focus-range li.selected { - border-color: #bdbdbd; + border-color: #808080; color: #000; - background: #bdbdbd; + background: #808080; } @media (max-width: 800px) { diff --git a/static/js/client.js b/static/js/client.js index 492225f2ea7..c5a98adc8a8 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -11,6 +11,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , 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 @@ -135,7 +136,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } else if (noise < 2 && browserSettings.showRawbg != 'always') { return 0; } else if (filtered == 0 || sgv < 40) { - console.info('Skipping ratio adjustment for SGV ' + sgv); return scale * (unfiltered - intercept) / slope; } else { var ratio = scale * (filtered - intercept) / slope / sgv; @@ -461,11 +461,14 @@ 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 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); - return radius; + + return radius / focusRangeAdjustment; }; function prepareFocusCircles(sel) { @@ -534,7 +537,6 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; // add treatment bubbles // a higher bubbleScale will produce smaller bubbles (it's not a radius like focusDotRadius) - var focusRangeAdjustment = 1 + (foucusRangeMS / THREE_HOURS_MS) / 10; var bubbleScale = (prevChartWidth < WIDTH_SMALL_DOTS ? 4 : (prevChartWidth < WIDTH_BIG_DOTS ? 3 : 2)) * focusRangeAdjustment; focus.selectAll('circle') From bf5c3ddf58d0d411c4485282c6938279d661a6fb Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 19:46:44 -0700 Subject: [PATCH 154/714] bigger pills for bigger screens; only use the briter gray for the pill value --- static/css/main.css | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index d5be82207c1..290df78b8d8 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -72,7 +72,7 @@ body { } .bgStatus .currentDetails { - font-size: 20px; + font-size: 30px; } .currentDetails > span:not(:first-child) { @@ -82,7 +82,7 @@ body { span.pill { white-space: nowrap; border-radius: 5px; - border: 2px solid #bdbdbd; + border: 2px solid #808080; } span.pill * { @@ -97,7 +97,7 @@ span.pill em { span.pill label { color: #000; - background: #bdbdbd; + background: #808080; } .bgStatus.current .currentBG { @@ -129,7 +129,7 @@ span.pill label { } #lastEntry.pill em { - color: #808080; + color: #bdbdbd; background: #000; } @@ -304,6 +304,10 @@ div.tooltip { line-height: 70px; } + .bgStatus .currentDetails { + font-size: 20px; + } + .time { font-size: 70px; line-height: 60px; From 17600b74baa8b8ab853073f05752745a0a81511f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 19:56:40 -0700 Subject: [PATCH 155/714] fixed radius of time ago value --- static/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/main.css b/static/css/main.css index 290df78b8d8..2a57d94d187 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -131,6 +131,7 @@ span.pill label { #lastEntry.pill em { color: #bdbdbd; background: #000; + border-radius: 5px 0 0 5px; } #lastEntry.pill label { From 39b141facfb4a18529e023e597bd04e5719862b2 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 19:57:49 -0700 Subject: [PATCH 156/714] make clicking on the focus ranges more responsive by using a timeout --- static/js/client.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index c5a98adc8a8..34e5d4356f1 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1396,9 +1396,13 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; context.append('g') .attr('class', 'y axis'); + window.onresize = function () { + updateChartSoon() + } + // look for resize but use timer to only call the update script when a resize stops var resizeTimer; - window.onresize = function () { + function updateChartSoon() { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { updateChart(false); @@ -1424,7 +1428,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; li.addClass('selected'); var hours = Number(li.data('hours')); foucusRangeMS = hours * 60 * 60 * 1000; - updateChart(false); + updateChartSoon(); }); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 7309e66bac45b206b5f1983bbacbd1d38ca41d88 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 20:01:40 -0700 Subject: [PATCH 157/714] another radius tweek --- static/css/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 2a57d94d187..18b6944a4cb 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -131,7 +131,7 @@ span.pill label { #lastEntry.pill em { color: #bdbdbd; background: #000; - border-radius: 5px 0 0 5px; + border-radius: 4px 0 0 4px; } #lastEntry.pill label { From b73aec7c8645ca2668dca319574a9afa251fb983 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 20:13:15 -0700 Subject: [PATCH 158/714] only show 'time ago' offset == -1 (only while loading) --- 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 34e5d4356f1..75bc5600c9c 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1067,7 +1067,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , parts = {}; if (offset < MINUTE_IN_SECS * -5) parts = { label: 'in the future' }; - else if (offset <= 0) parts = { label: 'time ago' }; + 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' }; From 647dec228ee39f5eef24ac4c93c2bb5792464390 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 23:23:57 -0700 Subject: [PATCH 159/714] display uploader battery next to time ago --- lib/websocket.js | 18 ++++++++++++++---- server.js | 2 +- static/css/main.css | 16 ++++++++-------- static/index.html | 3 ++- static/js/client.js | 13 +++++++++++++ 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 161d239f8b4..59a66e35074 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,6 +1,6 @@ var async = require('async'); -function websocket (env, server, entries, treatments, profiles) { +function websocket (env, server, entries, treatments, profiles, devicestatus) { "use strict"; // CONSTANTS var ONE_HOUR = 3600000, @@ -31,7 +31,8 @@ var dir2Char = { mbgData = [], calData = [], profileData = [], - patientData = []; + patientData = [], + devicestatusData = {}; function start ( ) { io = require('socket.io').listen(server, { @@ -154,6 +155,7 @@ function update() { treatmentData = []; mbgData = []; profileData = []; + devicestatusData = {}; var earliest_data = now - TWO_DAYS; async.parallel({ @@ -228,6 +230,14 @@ function update() { callback(); }); } + , devicestatus: function(callback) { + devicestatus.last(function (err, result) { + devicestatusData = { + uploaderBattery: result.uploaderBattery + }; + callback(); + }) + } }, loadData); return update; @@ -311,8 +321,8 @@ function loadData() { //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); - patientData = [actual, predicted, mbg, treatment, profile, cal]; + 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( ); diff --git a/server.js b/server.js index e427926d673..10eb3602847 100644 --- a/server.js +++ b/server.js @@ -115,7 +115,7 @@ store(function ready ( ) { // setup socket io for data and message transmission /////////////////////////////////////////////////// var websocket = require('./lib/websocket'); - var io = websocket(env, server, entriesStorage, treatmentsStorage, profile); + var io = websocket(env, server, entriesStorage, treatmentsStorage, profile, devicestatusStorage); }); /////////////////////////////////////////////////// diff --git a/static/css/main.css b/static/css/main.css index 18b6944a4cb..b4b9e878899 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -119,38 +119,38 @@ span.pill label { line-height: 30px; } -.loading #lastEntry { +.loading .timeOther { display: none; } -#lastEntry { +.timeOther .pill { background: #808080; border-color: #808080; } -#lastEntry.pill em { +.timeOther .pill em { color: #bdbdbd; background: #000; border-radius: 4px 0 0 4px; } -#lastEntry.pill label { +.timeOther .pill label { background: #808080; } -#lastEntry.warn, #lastEntry.warn label { +.timeOther .warn, .timeOther .warn label { background: yellow; border-color: yellow; } -#lastEntry.warn em { +.timeOther .warn em { color: yellow; } -#lastEntry.urgent, #lastEntry.urgent label { +.timeOther .urgent, .timeOther .urgent label { background: red; border-color: red; } -#lastEntry.urgent em { +.timeOther .urgent em { color: red; } diff --git a/static/index.html b/static/index.html index 5fab9d6b180..6a8e1df85fc 100644 --- a/static/index.html +++ b/static/index.html @@ -44,7 +44,8 @@

Nightscout

---
- - + +
diff --git a/static/js/client.js b/static/js/client.js index 75bc5600c9c..ffb9f4f2587 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -35,6 +35,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , treatments , profile , cal + , devicestatusData , padding = { top: 0, right: 10, bottom: 30, left: 10 } , opacity = {current: 1, DAY: 1, NIGHT: 0.5} , now = Date.now() @@ -442,6 +443,17 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClockDisplay(); updateTimeAgo(); + var battery = devicestatusData.uploaderBattery; + $('#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').toggleClass('warn', battery <= 30 && battery > 20); + $('#uploaderBattery').toggleClass('urgent', battery <= 20); + updateBGDelta(prevSGV.y, latestSGV.y); updateIOBIndicator(nowDate); @@ -1452,6 +1464,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; profile = d[4][0]; cal = d[5][d[5].length-1]; + devicestatusData = d[6]; var temp1 = [ ]; if (cal && showRawBGs()) { From 9062a6c5b0680278bed6849132355b7e9b0f850a Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 15 Mar 2015 23:40:39 -0700 Subject: [PATCH 160/714] smaller battery icon when screen < 800px --- static/css/main.css | 4 ++++ static/index.html | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index b4b9e878899..62dce4ceba5 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -319,6 +319,10 @@ div.tooltip { line-height: 40px; } + #uploaderBattery label { + font-size: 15px !important; + } + .focus-range { margin: 0; } diff --git a/static/index.html b/static/index.html index 6a8e1df85fc..0ff53abe7ba 100644 --- a/static/index.html +++ b/static/index.html @@ -45,7 +45,7 @@

Nightscout

---
- +
From acceaa5521de340faee34166f542285831d30bde Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 13:20:51 -0700 Subject: [PATCH 161/714] respect potential I/O errors in pebble response Make pebble a more sensitive to potential I/O errors. --- lib/pebble.js | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/pebble.js b/lib/pebble.js index f8c31bf54dc..4f78cf68d21 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -78,13 +78,17 @@ function pebble (req, res) { } , profile: function(callback) { loadProfile(req, function (err, profileResults) { - profileResults.forEach(function (profile) { - if (profile) { - if (profile.dia) { - profileResult = profile; - } - } - }); + if (!err && profileResults) { + profileResults.forEach(function (profile) { + if (profile) { + if (profile.dia) { + profileResult = profile; + } + } + }); + } else { + console.error("pebble profile error", arguments); + } callback(); }); } @@ -92,6 +96,7 @@ function pebble (req, res) { if (req.rawbg) { var cq = { count: req.count, find: {type: 'cal'} }; req.entries.list(cq, function (err, results) { + if (!err && results) { results.forEach(function (element) { if (element) { calData.push({ @@ -101,7 +106,10 @@ function pebble (req, res) { }); } }); - callback(); + } else { + console.error("pebble cal error", arguments); + } + callback(); }); } else { callback(); @@ -111,6 +119,7 @@ function pebble (req, res) { var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; req.entries.list(q, function(err, results) { + if (!err && results) { results.forEach(function(element, index) { if (element) { var obj = {}; @@ -139,6 +148,9 @@ function pebble (req, res) { sgvData.push(obj); } }); + } else { + console.error("pebble entries error", arguments); + } callback(); }); } From 2376352190a26c5815ba300adac82ced84b48235 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 14:20:36 -0700 Subject: [PATCH 162/714] does compression interfere with socket.io? Test theory with @jimsiff. --- server.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.js b/server.js index e427926d673..8f23c86fec1 100644 --- a/server.js +++ b/server.js @@ -72,6 +72,11 @@ 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 + var IGNORE = /^\/socket/; + if (IGNORE.test(req.url)) { + console.log('XXXX', 'IGNORE COMPRESSION', req.url, IGNORE.test(req.url)); + return false; + } return compression.filter(req, res); } From 8cebeb3f990af1ada04137f2cb842707e6e1fa9d Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 15:14:49 -0700 Subject: [PATCH 163/714] turn off double compression? Maybe help, testing with @jimsiff. --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 161d239f8b4..159c8541349 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -38,7 +38,7 @@ var dir2Char = { //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': true + 'browser client gzip': false }); } // get data from database and setup to update every minute From ad9ea58b83d7be03d1f0c4e4d4437d965bd56757 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 15:53:21 -0700 Subject: [PATCH 164/714] try different approach for azure git scm commit id --- deploy.sh | 1 + env.js | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/deploy.sh b/deploy.sh index 4bc630de4ab..8e78ae6e58a 100755 --- a/deploy.sh +++ b/deploy.sh @@ -113,6 +113,7 @@ selectNodeVersion if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then cd "$DEPLOYMENT_TARGET" eval $NPM_CMD install --production + echo "\""$SCM_COMMIT_ID\"" > scm-commit-id.json exitWithMessageOnError "npm failed" cd - > /dev/null fi diff --git a/env.js b/env.js index c063c2274a2..7c9fccf34a4 100644 --- a/env.js +++ b/env.js @@ -22,10 +22,8 @@ function config ( ) { var git = require('git-rev'); if (readENV('SCM_GIT_EMAIL') == 'windowsazure' && readENV('ScmType') == 'GitHub') { - git.cwd('/home/site/repository'); - } - if (readENV('SCM_COMMIT_ID')) { - env.head = readENV('SCM_COMMIT_ID'); + env.head = require('./scm-commit-id.json'); + console.log("SCM COMMIT ID", env.head); } else { git.short(function record_git_head (head) { console.log("GIT HEAD", head); From 5d96424fa2b3b116c88f13924615d08e43b1955b Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 15:54:25 -0700 Subject: [PATCH 165/714] fix deploy syntax error --- deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy.sh b/deploy.sh index 8e78ae6e58a..fd4416e70e9 100755 --- a/deploy.sh +++ b/deploy.sh @@ -113,7 +113,7 @@ selectNodeVersion if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then cd "$DEPLOYMENT_TARGET" eval $NPM_CMD install --production - echo "\""$SCM_COMMIT_ID\"" > scm-commit-id.json + echo "\"$SCM_COMMIT_ID\"" > scm-commit-id.json exitWithMessageOnError "npm failed" cd - > /dev/null fi From 5c66dc810a49c849996d415882cdd4dec1653a36 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 16:08:43 -0700 Subject: [PATCH 166/714] try letting kudu move everything --- deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy.sh b/deploy.sh index fd4416e70e9..29fbdc49d2b 100755 --- a/deploy.sh +++ b/deploy.sh @@ -99,6 +99,7 @@ selectNodeVersion () { # ---------- echo Handling node.js deployment. +echo "\"$SCM_COMMIT_ID\"" > $DEPLOYMENT_SOURCE/scm-commit-id.json # 1. KuduSync if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then @@ -113,7 +114,6 @@ selectNodeVersion if [ -e "$DEPLOYMENT_TARGET/package.json" ]; then cd "$DEPLOYMENT_TARGET" eval $NPM_CMD install --production - echo "\"$SCM_COMMIT_ID\"" > scm-commit-id.json exitWithMessageOnError "npm failed" cd - > /dev/null fi From ba3dda95e989ed2352f26f728e36b2d961b8ef42 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 16:13:23 -0700 Subject: [PATCH 167/714] fixing casing! --- env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.js b/env.js index 7c9fccf34a4..8e34a7e7608 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('SCM_GIT_EMAIL') == 'windowsazure' && readENV('ScmType') == 'GitHub') { + if (readENV('SCM_GIT_EMAIL') == 'windowsazure' && readENV('SCMTYPE') == 'GitHub') { env.head = require('./scm-commit-id.json'); console.log("SCM COMMIT ID", env.head); } else { From 47a0679b4095934dd752623e84e1452ad129966b Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 16:34:18 -0700 Subject: [PATCH 168/714] sniff for azure differently --- env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.js b/env.js index 8e34a7e7608..81e6ad219fb 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('SCM_GIT_EMAIL') == 'windowsazure' && readENV('SCMTYPE') == 'GitHub') { + if (readENV('APPSETTING_ScmType') == readENV('ScmType')) { env.head = require('./scm-commit-id.json'); console.log("SCM COMMIT ID", env.head); } else { From 429d3b2d06aa433486891de9d6fb89500ab052db Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 16 Mar 2015 17:52:30 -0700 Subject: [PATCH 169/714] better handling of potentional errors and null vars --- lib/websocket.js | 96 ++++++++++++++++++++++++--------------------- static/js/client.js | 26 +++++++----- 2 files changed, 68 insertions(+), 54 deletions(-) diff --git a/lib/websocket.js b/lib/websocket.js index 59a66e35074..f035dd90151 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -162,47 +162,51 @@ function update() { entries: function(callback) { var q = { find: {"date": {"$gte": earliest_data}} }; entries.list(q, function (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 - }); + 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"} }; entries.list(cq, function (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 - }); - } - }); + 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(); }); } @@ -219,22 +223,26 @@ function update() { } , profile: function(callback) { profiles.list(function (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; + 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) { devicestatus.last(function (err, result) { - devicestatusData = { - uploaderBattery: result.uploaderBattery - }; + if (!err && result) { + devicestatusData = { + uploaderBattery: result.uploaderBattery + }; + } callback(); }) } diff --git a/static/js/client.js b/static/js/client.js index ffb9f4f2587..c2d3c5c9516 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -443,16 +443,22 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateClockDisplay(); updateTimeAgo(); - var battery = devicestatusData.uploaderBattery; - $('#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').toggleClass('warn', battery <= 30 && battery > 20); - $('#uploaderBattery').toggleClass('urgent', battery <= 20); + 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.y, latestSGV.y); updateIOBIndicator(nowDate); From cce7622be8f4418f120e31e0762d1479e16a6c03 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 16 Mar 2015 19:28:07 -0700 Subject: [PATCH 170/714] some tweeks for the battery and time ago pills --- static/css/main.css | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 62dce4ceba5..554be735c1e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -79,27 +79,35 @@ body { margin-left: 5px; } -span.pill { +.pill { white-space: nowrap; border-radius: 5px; border: 2px solid #808080; } -span.pill * { +.pill * { padding-left: 2px; padding-right: 2px; } -span.pill em { +.pill em { font-style: normal; font-weight: bold; } -span.pill label { +.pill label { color: #000; background: #808080; } +.pill.warn em { + color: yellow !important; +} + +.pill.urgent em { + color: red !important; +} + .bgStatus.current .currentBG { text-decoration: none; } @@ -138,20 +146,8 @@ span.pill label { background: #808080; } -.timeOther .warn, .timeOther .warn label { - background: yellow; - border-color: yellow; -} -.timeOther .warn em { - color: yellow; -} - -.timeOther .urgent, .timeOther .urgent label { - background: red; - border-color: red; -} -.timeOther .urgent em { - color: red; +#uploaderBattery label { + padding: 0 !important; } #chartContainer { From 1940cf4a990fa61b5b80dd16785fb5df728b6532 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 15:14:49 -0700 Subject: [PATCH 171/714] turn off double compression? Maybe help, testing with @jimsiff. --- lib/websocket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/websocket.js b/lib/websocket.js index 161d239f8b4..159c8541349 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -38,7 +38,7 @@ var dir2Char = { //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': true + 'browser client gzip': false }); } // get data from database and setup to update every minute From 46f4a40ecc7241e85b95ebe0023d6860e6256e4f Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Mon, 16 Mar 2015 20:11:23 -0700 Subject: [PATCH 172/714] adjust size in case where BG is at LOW or HIGH limit --- static/css/main.css | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 554be735c1e..2a9c92880a0 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -59,7 +59,7 @@ body { vertical-align: middle; } -.bgStatus .currentBG.icon-hourglass { +.bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 90px; } @@ -292,7 +292,7 @@ div.tooltip { line-height: 80px; } - .bgStatus .currentBG.icon-hourglass { + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 70px; } @@ -346,7 +346,7 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentBG.icon-hourglass { + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 50px; } @@ -398,8 +398,9 @@ div.tooltip { line-height: 70px; } - .bgStatus .currentBG.icon-hourglass { + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 60px; + padding-left: 20px; } .bgStatus .currentDirection { @@ -508,7 +509,7 @@ div.tooltip { line-height: 80px; } - .bgStatus .currentBG.icon-hourglass { + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 70px; } @@ -540,7 +541,7 @@ div.tooltip { line-height: 60px; } - .bgStatus .currentBG.icon-hourglass { + .bgStatus .currentBG.bg-limit, .bgStatus .currentBG.icon-hourglass { font-size: 60px; } From 4e17e8e9034541aefafc91ec396cf516d6cc27d6 Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 16 Mar 2015 23:16:57 -0700 Subject: [PATCH 173/714] check for azure and check for value Make sure the environment variables are set to GitHub. This should help guard against false positives when sniffing whether or not we're in Azure. --- env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.js b/env.js index 81e6ad219fb..2669dee95fd 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')) { + if (readENV('APPSETTING_ScmType') == readENV('ScmType') && readENV('ScmType') == 'GitHub') { env.head = require('./scm-commit-id.json'); console.log("SCM COMMIT ID", env.head); } else { From 04d1d37fc0fa29ebf8545ea4701cd7ea0af69c93 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 07:59:06 -0700 Subject: [PATCH 174/714] a couple more little css tweeks for battery --- static/css/main.css | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index 2a9c92880a0..ef5bc95efbf 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -116,7 +116,6 @@ body { color: #808080; font-size: 100px; line-height: 100px; - padding-top: 15px; } .timebox { text-align: center; @@ -259,6 +258,7 @@ div.tooltip { list-style: none; margin: 4px; padding: 0; + color: #808080; width: 250px; text-align: center; } @@ -443,6 +443,10 @@ div.tooltip { font-size: 15px; } + #uploaderBattery label { + font-size: 15px !important; + } + .alarming .focus-range { display: none; } @@ -532,6 +536,10 @@ div.tooltip { font-size: 15px; } + #uploaderBattery label { + font-size: 15px !important; + } + } @media (max-height: 480px) and (max-width: 400px) { From b38748aee25ed4cad6c777701fe3afa49d843ad6 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 08:01:06 -0700 Subject: [PATCH 175/714] removed first attempt to disable compression for sockets since it didn't work --- server.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/server.js b/server.js index 8f23c86fec1..e427926d673 100644 --- a/server.js +++ b/server.js @@ -72,11 +72,6 @@ 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 - var IGNORE = /^\/socket/; - if (IGNORE.test(req.url)) { - console.log('XXXX', 'IGNORE COMPRESSION', req.url, IGNORE.test(req.url)); - return false; - } return compression.filter(req, res); } From b914f41814dac63daab7132c0538d37b52fb677c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 17:25:54 -0700 Subject: [PATCH 176/714] fixed bug where the now line would jump when changing the focus range after scrolling --- 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 c2d3c5c9516..8d07beb5d5a 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -949,7 +949,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; brushed(true); } else { updateBrush - .call(brush.extent([currentBrushExtent[0], currentBrushExtent[1]])); + .call(brush.extent([new Date(currentBrushExtent[1].getTime() - foucusRangeMS), currentBrushExtent[1]])); brushed(true); } From a6978138f7dca33d63958d0b25eaa2a4e5fc20bf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 17:26:19 -0700 Subject: [PATCH 177/714] some more css tweeks --- static/css/main.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/css/main.css b/static/css/main.css index ef5bc95efbf..9e7e36a6590 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -265,7 +265,7 @@ div.tooltip { .focus-range li { display: inline-block; - font-size: 14px; + font-size: 18px; white-space: nowrap; border-radius: 5px; border: 2px solid #000; @@ -308,6 +308,7 @@ div.tooltip { .time { font-size: 70px; line-height: 60px; + padding-top: 8px; } .timeOther { @@ -323,6 +324,10 @@ div.tooltip { margin: 0; } + .focus-range li { + font-size: 14px; + } + #silenceBtn * { font-size: 30px; } From 469d86878e6f1ef9a9d6f91496ae1be43861038b Mon Sep 17 00:00:00 2001 From: Ben West Date: Tue, 17 Mar 2015 22:40:02 -0700 Subject: [PATCH 178/714] rebasing, it seems to run --- app.js | 67 +++++++++++++++++++++++++++++ lib/devicestatus.js | 7 ++-- lib/entries.js | 7 ++-- lib/treatments.js | 6 +-- package.json | 4 +- server.js | 100 +++++++++++--------------------------------- 6 files changed, 102 insertions(+), 89 deletions(-) create mode 100644 app.js diff --git a/app.js b/app.js new file mode 100644 index 00000000000..2fc1033604f --- /dev/null +++ b/app.js @@ -0,0 +1,67 @@ + +var express = require('express'); +function create (env) { + var store = env.store; + var pushover = require('./lib/pushover')(env); + /////////////////////////////////////////////////// + // api and json object variables + /////////////////////////////////////////////////// + var entries = require('./lib/entries')(env.mongo_collection, store); + var settings = require('./lib/settings')(env.settings_collection, store); + var treatments = require('./lib/treatments')(env.treatments_collection, store, pushover); + var profile = require('./lib/profile')(env.profile_collection, store); + + var devicestatus = require('./lib/devicestatus')(env.devicestatus_collection, store); + var api = require('./lib/api/')(env, entries, settings, treatments, profile, devicestatus); + var pebble = require('./lib/pebble'); + var compression = require('compression'); + + var app = express(); + app.entries = entries; + app.treatments = treatments; + app.profiles = profile; + app.devicestatus = devicestatus; + var appInfo = env.name + ' ' + env.version; + 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); + } + + //if (env.api_secret) { + // console.log("API_SECRET", env.api_secret); + //} + app.use('/api/v1', api); + + + // pebble data + app.get('/pebble', pebble(entries, treatments, profile, devicestatus)); + + //app.get('/package.json', software); + + // define static server + //TODO: JC - changed cache to 1 hour from 30d ays to bypass cache hell until we have a real solution + var staticFiles = express.static(env.static_files, {maxAge: 60 * 60 * 1000}); + + // serve the static content + app.use(staticFiles); + + 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') { + app.use(errorhandler()); + //} + return app; +} +module.exports = create; + diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 82a2781eb0b..5195ecb5f4f 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -42,7 +42,6 @@ function ensureIndexes(name, storage) { }); } -module.exports = { - storage: storage, - ensureIndexes: ensureIndexes -}; + +storage.ensureIndexes = ensureIndexes; +module.exports = storage; diff --git a/lib/entries.js b/lib/entries.js index 4363bf46967..4310e5db81d 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -196,7 +196,6 @@ function ensureIndexes(name, storage) { } // expose module -module.exports = { - storage: storage, - ensureIndexes: ensureIndexes -}; +storage.ensureIndexes = ensureIndexes; +module.exports = storage; + diff --git a/lib/treatments.js b/lib/treatments.js index c6dfae54bbb..27bd98e2444 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -137,8 +137,6 @@ function ensureIndexes(name, storage) { } }); } +storage.ensureIndexes = ensureIndexes; +module.exports = storage; -module.exports = { - storage: storage, - ensureIndexes: ensureIndexes -}; diff --git a/package.json b/package.json index 92b50a630df..27f30c05ab1 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ "moment": "2.8.1", "pushover-notifications": "0.2.0", "sgvdata": "0.0.2", - "socket.io": "^0.9.17" + "socket.io": "^0.9.17", + "git-rev": "git://github.com/bewest/git-rev.git", + "bootevent": "0.0.1" }, "devDependencies": { "istanbul": "~0.3.5", diff --git a/server.js b/server.js index 10eb3602847..4bff559d76b 100644 --- a/server.js +++ b/server.js @@ -26,79 +26,15 @@ // DB Connection setup and utils /////////////////////////////////////////////////// -var software = require('./package.json'); var env = require('./env')( ); -var pushover = require('./lib/pushover')(env); - -var entries = require('./lib/entries'); -var treatments = require('./lib/treatments'); -var devicestatus = require('./lib/devicestatus'); - -var store = require('./lib/storage')(env, function() { - console.info("Mongo ready"); - entries.ensureIndexes(env.mongo_collection, store); - treatments.ensureIndexes(env.treatments_collection, store); - devicestatus.ensureIndexes(env.devicestatus_collection, store); -}); - - -var express = require('express'); -var compression = require('compression'); - -/////////////////////////////////////////////////// -// api and json object variables -/////////////////////////////////////////////////// -var entriesStorage = entries.storage(env.mongo_collection, store, pushover); -var settings = require('./lib/settings')(env.settings_collection, store); -var treatmentsStorage = treatments.storage(env.treatments_collection, store, pushover); -var profile = require('./lib/profile')(env.profile_collection, store); -var devicestatusStorage = devicestatus.storage(env.devicestatus_collection, store); -var api = require('./lib/api/')(env, entriesStorage, settings, treatmentsStorage, profile, devicestatusStorage); -var pebble = require('./lib/pebble'); -/////////////////////////////////////////////////// +var store = require('./lib/storage')(env) /////////////////////////////////////////////////// // setup http server /////////////////////////////////////////////////// var PORT = env.PORT; -var app = express(); -var appInfo = software.name + ' ' + software.version; -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('/api/v1', api); - -// pebble data -app.get('/pebble', pebble(entriesStorage, treatmentsStorage, profile, devicestatusStorage, env)); - -//app.get('/package.json', software); - -// define static server -//TODO: JC - changed cache to 1 hour from 30d ays to bypass cache hell until we have a real solution -var staticFiles = express.static(env.static_files, {maxAge: 60 * 60 * 1000}); - -// serve the static content -app.use(staticFiles); - -var bundle = require('./bundle')(); -app.use(bundle); - -// Handle errors with express's errorhandler, to display more readable error messages. -var errorhandler = require('errorhandler'); -//if (process.env.NODE_ENV === 'development') { - app.use(errorhandler()); -//} - -function create ( ) { +function create (app) { var transport = (env.ssl ? require('https') : require('http')); if (env.ssl) { @@ -107,15 +43,27 @@ function create ( ) { return transport.createServer(app); } -store(function ready ( ) { - var server = create( ).listen(PORT); - console.log('listening', PORT); - - /////////////////////////////////////////////////// - // setup socket io for data and message transmission - /////////////////////////////////////////////////// - var websocket = require('./lib/websocket'); - var io = websocket(env, server, entriesStorage, treatmentsStorage, profile, devicestatusStorage); -}); +var bootevent = require('bootevent'); +bootevent( ) + .acquire(function db (ctx, next) { + // initialize db connections + store( function ready ( ) { + console.log('storage system ready'); + ctx.store = store; + next( ); + }); + }) + .boot(function booted (ctx) { + env.store = ctx.store; + var app = require('./app')(env); + var server = create(app).listen(PORT); + console.log('listening', PORT); + /////////////////////////////////////////////////// + // 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); + }) +; /////////////////////////////////////////////////// From 1a38f4e1911c37ca644b35b4715ab7d21a3a631f Mon Sep 17 00:00:00 2001 From: Ben West Date: Sun, 2 Nov 2014 08:09:34 -0800 Subject: [PATCH 179/714] fix broken test --- lib/entries.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/entries.js b/lib/entries.js index 4310e5db81d..88b4f301dd7 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -196,6 +196,7 @@ function ensureIndexes(name, storage) { } // expose module +storage.storage = storage; storage.ensureIndexes = ensureIndexes; module.exports = storage; From ca54f38e77d4525cf129844d54069f3a33dff800 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 23:12:29 -0700 Subject: [PATCH 180/714] better future data warning --- static/js/client.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index 8d07beb5d5a..c8f1b825b02 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1080,22 +1080,26 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; } function timeAgo(time) { + var now = Date.now() , offset = time == -1 ? -1 : (now - time) / 1000 , parts = {}; - if (offset < MINUTE_IN_SECS * -5) parts = { label: 'in the future' }; + 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 if (offset < (WEEK_IN_SECS * 52)) parts = { value: Math.round(Math.abs(offset / WEEK_IN_SECS)), label: 'week ago' }; - else parts = { label: 'a long time 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 || offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_URGENT)) { + console.info('>>>>offset', offset, parts); + if (offset > DAY_IN_SECS * 7) { + parts.removeClass = 'current urgent'; + parts.addClass = 'warn'; + } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_URGENT)) { parts.removeClass = 'current warn'; parts.addClass = 'urgent'; } else if (offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_WARN)) { @@ -1350,7 +1354,11 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; lastEntry.find('em').show().text(ago.value); } - lastEntry.find('label').text(retroMode ? 'RETRO' : ago.label); + if (retroMode || ago.label) { + lastEntry.find('label').show().text(retroMode ? 'RETRO' : ago.label); + } else { + lastEntry.find('label').hide(); + } } function init() { From 1f01d030c621f9fbf42e945cb0fb79bbe6c5381b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 23:14:56 -0700 Subject: [PATCH 181/714] remove debug --- static/js/client.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/static/js/client.js b/static/js/client.js index c8f1b825b02..96a39931b25 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -1095,9 +1095,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; 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' }; - console.info('>>>>offset', offset, parts); if (offset > DAY_IN_SECS * 7) { - parts.removeClass = 'current urgent'; + parts.removeClass = 'current urgent'; parts.addClass = 'warn'; } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_URGENT)) { parts.removeClass = 'current warn'; From 6705b621d5ce861dcc1c58ccaa390c3ac25b189a Mon Sep 17 00:00:00 2001 From: Ben West Date: Mon, 3 Nov 2014 20:47:51 -0800 Subject: [PATCH 182/714] bring back ensureIndexes Make sure indexes are created for commonly sought fields. --- app.js | 26 ++---- lib/bootevent.js | 39 +++++++++ lib/devicestatus.js | 15 +--- lib/entries.js | 13 +-- lib/models/downloads.js | 0 lib/models/entries.js | 42 +++++++++ lib/pebble.js~ | 187 ++++++++++++++++++++++++++++++++++++++++ lib/poller.js | 18 ++++ lib/treatments.js | 13 +-- server.js | 17 +--- 10 files changed, 308 insertions(+), 62 deletions(-) create mode 100644 lib/bootevent.js create mode 100644 lib/models/downloads.js create mode 100644 lib/models/entries.js create mode 100644 lib/pebble.js~ create mode 100644 lib/poller.js diff --git a/app.js b/app.js index 2fc1033604f..88852d3b718 100644 --- a/app.js +++ b/app.js @@ -1,26 +1,18 @@ var express = require('express'); -function create (env) { - var store = env.store; - var pushover = require('./lib/pushover')(env); +var compression = require('compression'); +function create (env, ctx) { /////////////////////////////////////////////////// // api and json object variables /////////////////////////////////////////////////// - var entries = require('./lib/entries')(env.mongo_collection, store); - var settings = require('./lib/settings')(env.settings_collection, store); - var treatments = require('./lib/treatments')(env.treatments_collection, store, pushover); - var profile = require('./lib/profile')(env.profile_collection, store); - - var devicestatus = require('./lib/devicestatus')(env.devicestatus_collection, store); - var api = require('./lib/api/')(env, entries, settings, treatments, profile, devicestatus); - var pebble = require('./lib/pebble'); - var compression = require('compression'); + var api = require('./lib/api/')(env, ctx.entries, ctx.settings, ctx.treatments, ctx.profiles, ctx.devicestatus); + var pebble = ctx.pebble; var app = express(); - app.entries = entries; - app.treatments = treatments; - app.profiles = profile; - app.devicestatus = devicestatus; + 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. @@ -40,7 +32,7 @@ function create (env) { // pebble data - app.get('/pebble', pebble(entries, treatments, profile, devicestatus)); + app.get('/pebble', pebble(ctx.entries, ctx.treatments, ctx.profiles, ctx.devicestatus)); //app.get('/package.json', software); diff --git a/lib/bootevent.js b/lib/bootevent.js new file mode 100644 index 00000000000..c6c87196c24 --- /dev/null +++ b/lib/bootevent.js @@ -0,0 +1,39 @@ + +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.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); + ctx.pebble = require('./pebble'); + console.info("Ensuring indexes"); + + console.log(ctx.entries, ctx.entries.indexedFields); + store.ensureIndexes(ctx.entries( ), ctx.entries.indexedFields); + store.ensureIndexes(ctx.treatments( ), ctx.treatments.indexedFields); + store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields); + + next( ); + }) + ; + return proc; + +} +module.exports = boot; diff --git a/lib/devicestatus.js b/lib/devicestatus.js index 5195ecb5f4f..b839944442e 100644 --- a/lib/devicestatus.js +++ b/lib/devicestatus.js @@ -29,19 +29,10 @@ function storage (collection, storage) { api.list = list; api.create = create; api.last = last; + api.indexedFields = indexedFields; return api; } +var indexedFields = ['created_at']; +storage.indexedFields = indexedFields; -function ensureIndexes(name, storage) { - storage.with_collection(name)(function (err, collection) { - if (err) { - console.error("ensureIndexes, unable to get collection for: " + name + " - " + err); - } else { - storage.ensureIndexes(collection, ['created_at']); - } - }); -} - - -storage.ensureIndexes = ensureIndexes; module.exports = storage; diff --git a/lib/entries.js b/lib/entries.js index 88b4f301dd7..5854905be44 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -182,21 +182,14 @@ function storage(name, storage, pushover) { api.persist = persist; api.getEntries = getEntries; api.getEntry = getEntry; + api.indexedFields = indexedFields; return api; } -function ensureIndexes(name, storage) { - storage.with_collection(name)(function(err, collection) { - if (err) { - console.error("ensureIndexes, unable to get collection for: " + name + " - " + err); - } else { - storage.ensureIndexes(collection, ['date', 'type', 'sgv']); - } - }); -} +var indexedFields = [ 'date', 'type', 'sgv' ]; +storage.indexedFields = indexedFields; // expose module storage.storage = storage; -storage.ensureIndexes = ensureIndexes; module.exports = storage; diff --git a/lib/models/downloads.js b/lib/models/downloads.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/lib/models/entries.js b/lib/models/entries.js new file mode 100644 index 00000000000..8bea7ae6b25 --- /dev/null +++ b/lib/models/entries.js @@ -0,0 +1,42 @@ + +var _ = require('lodash'); + +var config = { + keys: ['sgv' ] + , fields: ['sgv', 'timestamp'] + // , +}; + +function sgvs ( ) { + +} + + +function make_timestamp ( ) { +} + +function make_field ( ) { +} + +function sanitizer (opts) { + +} + +function index (opts) { + function calc (data) { + var o = lo.pick(data, opts.keys); + var scheme, r; + var stack = [ ]; + if (o.type) { + scheme = o.type + ':/'; + } + opts.fields.forEach(function iter (elem) { + stack.push(data[elem]); + }); + r = [ scheme ].concat(stack).join('/'); + return r; + } + return calc; +} + +module.exports = index; diff --git a/lib/pebble.js~ b/lib/pebble.js~ new file mode 100644 index 00000000000..cf328b416d7 --- /dev/null +++ b/lib/pebble.js~ @@ -0,0 +1,187 @@ +'use strict'; + +var DIRECTIONS = { + NONE: 0 +, DoubleUp: 1 +, SingleUp: 2 +, FortyFiveUp: 3 +, Flat: 4 +, FortyFiveDown: 5 +, SingleDown: 6 +, DoubleDown: 7 +, 'NOT COMPUTABLE': 8 +, 'RATE OUT OF RANGE': 9 +}; + +var iob = require("./iob")(); +var async = require('async'); + +function directionToTrend (direction) { + var trend = 8; + if (direction in DIRECTIONS) { + trend = DIRECTIONS[direction]; + } + return trend; +} + +function pebble (req, res) { + var ONE_DAY = 24 * 60 * 60 * 1000 + , uploaderBattery + , treatmentResults + , profileResult + , sgvData = [ ] + , calData = [ ]; + + function scaleBg(bg) { + if (req.mmol) { + return (Math.round((bg / 18) * 10) / 10).toFixed(1); + } else { + return bg; + } + } + + function sendData () { + var now = Date.now(); + + //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; + if (req.iob) { + sgvData[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; + } + } + + var result = { status: [ {now: now} ], bgs: sgvData.slice(0, req.count), cals: calData }; + res.setHeader('content-type', 'application/json'); + res.write(JSON.stringify(result)); + res.end( ); + } + + var earliest_data = Date.now() - ONE_DAY; + + async.parallel({ + devicestatus: function (callback) { + req.devicestatus.last(function (err, value) { + if (!err && value) { + uploaderBattery = value.uploaderBattery; + } else { + console.error("req.devicestatus.tail", err); + } + callback(); + }); + } + , treatments: function(callback) { + loadTreatments(req, earliest_data, function (err, trs) { + treatmentResults = trs; + callback(); + }); + } + , profile: function(callback) { + loadProfile(req, function (err, profileResults) { + if (!err && profileResults) { + profileResults.forEach(function (profile) { + if (profile) { + if (profile.dia) { + profileResult = profile; + } + } + }); + } else { + console.error("pebble profile error", arguments); + } + callback(); + }); + } + , cal: function(callback) { + if (req.rawbg) { + var cq = { count: req.count, find: {type: 'cal'} }; + req.entries.list(cq, function (err, results) { + results.forEach(function (element) { + if (element) { + calData.push({ + slope: Math.round(element.slope) + , intercept: Math.round(element.intercept) + , scale: Math.round(element.scale) + }); + } + }); + callback(); + }); + } else { + callback(); + } + } + , entries: function(callback) { + var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; + + req.entries.list(q, function(err, results) { + results.forEach(function(element, index) { + if (element) { + var obj = {}; + var next = null; + var sgvs = results.filter(function(d) { + return !!d.sgv; + }); + if (index + 1 < sgvs.length) { + next = sgvs[index + 1]; + } + obj.sgv = scaleBg(element.sgv).toString(); + obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); + if (req.mmol) { + obj.bgdelta = obj.bgdelta.toFixed(1); + } + if ('direction' in element) { + obj.trend = directionToTrend(element.direction); + obj.direction = element.direction; + } + obj.datetime = element.date; + if (req.rawbg) { + obj.filtered = element.filtered; + obj.unfiltered = element.unfiltered; + obj.noise = element.noise; + } + sgvData.push(obj); + } + }); + callback(); + }); + } + }, sendData); + +} + +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); + } else { + fn(null, []); + } +} + +function loadProfile(req, fn) { + if (req.iob) { + req.profile.list(fn); + } else { + fn(null, []); + } +} + +function configure (entries, treatments, profile, devicestatus, env) { + function middle (req, res, next) { + req.entries = entries; + req.treatments = treatments; + req.profile = profile; + req.devicestatus = devicestatus; + 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'; + req.count = parseInt(req.query.count) || 1; + + next( ); + } + return [middle, pebble]; +} + +configure.pebble = pebble; +module.exports = configure; diff --git a/lib/poller.js b/lib/poller.js new file mode 100644 index 00000000000..fd78327a1b2 --- /dev/null +++ b/lib/poller.js @@ -0,0 +1,18 @@ + +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/treatments.js b/lib/treatments.js index 27bd98e2444..ddbc56cadd7 100644 --- a/lib/treatments.js +++ b/lib/treatments.js @@ -125,18 +125,11 @@ function storage (collection, storage, pushover) { api.list = list; api.create = create; + api.indexedFields = indexedFields; return api; } +var indexedFields = ['created_at', 'eventType']; +storage.indexedFields = indexedFields; -function ensureIndexes(name, storage) { - storage.with_collection(name)(function (err, collection) { - if (err) { - console.error("ensureIndexes, unable to get collection for: " + name + " - " + err); - } else { - storage.ensureIndexes(collection, ['created_at', 'eventType']); - } - }); -} -storage.ensureIndexes = ensureIndexes; module.exports = storage; diff --git a/server.js b/server.js index 4bff559d76b..1924ae4624c 100644 --- a/server.js +++ b/server.js @@ -27,7 +27,7 @@ /////////////////////////////////////////////////// var env = require('./env')( ); -var store = require('./lib/storage')(env) + /////////////////////////////////////////////////// // setup http server @@ -43,19 +43,10 @@ function create (app) { return transport.createServer(app); } -var bootevent = require('bootevent'); -bootevent( ) - .acquire(function db (ctx, next) { - // initialize db connections - store( function ready ( ) { - console.log('storage system ready'); - ctx.store = store; - next( ); - }); - }) - .boot(function booted (ctx) { +var bootevent = require('./lib/bootevent'); +bootevent(env).boot(function booted (ctx) { env.store = ctx.store; - var app = require('./app')(env); + var app = require('./app')(env, ctx); var server = create(app).listen(PORT); console.log('listening', PORT); /////////////////////////////////////////////////// From c2a1141adb4ddb503c5680b4764206cb6c2fa652 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 17 Mar 2015 23:45:38 -0700 Subject: [PATCH 183/714] much smoother opening/closing of the settings and treatment drawers --- static/js/ui-utils.js | 101 +++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 4a5e9fcf8e3..c85666b26ae 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -1,7 +1,7 @@ 'use strict'; -var drawerIsOpen = false; -var treatmentDrawerIsOpen = false; +var openDraw = null; + var defaultSettings = { 'units': 'mg/dl', 'alarmUrgentHigh': true, @@ -150,38 +150,53 @@ function isTouch() { catch (e) { return false; } } +function toggleDrawer(id, openCallback, closeCallback) { -function closeDrawer(callback) { - $('#container').animate({marginLeft: '0px'}, 300, callback); - $('#chartContainer').animate({marginLeft: '0px'}, 300); - $('#drawer').animate({right: '-300px'}, 300, function() { - $('#drawer').css('display', 'none'); - }); - drawerIsOpen = false; -} + function openDrawer(id, callback) { + function closeOpenDraw(callback) { + if (openDraw) { + fastClose(openDraw, callback); + } else { + callback(true) + } + } -function openDrawer() { - drawerIsOpen = true; - $('#container').animate({marginLeft: '-300px'}, 300); - $('#chartContainer').animate({marginLeft: '-300px'}, 300); - $('#drawer').css('display', 'block').animate({right: '0'}, 300); -} + closeOpenDraw(function (container) { + openDraw = id; + if (container) { + $('#container').animate({marginLeft: '-300px'}, 300); + $('#chartContainer').animate({marginLeft: '-300px'}, 300); + } + $(id).css('display', 'block').animate({right: '0'}, 300, function () { + if (callback) callback(); + }); + }); -function closeTreatmentDrawer(callback) { - $('#container').animate({marginLeft: '0px'}, 400, callback); - $('#chartContainer').animate({marginLeft: '0px'}, 400); - $('#treatmentDrawer').animate({right: '-300px'}, 400, function() { - $('#treatmentDrawer').css('display', 'none'); - }); - treatmentDrawerIsOpen = false; -} + } -function openTreatmentDrawer() { - treatmentDrawerIsOpen = true; - $('#container').animate({marginLeft: '-300px'}, 400); - $('#chartContainer').animate({marginLeft: '-300px'}, 400); - $('#treatmentDrawer').css('display', 'block').animate({right: '0'}, 400); + function fastClose(id, callback) { + $(id).animate({right: '-300px'}, 300, function () { + $(id).css('display', 'none'); + if (callback) callback(); + }); + } + function closeDrawer(id, callback) { + openDraw = null; + $('#container').animate({marginLeft: '0px'}, 300, callback); + $('#chartContainer').animate({marginLeft: '0px'}, 300); + fastClose(id, callback); + } + + if (openDraw == id) { + closeDrawer(id, closeCallback); + } else { + openDrawer(id, openCallback); + } + +} + +function initTreatmentDrawer() { $('#eventType').val('BG Check').focus(); $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); $('#meter').prop('checked', true); @@ -322,36 +337,12 @@ Dropdown.prototype.open = function (e) { $('#drawerToggle').click(function(event) { - //close other drawers - if(treatmentDrawerIsOpen) { - closeTreatmentDrawer(); - treatmentDrawerIsOpen = false; - } - - if(drawerIsOpen) { - closeDrawer(); - drawerIsOpen = false; - } else { - openDrawer(); - drawerIsOpen = true; - } + toggleDrawer('#drawer'); event.preventDefault(); }); $('#treatmentDrawerToggle').click(function(event) { - //close other drawers - if(drawerIsOpen) { - closeDrawer(); - drawerIsOpen = false; - } - - if(treatmentDrawerIsOpen) { - closeTreatmentDrawer(); - treatmentDrawerIsOpen = false; - } else { - openTreatmentDrawer(); - treatmentDrawerIsOpen = true; - } + toggleDrawer('#treatmentDrawer', initTreatmentDrawer); event.preventDefault(); }); From fd47ec9cd15fef2875d8790c57a8e1121cbe0f08 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 18 Mar 2015 00:04:08 -0700 Subject: [PATCH 184/714] stop moving the margin-left of the container when a drawer is opened to prevent jumpyness --- static/js/ui-utils.js | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index c85666b26ae..0b1fb243f05 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -155,18 +155,14 @@ function toggleDrawer(id, openCallback, closeCallback) { function openDrawer(id, callback) { function closeOpenDraw(callback) { if (openDraw) { - fastClose(openDraw, callback); + closeDrawer(openDraw, callback); } else { - callback(true) + callback() } } - closeOpenDraw(function (container) { + closeOpenDraw(function () { openDraw = id; - if (container) { - $('#container').animate({marginLeft: '-300px'}, 300); - $('#chartContainer').animate({marginLeft: '-300px'}, 300); - } $(id).css('display', 'block').animate({right: '0'}, 300, function () { if (callback) callback(); }); @@ -174,20 +170,14 @@ function toggleDrawer(id, openCallback, closeCallback) { } - function fastClose(id, callback) { + function closeDrawer(id, callback) { + openDraw = null; $(id).animate({right: '-300px'}, 300, function () { $(id).css('display', 'none'); if (callback) callback(); }); } - function closeDrawer(id, callback) { - openDraw = null; - $('#container').animate({marginLeft: '0px'}, 300, callback); - $('#chartContainer').animate({marginLeft: '0px'}, 300); - fastClose(id, callback); - } - if (openDraw == id) { closeDrawer(id, closeCallback); } else { @@ -400,13 +390,13 @@ $(function() { fade: true, gravity: 'n', opacity: 0.75 - } + }; if (querystring.notify) { showNotification(querystring.notify, querystring.notifytype); } if (querystring.drawer) { - openDrawer(); + openDrawer('#drawer'); } }); From 0cdd3c0835e55cd3009d68e7ed1334c07343f84f Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 18 Mar 2015 01:42:33 -0700 Subject: [PATCH 185/714] rm spurious tmp file --- lib/pebble.js~ | 187 ------------------------------------------------- 1 file changed, 187 deletions(-) delete mode 100644 lib/pebble.js~ diff --git a/lib/pebble.js~ b/lib/pebble.js~ deleted file mode 100644 index cf328b416d7..00000000000 --- a/lib/pebble.js~ +++ /dev/null @@ -1,187 +0,0 @@ -'use strict'; - -var DIRECTIONS = { - NONE: 0 -, DoubleUp: 1 -, SingleUp: 2 -, FortyFiveUp: 3 -, Flat: 4 -, FortyFiveDown: 5 -, SingleDown: 6 -, DoubleDown: 7 -, 'NOT COMPUTABLE': 8 -, 'RATE OUT OF RANGE': 9 -}; - -var iob = require("./iob")(); -var async = require('async'); - -function directionToTrend (direction) { - var trend = 8; - if (direction in DIRECTIONS) { - trend = DIRECTIONS[direction]; - } - return trend; -} - -function pebble (req, res) { - var ONE_DAY = 24 * 60 * 60 * 1000 - , uploaderBattery - , treatmentResults - , profileResult - , sgvData = [ ] - , calData = [ ]; - - function scaleBg(bg) { - if (req.mmol) { - return (Math.round((bg / 18) * 10) / 10).toFixed(1); - } else { - return bg; - } - } - - function sendData () { - var now = Date.now(); - - //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; - if (req.iob) { - sgvData[0].iob = iob.calcTotal(treatmentResults.slice(0, 20), profileResult, new Date(now)).display; - } - } - - var result = { status: [ {now: now} ], bgs: sgvData.slice(0, req.count), cals: calData }; - res.setHeader('content-type', 'application/json'); - res.write(JSON.stringify(result)); - res.end( ); - } - - var earliest_data = Date.now() - ONE_DAY; - - async.parallel({ - devicestatus: function (callback) { - req.devicestatus.last(function (err, value) { - if (!err && value) { - uploaderBattery = value.uploaderBattery; - } else { - console.error("req.devicestatus.tail", err); - } - callback(); - }); - } - , treatments: function(callback) { - loadTreatments(req, earliest_data, function (err, trs) { - treatmentResults = trs; - callback(); - }); - } - , profile: function(callback) { - loadProfile(req, function (err, profileResults) { - if (!err && profileResults) { - profileResults.forEach(function (profile) { - if (profile) { - if (profile.dia) { - profileResult = profile; - } - } - }); - } else { - console.error("pebble profile error", arguments); - } - callback(); - }); - } - , cal: function(callback) { - if (req.rawbg) { - var cq = { count: req.count, find: {type: 'cal'} }; - req.entries.list(cq, function (err, results) { - results.forEach(function (element) { - if (element) { - calData.push({ - slope: Math.round(element.slope) - , intercept: Math.round(element.intercept) - , scale: Math.round(element.scale) - }); - } - }); - callback(); - }); - } else { - callback(); - } - } - , entries: function(callback) { - var q = { count: req.count + 1, find: { "sgv": { $exists: true }} }; - - req.entries.list(q, function(err, results) { - results.forEach(function(element, index) { - if (element) { - var obj = {}; - var next = null; - var sgvs = results.filter(function(d) { - return !!d.sgv; - }); - if (index + 1 < sgvs.length) { - next = sgvs[index + 1]; - } - obj.sgv = scaleBg(element.sgv).toString(); - obj.bgdelta = (next ? (scaleBg(element.sgv) - scaleBg(next.sgv) ) : 0); - if (req.mmol) { - obj.bgdelta = obj.bgdelta.toFixed(1); - } - if ('direction' in element) { - obj.trend = directionToTrend(element.direction); - obj.direction = element.direction; - } - obj.datetime = element.date; - if (req.rawbg) { - obj.filtered = element.filtered; - obj.unfiltered = element.unfiltered; - obj.noise = element.noise; - } - sgvData.push(obj); - } - }); - callback(); - }); - } - }, sendData); - -} - -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); - } else { - fn(null, []); - } -} - -function loadProfile(req, fn) { - if (req.iob) { - req.profile.list(fn); - } else { - fn(null, []); - } -} - -function configure (entries, treatments, profile, devicestatus, env) { - function middle (req, res, next) { - req.entries = entries; - req.treatments = treatments; - req.profile = profile; - req.devicestatus = devicestatus; - 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'; - req.count = parseInt(req.query.count) || 1; - - next( ); - } - return [middle, pebble]; -} - -configure.pebble = pebble; -module.exports = configure; From 7578c1af5a9cceba4ac6ed0f5998b397094ce54e Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 18 Mar 2015 01:52:23 -0700 Subject: [PATCH 186/714] make mqtt listener more unique --- lib/models/downloads.js | 0 lib/models/entries.js | 42 ----------------------------------------- lib/mqtt.js | 2 +- 3 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 lib/models/downloads.js delete mode 100644 lib/models/entries.js diff --git a/lib/models/downloads.js b/lib/models/downloads.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/models/entries.js b/lib/models/entries.js deleted file mode 100644 index 8bea7ae6b25..00000000000 --- a/lib/models/entries.js +++ /dev/null @@ -1,42 +0,0 @@ - -var _ = require('lodash'); - -var config = { - keys: ['sgv' ] - , fields: ['sgv', 'timestamp'] - // , -}; - -function sgvs ( ) { - -} - - -function make_timestamp ( ) { -} - -function make_field ( ) { -} - -function sanitizer (opts) { - -} - -function index (opts) { - function calc (data) { - var o = lo.pick(data, opts.keys); - var scheme, r; - var stack = [ ]; - if (o.type) { - scheme = o.type + ':/'; - } - opts.fields.forEach(function iter (elem) { - stack.push(data[elem]); - }); - r = [ scheme ].concat(stack).join('/'); - return r; - } - return calc; -} - -module.exports = index; diff --git a/lib/mqtt.js b/lib/mqtt.js index 8f2a9fc6f57..b67095fe66c 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -101,7 +101,7 @@ function configure(env, core, devicestatus) { var opts = { encoding: 'binary', clean: false, - clientId: 'master' + clientId: env.head }; var client = mqtt.connect(uri, opts); var downloads = downloader(); From d185010538138d2394894b66ff3e13d31b1415f8 Mon Sep 17 00:00:00 2001 From: Ben West Date: Wed, 18 Mar 2015 01:53:21 -0700 Subject: [PATCH 187/714] remove spurious, unused code --- lib/entries.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/entries.js b/lib/entries.js index 58d949cb7f6..5854905be44 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -167,11 +167,6 @@ function storage(name, storage, pushover) { }); } - function writeStream (opts) { - function map (item, next) { - } - } - // closure to represent the API function api ( ) { // obtain handle usable for querying the collection associated From 186fdff6ca8d2c4758a890a27df8b1100fa56e39 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 18 Mar 2015 07:47:48 -0700 Subject: [PATCH 188/714] fixed bug that caused the treatment to be added 2 times --- static/js/ui-utils.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 0b1fb243f05..46f220dfce9 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -150,6 +150,14 @@ function isTouch() { catch (e) { return false; } } +function closeDrawer(id, callback) { + openDraw = null; + $(id).animate({right: '-300px'}, 300, function () { + $(id).css('display', 'none'); + if (callback) callback(); + }); +} + function toggleDrawer(id, openCallback, closeCallback) { function openDrawer(id, callback) { @@ -170,14 +178,6 @@ function toggleDrawer(id, openCallback, closeCallback) { } - function closeDrawer(id, callback) { - openDraw = null; - $(id).animate({right: '-300px'}, 300, function () { - $(id).css('display', 'none'); - if (callback) callback(); - }); - } - if (openDraw == id) { closeDrawer(id, closeCallback); } else { @@ -294,7 +294,7 @@ function treatmentSubmit(event) { browserStorage.set('enteredBy', data.enteredBy); - closeTreatmentDrawer(); + closeDrawer('#treatmentDrawer'); } if (event) { From 2575b6a4a9b05caacc9ae9fa97077151abcdd85e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 18 Mar 2015 20:02:30 +0100 Subject: [PATCH 189/714] New profile api --- lib/api/profile/index.js | 49 +++++++++++++++++++++++++++++++++++++++- lib/profile.js | 45 +++++++++++++++++++++++++++++++----- server.js | 10 ++++---- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js index 5dae6a971ee..8892d062dae 100644 --- a/lib/api/profile/index.js +++ b/lib/api/profile/index.js @@ -15,13 +15,60 @@ function configure (app, wares, profile) { // also support url-encoded content-type api.use(wares.bodyParser.urlencoded({ extended: true })); - // List settings available + // List profiles available api.get('/profile/', function(req, res) { 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) { + return res.json(records.length > 0 ? records[0] : null); + }); + return; + }); + + function config_authed (app, api, wares, profile) { + + // create new record + api.post('/profile/', wares.verifyAuthorization, function(req, res) { + var data = req.body; + profile.create(data, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + console.log('Error creating profile'); + console.log(err); + } else { + res.json(created); + console.log('Profile created'); + } + + }); + }); + + // update record + api.put('/profile/', wares.verifyAuthorization, function(req, res) { + var data = req.body; + profile.save(data, function (err, created) { + if (err) { + res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err); + console.log('Error saving profile'); + console.log(err); + } else { + res.json(created); + console.log('Profile saved'); + } + + }); + }); + } + + if (app.enabled('api')) { + config_authed(app, api, wares, profile); + } + return api; } diff --git a/lib/profile.js b/lib/profile.js index c7ba0e101f9..b758a9afc66 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -1,24 +1,57 @@ 'use strict'; -function configure (collection, storage) { +var ObjectID = require('mongodb').ObjectID; +var lastrecord = null; + +function storage (collection, storage) { + function create (obj, fn) { obj.created_at = (new Date( )).toISOString( ); - api( ).insert(obj, function (err, doc) { + api().insert(obj, function (err, doc) { fn(null, doc); }); } + function save (obj, fn) { + obj._id = new ObjectID(obj._id); + obj.created_at = (new Date( )).toISOString( ); + api().save(obj, function (err, doc) { + fn(err, doc); + }); + } + function list (fn) { - return api( ).find({ }).sort({created_at: -1}).toArray(fn); + return api().find({ }).sort({validfrom: -1}).toArray(fn); } - function api ( ) { - return storage.pool.db.collection(collection); + function last (fn) { + return api().find().sort({validfrom: -1}).limit(1).toArray(fn);; } + function api () { + return storage.pool.db.collection(collection); + } + api.list = list; api.create = create; + api.save = save; + api.last = last; return api; } -module.exports = configure; + +function ensureIndexes(name, storage) { + storage.with_collection(name)(function (err, collection) { + if (err) { + console.error("ensureIndexes, unable to get collection for: " + name + " - " + err); + } else { + storage.ensureIndexes(collection, ['validfrom']); + } + }); +} + +module.exports = { + storage: storage + , ensureIndexes: ensureIndexes +}; + diff --git a/server.js b/server.js index 10eb3602847..96001192b1f 100644 --- a/server.js +++ b/server.js @@ -33,12 +33,14 @@ var pushover = require('./lib/pushover')(env); var entries = require('./lib/entries'); var treatments = require('./lib/treatments'); var devicestatus = require('./lib/devicestatus'); +var profiles = require('./lib/profile'); var store = require('./lib/storage')(env, function() { console.info("Mongo ready"); entries.ensureIndexes(env.mongo_collection, store); treatments.ensureIndexes(env.treatments_collection, store); devicestatus.ensureIndexes(env.devicestatus_collection, store); + profiles.ensureIndexes(env.profile_collection, store); }); @@ -51,9 +53,9 @@ var compression = require('compression'); var entriesStorage = entries.storage(env.mongo_collection, store, pushover); var settings = require('./lib/settings')(env.settings_collection, store); var treatmentsStorage = treatments.storage(env.treatments_collection, store, pushover); -var profile = require('./lib/profile')(env.profile_collection, store); +var profilesStorage = profiles.storage(env.profile_collection, store); var devicestatusStorage = devicestatus.storage(env.devicestatus_collection, store); -var api = require('./lib/api/')(env, entriesStorage, settings, treatmentsStorage, profile, devicestatusStorage); +var api = require('./lib/api/')(env, entriesStorage, settings, treatmentsStorage, profilesStorage, devicestatusStorage); var pebble = require('./lib/pebble'); /////////////////////////////////////////////////// @@ -78,7 +80,7 @@ function shouldCompress(req, res) { app.use('/api/v1', api); // pebble data -app.get('/pebble', pebble(entriesStorage, treatmentsStorage, profile, devicestatusStorage, env)); +app.get('/pebble', pebble(entriesStorage, treatmentsStorage, profilesStorage, devicestatusStorage, env)); //app.get('/package.json', software); @@ -115,7 +117,7 @@ store(function ready ( ) { // setup socket io for data and message transmission /////////////////////////////////////////////////// var websocket = require('./lib/websocket'); - var io = websocket(env, server, entriesStorage, treatmentsStorage, profile, devicestatusStorage); + var io = websocket(env, server, entriesStorage, treatmentsStorage, profilesStorage, devicestatusStorage); }); /////////////////////////////////////////////////// From c2d7daad9b0c0eb388b0ca27bc08d879aba50146 Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 18 Mar 2015 20:07:22 +0100 Subject: [PATCH 190/714] Removed unneeded var --- lib/profile.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/profile.js b/lib/profile.js index b758a9afc66..8144256ef46 100644 --- a/lib/profile.js +++ b/lib/profile.js @@ -2,8 +2,6 @@ var ObjectID = require('mongodb').ObjectID; -var lastrecord = null; - function storage (collection, storage) { function create (obj, fn) { From 0af24b6851e5681e6277a9d30f9bd7aee36fd71d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Wed, 18 Mar 2015 22:20:31 -0700 Subject: [PATCH 191/714] env got dropped off durring refactoring and missed in testing --- app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.js b/app.js index 88852d3b718..272735bcb77 100644 --- a/app.js +++ b/app.js @@ -32,7 +32,7 @@ function create (env, ctx) { // pebble data - app.get('/pebble', pebble(ctx.entries, ctx.treatments, ctx.profiles, ctx.devicestatus)); + app.get('/pebble', pebble(ctx.entries, ctx.treatments, ctx.profiles, ctx.devicestatus, env)); //app.get('/package.json', software); From 95746ab5be397d1e4cf0b70f479d460fe6566169 Mon Sep 17 00:00:00 2001 From: Ben West Date: Thu, 19 Mar 2015 13:22:27 -0700 Subject: [PATCH 192/714] allow API to search things better This allows a query such as this to search for events of hypoglycemia for example: curl -g localhost:3434 \ /api/v1/entries \ '?find[sgv][$lte]=70&find[sgv][$gte]=20&count=1000 It's possible to construct most mongo queries by url encoding the query string. In this instance, mongo performs poorly when searching for lte/gte for strings. In order for ranged queries to perform properly, the query parameters must be set to integer type. There is a quick and ugly helper to ensure that some sgv queries will be respected as integer searches. --- lib/api/treatments/index.js | 2 +- lib/entries.js | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/api/treatments/index.js b/lib/api/treatments/index.js index 1a7c0bc4d40..259b7f00fe1 100644 --- a/lib/api/treatments/index.js +++ b/lib/api/treatments/index.js @@ -17,7 +17,7 @@ function configure (app, wares, treatments) { // List settings available api.get('/treatments/', function(req, res) { - treatments.list({}, function (err, profiles) { + treatments.list({find: req.params}, function (err, profiles) { return res.json(profiles); }); }); diff --git a/lib/entries.js b/lib/entries.js index 5854905be44..abd01989703 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -10,6 +10,18 @@ var TEN_MINS = 10 * 60 * 1000; * Encapsulate persistent storage of sgv entries. \**********/ +function find_sgv_query (opts) { + if (opts && opts.find && opts.find.sgv) { + Object.keys(opts.find.sgv).forEach(function (key) { + var is_keyword = /^\$/g; + if (is_keyword.test(key)) { + opts.find.sgv[key] = parseInt(opts.find.sgv[key]); + } + }); + } + return opts; +} + function storage(name, storage, pushover) { // TODO: Code is a little redundant. @@ -25,7 +37,8 @@ function storage(name, storage, pushover) { // determine find options function find ( ) { - var q = opts && opts.find ? opts.find : { }; + var finder = find_sgv_query(opts); + var q = finder && finder.find ? finder.find : { }; return q; // return this.find(q); } From ed264b17e1a4a25a18bb21496f2f24274610d2bf Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Thu, 19 Mar 2015 23:17:26 -0700 Subject: [PATCH 193/714] initial display of noise level when rawbg is enabled --- static/css/main.css | 15 +++++++++++++++ static/js/client.js | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 9e7e36a6590..2146d168d06 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -227,6 +227,21 @@ body { -webkit-box-shadow: none; } +.bgButton.noise-heavy, .bgButton.noise-error { + border-color: rgba(255, 0, 0, 0.39); + box-shadow: 2px 2px 10px rgba(255, 0, 0, 0.75); +} + +.bgButton.noise-medium { + border-color: rgba(255, 255, 0, 0.39); + box-shadow: 2px 2px 10px rgba(255, 255, 0, 0.75); +} + +.bgButton.noise-light { + border-color: rgba(189, 189, 189, 0.39); + box-shadow: 2px 2px 10px rgba(189, 189, 189, 0.75); +} + .button { text-align: center; background: #ababab; diff --git a/static/js/client.js b/static/js/client.js index 96a39931b25..ce60307e27d 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -341,6 +341,28 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; currentBG.toggleClass('bg-limit', value == 39 || value > 400); } + function updateCurrentNoise(entry) { + var noise = entry.noise + , noiseType; + + if ((noise <= 1 && entry.y >= 40) || !showRawBGs()) { + noiseType = null; + } else if (entry.y < 40) { + noiseType = 'error'; + } else if (noise == 2) { + noiseType = 'light'; + } else if (noise == 3) { + noiseType = 'medium'; + } else if (noise >= 4) { + noiseType = 'heavy'; + } + + bgButton.removeClass('noise-light noise-medium noise-heavy noise-error'); + if (noiseType) { + bgButton.addClass('noise-' + noiseType); + } + } + function updateBGDelta(prev, current) { var pill = currentDetails.find('span.pill.bgdelta'); @@ -416,6 +438,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; var prevfocusPoint = nowData[nowData.length - 2]; updateCurrentSGV(focusPoint.y); + updateCurrentNoise(focusPoint); updateBGDelta(prevfocusPoint.y, focusPoint.y); @@ -440,6 +463,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; nowDate = new Date(now); updateCurrentSGV(latestSGV.y); + updateCurrentNoise(latestSGV); updateClockDisplay(); updateTimeAgo(); From 1431be6ec950ea66e7df075c1cfe61b014e7e381 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 20 Mar 2015 00:06:26 -0700 Subject: [PATCH 194/714] display rawbg in top right if enabled; fix alarm button border --- static/css/main.css | 51 ++++++++++++++++++++++++++++----------------- static/index.html | 1 + static/js/client.js | 35 ++++++++++++++++++++----------- 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 2146d168d06..01b7186dde9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -206,18 +206,41 @@ body { width: 320px; } +.bgButton.noise-heavy, .bgButton.noise-error { + border-color: rgba(255, 0, 0, 0.39); + box-shadow: 2px 2px 10px rgba(255, 0, 0, 0.75); +} + +.bgButton.noise-medium { + border-color: rgba(255, 255, 0, 0.39); + box-shadow: 2px 2px 10px rgba(255, 255, 0, 0.75); +} + +.bgButton.noise-light { + border-color: rgba(189, 189, 189, 0.39); + box-shadow: 2px 2px 10px rgba(189, 189, 189, 0.75); +} + +.bgButton .rawbg { + color: white; + position: absolute; + top: 60px; + right: 20px; + text-shadow: 4px 4px 7px rgba(0, 0, 0, 0.84); +} + .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 .bgButton.urgent { - background-color: red; + background-color: red; } .alarming .bgButton.warning { - background-color: yellow; + background-color: yellow; } .alarming .bgButton:active { @@ -227,20 +250,6 @@ body { -webkit-box-shadow: none; } -.bgButton.noise-heavy, .bgButton.noise-error { - border-color: rgba(255, 0, 0, 0.39); - box-shadow: 2px 2px 10px rgba(255, 0, 0, 0.75); -} - -.bgButton.noise-medium { - border-color: rgba(255, 255, 0, 0.39); - box-shadow: 2px 2px 10px rgba(255, 255, 0, 0.75); -} - -.bgButton.noise-light { - border-color: rgba(189, 189, 189, 0.39); - box-shadow: 2px 2px 10px rgba(189, 189, 189, 0.75); -} .button { text-align: center; @@ -513,6 +522,10 @@ div.tooltip { display: none; } + .bgButton .rawbg { + top: 40px; + } + .container { top: 15px; padding-bottom: 20px; diff --git a/static/index.html b/static/index.html index 0ff53abe7ba..765d6c2232f 100644 --- a/static/index.html +++ b/static/index.html @@ -30,6 +30,7 @@

Nightscout

--- - +

- + +
Experiments @@ -211,7 +212,7 @@

Nightscout

- +
View all treatments diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 28c44b3788d..8088d3bfd3e 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -327,7 +327,7 @@ $('#notification').click(function(event) { event.preventDefault(); }); -$('input#save').click(function(event) { +$('button#save').click(function(event) { storeInBrowser({ 'units': $('input:radio[name=units-browser]:checked').val(), 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), @@ -342,14 +342,24 @@ $('input#save').click(function(event) { }); event.preventDefault(); + reload(); +}); + + +$('button#useDefaults').click(function(event) { + console.info('>>>>browserStorage', browserStorage); + browserStorage.removeAll(); + 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; -}); - +} $(function() { // Tooltips can remain in the way on touch screens. From 6cd8638db8aafb005c3bead7c46f4f65cac06963 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 3 Apr 2015 21:37:29 -0700 Subject: [PATCH 243/714] changed reset button into a link and some other css tweeks; only remove known settings from localstorage --- static/css/drawer.css | 58 +++++++++++++++++++------------------------ static/index.html | 6 +++-- static/js/ui-utils.js | 11 +++++--- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index 606181e0e47..cbb5b2b3537 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -1,3 +1,13 @@ +#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; +} + #drawer { background-color: #666; border-left: 1px solid #999; @@ -14,10 +24,23 @@ top: 0; z-index: 1; } -#drawer i { + +#drawer i, #treatmentDrawer i { opacity: 0.6; } +#drawer a, #treatmentDrawer a { + color: white; +} + +#drawer .actions { + margin: 10px 2px; +} + +#drawer .actions a { + padding-left: 20px; +} + #treatmentDrawer { background-color: #666; border-left: 1px solid #999; @@ -34,13 +57,6 @@ top: 0; z-index: 1; } -#treatmentDrawer i { - opacity: 0.6; -} - -#treatmentDrawer a { - color: white; -} #treatmentDrawer input { box-sizing: border-box; @@ -66,7 +82,7 @@ } #eventTime { - padding-bottom: 5px; + padding-bottom: 15px; } #eventTime > span { @@ -258,27 +274,3 @@ h1, legend, color: #fff; font-size: 1.5em; } - -button { - background-color: #bbb; - font-size: 16px; - padding: 5px; - border-radius: 5px; - border: 2px solid #aaa; - margin: 15px 0; -} - -button.default { - font-weight: bolder; - background-color: #ccc; - box-shadow: 2px 2px 0 #eee; -} - -.pull-right { - float: right; -} - -#drawer button { - margin: 15px; -} - diff --git a/static/index.html b/static/index.html index 3c7b3341ffa..048119e7463 100644 --- a/static/index.html +++ b/static/index.html @@ -104,8 +104,10 @@

Nightscout

- - +
Experiments diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 8088d3bfd3e..af60cad3ec6 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -327,7 +327,7 @@ $('#notification').click(function(event) { event.preventDefault(); }); -$('button#save').click(function(event) { +$('#save').click(function(event) { storeInBrowser({ 'units': $('input:radio[name=units-browser]:checked').val(), 'alarmUrgentHigh': $('#alarm-urgenthigh-browser').prop('checked'), @@ -346,9 +346,12 @@ $('button#save').click(function(event) { }); -$('button#useDefaults').click(function(event) { - console.info('>>>>browserStorage', browserStorage); - browserStorage.removeAll(); +$('#useDefaults').click(function(event) { + //remove all known settings, since there might be something else is in localstorage + var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat']; + settings.forEach(function(setting) { + browserStorage.remove(setting); + }); event.preventDefault(); reload(); }); From b89efd66b28429156e04b64bcfadb6d695f123ec Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 3 Apr 2015 22:52:09 -0700 Subject: [PATCH 244/714] added some basic validation to prevent bad treatment data (from bad browsers) and make invalid numbers red --- static/css/drawer.css | 4 ++ static/index.html | 6 +-- static/js/ui-utils.js | 97 +++++++++++++++++++++++++------------------ 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/static/css/drawer.css b/static/css/drawer.css index cbb5b2b3537..01e6905c6a5 100644 --- a/static/css/drawer.css +++ b/static/css/drawer.css @@ -33,6 +33,10 @@ color: white; } +input[type=number]:invalid { + background-color: #FFCCCC; +} + #drawer .actions { margin: 10px 2px; } diff --git a/static/index.html b/static/index.html index 048119e7463..4f77b714dd5 100644 --- a/static/index.html +++ b/static/index.html @@ -105,7 +105,7 @@

Nightscout

- + Reset, and use defaults
@@ -139,7 +139,7 @@

Nightscout

-
+
Log a Treatment
- + View all treatments diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index af60cad3ec6..b8fab6f2c0b 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -224,51 +224,68 @@ function showLocalstorageError() { function treatmentSubmit(event) { var data = {}; - data.enteredBy = document.getElementById('enteredBy').value; - data.eventType = document.getElementById('eventType').value; - data.glucose = document.getElementById('glucoseValue').value; + data.enteredBy = $('#enteredBy').val(); + data.eventType = $('#eventType').val(); + data.glucose = $('#glucoseValue').val(); data.glucoseType = $('#treatment-form input[name=glucoseType]:checked').val(); - data.carbs = document.getElementById('carbsGiven').value; - data.insulin = document.getElementById('insulinGiven').value; - data.preBolus = document.getElementById('preBolus').value; - data.notes = document.getElementById('notes').value; + data.carbs = $('#carbsGiven').val(); + data.insulin = $('#insulinGiven').val(); + data.preBolus = $('#preBolus').val(); + data.notes = $('#notes').val(); data.units = browserSettings.units; - var eventTimeDisplay = ''; - if ($('#treatment-form input[name=nowOrOther]:checked').val() != 'now') { - var value = document.getElementById('eventTimeValue').value; - 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 errors = []; + if (isNaN(data.glucose)) { + errors.push('Blood glucose must be a number'); } - 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 (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) { From 53f5f63f408427b8a9855a4e23aedbcf39b800fe Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 3 Apr 2015 23:10:53 -0700 Subject: [PATCH 245/714] don't set focus to eventType since it causes iOS to open the selection wheel --- static/js/ui-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index b8fab6f2c0b..3736686a25a 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -161,7 +161,7 @@ function toggleDrawer(id, openCallback, closeCallback) { } function initTreatmentDrawer() { - $('#eventType').val('BG Check').focus(); + $('#eventType').val('BG Check'); $('#glucoseValue').val('').attr('placeholder', 'Value in ' + browserSettings.units); $('#meter').prop('checked', true); $('#carbsGiven').val(''); From d79835e1edd9ba890a49a5f24512c5b15a8200b5 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 00:26:11 -0700 Subject: [PATCH 246/714] first pass merging sgv and sensor records --- lib/mqtt.js | 86 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index 71e68e2132b..e917afb1df1 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -78,9 +78,81 @@ function toTimestamp (proto, receiver_time, download_time) { return obj; } +function sgvSensorMerge(packet) { + var receiver_time = packet.receiver_system_time_sec; + var download_time = moment(packet.download_timestamp); + + function timestamp(item) { + return toTimestamp(item, receiver_time, download_time.clone( )); + } + + function timeSort(a, b) { + return a.date - b.date; + } + + var sgvs = packet['sgv'].map(function(sgv) { + var timestamped = timestamp(sgv); + return toSGV(sgv, timestamped); + }).sort(timeSort); + + var sensors = packet['sensor'].map(function(sensor) { + var timestamped = timestamp(sensor); + return toSensor(sensor, timestamped); + }).sort(timeSort); + + //based on com.nightscout.core.dexcom.Utils#mergeGlucoseDataRecords + var merged = [] + , sgvsLength = sgvs.length + , sensorsLength = sensors.length; + + if (sgvsLength >= 0 && sensorsLength == 0) { + merged.concat(sgvs); + } else { + var smallerLength = sgvsLength < sensorsLength ? sgvsLength : sensorsLength; + 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) { + //timestamps are close so merge + sgv.filtered = sensor.filtered; + sgv.unfiltered = sensor.unfiltered; + sgv.rssi = sensor.rssi; + merged.push(sgv); + } else { + //timestamps aren't close enough so add both + merged.push(sgv); + //but the sensor will become and sgv now + sensor.type = 'sgv'; + merged.push(sensor); + } + } + + //any extra sgvs? + if (sgvsLength > smallerLength) { + for (var j = sgvsLength - smallerLength; j < sgvsLength; j++) { + var extraSGV = sgvs[j]; + merged.push(extraSGV); + } + } + + //any extra sensors? + if (sensorsLength > smallerLength) { + for (var k = sensorsLength - smallerLength; k < sensorsLength; k++) { + var extraSensor = sensors[k]; + //from now on we consider it a sgv + extraSensor.type = 'sgv'; + merged.push(extraSensor); + } + } + + } + + return merged; +} + function iter_mqtt_record_stream (packet, prop, sync) { var list = packet[prop]; - console.log('incoming', prop, (list || [ ]).length); + console.log('incoming', prop, (list || [ ]).length); var stream = es.readArray(list || [ ]); var receiver_time = packet.receiver_system_time_sec; var download_time = moment(packet.download_timestamp); @@ -140,10 +212,10 @@ function configure(env, core, devicestatus) { console.log("WRITE TO MONGO"); var download_timestamp = moment(packet.download_timestamp); if (packet.download_status === 0) { - iter_mqtt_record_stream(packet, 'sgv', toSGV) - .pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING SGV TO MONGO", err, result.length); - })); + es.readArray(sgvSensorMerge(packet)).pipe(core.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) { console.log("DONE WRITING Cal TO MONGO", err, result.length); @@ -152,10 +224,6 @@ function configure(env, core, devicestatus) { .pipe(core.persist(function empty(err, result) { console.log("DONE WRITING Meter TO MONGO", err, result.length); })); - iter_mqtt_record_stream(packet, 'sensor', toSensor) - .pipe(core.persist(function empty(err, result) { - console.log("DONE WRITING Sensor TO MONGO", err, result.length); - })); } packet.type = "download"; devicestatus.create({ From cea0ff15e722939b88d50b6718ecaad52f6c55d8 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 00:55:40 -0700 Subject: [PATCH 247/714] egg hunt --- static/images/logo-egg.png | Bin 0 -> 3517 bytes static/js/experiments.js | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 static/images/logo-egg.png diff --git a/static/images/logo-egg.png b/static/images/logo-egg.png new file mode 100644 index 0000000000000000000000000000000000000000..805726a0f8fc0504605e14dfdc0d8fff5d77cfb0 GIT binary patch literal 3517 zcmV;u4MOsXP)4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH z9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK zVkc9?T=n|PIo~X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1 zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#mZ8eu=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7 zqW-CFs9&fT)ZaU5gc&=gBz-DaCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaER00009a7bBm000XU000XU0RWnu7ytkXKS@MER9Fe6 zSP4{=*A>41EdTto0W$*((P4>z608+e(1b-%kv7ppjW#G~wV_ZO8?9YWwf3aiw5d(n z z`a@-888C>ZY_&pj4ad!T(exLjCYqPmM=N{OK)lLSMD=R(YE(?xnOPsfsZIP2`L*Zn)N2B=MgwX@7D530ga~FMfr$=Ibrz%I{omu(;EDsaV>o>= z;}{3EVQ#sLc(r0s9nuglC~oE9jPO7}D2!ikolZAJA*d(;vUhTrhpdwnmFu;Fg;RqV z!nWr!b_BuFH!pruuj~F7PY(q2z{H5B;aOPW*8R!DT#DyY|K=WV1>u7)K7>=Z3c)Ca zf}%G;LqU2G#DoA8mVXNO+5y&OY*Cr@k9s=n4LN!#WIoEGq^ZP14c&${pVw7JDP$E& zV6mYYJc!>zE6}*_mtb4MUA1jd7oX3J+r+K(i*H`#uG5L83&_u1k95deH>AttO#o4n zqz<@B%74GO-AzFoXU*bDgLL66E-Z_s$Nqr5i@{B9njZPQ59n{7Y2|Z?wr};iP7_){ z2EaDtt>Z|EV_-wm(k54sc5Gg&{q}oFkt^NX7VlF2{d_qTo?HoeDF+~Zd^||%K08qt zC7tW{0rs5!6>9ICYVDw$Q4RGb|A*g9O1MT*EU^69xEocg)Mt=S`%~{6I$yo>^!j)etO_L&_xdk zwxozCg+(8YV zgC+#jN{PEFEw%vi()NNDsoXL2E! zI`~cw!M{SZGjl}+A6ce$*AUM(6ul;L=Z)1n!K6E%5_&7D@`>;HDDFjGieB9}1 z^sODO{3CpN@f@FzdnAijl5UT4x>t~&+lB-rD4Y{-*fhwq6Jse4F5-(eX)FjqS{t-<*G9y~KB=j!EOK~5#Am4?E#&byO$10Mnz6DX zKPn*|!5q9)0n6SOAbJ8*6O|)jOKV73=m&H6nuk%k}A$f>OekU({z^8>kXwCz?f&$7~BdiP{Co; zgmD1Xn3t$kHWY&$bF@}%!uPH>!+5FM?RE^Rd$2sj&4)EkCkb`9JNVs1OF>;TKz?d2 zR~#LZj<*TmQvD~89JLG;0>+OK>RSQUq^*WPX)MHs&ISoJRMZziYV2|nhjGJmVTU9I z(i`X321CgKfuOia+}H0P<5zQ0!dm#C{5Nm|FV^y;xiBR{gneg@!FIn z(A-(Yfnb(L9t=kWy#L&B*BeLDQ~_G+>#bdP)}t=J=flrsat1|Og|+kNciJklzPMHG znKM2@Y1RWQo%#fL*c1385zmI-f%9eX&gV~p-PHyy_9|GDz5|veXMzhIqDI@w&!-3N ze)!7E+}fBTJ|1Wp-xwA9l_0ptt3;z{eFpn1%97$LS;yE%d5pzd=d*;d*=$-uE7%*);cqqM+p&Uq@BqC9 znrPZO92i>j3u=NcH#z7;QJ1U)vDofxHKs%tIA56gXVoJ?Jmp|X(S=w262_Ga^GzUT z<9M(ddnFwF@~rFil36N;s~xKV^Nvn?V;-#x%+9htzUD>TcdlO_H}lF-BWNb7MExEt zALxyPg$aLfr$pzogm9xmg>Rj_37-|ushh{yKQ3)mUcU2BK5vlBTiUxDHxtirV7Ls~ zcf(O+Bv8aJ1xTSUA*E-Yy9-NP78V%K#sqzxg&1S7vQYWlh_Q9VD4Eh^*|>*$L>>=D zhjkeW@HN?eG+{J`g{nQOMRr%m?CzdcvKZAJ^wi+93pemb<4Fv_kDI!0AHy+Og|0jO z?}(p@=7wq`21EkL+(;M~8B+n})PBAhifd%I<2gYnC2L(x$Il7)B` r$)~34+^^v}C0sIy4|qR@VFv#NWc>9K4+q~x00000NkvXXu0mjfg>sYn literal 0 HcmV?d00001 diff --git a/static/js/experiments.js b/static/js/experiments.js index d0e7b8aa100..b2b6e6d479c 100644 --- a/static/js/experiments.js +++ b/static/js/experiments.js @@ -25,4 +25,8 @@ $(function() { $("#toolbar").css({'background-image':'url('+newIcon+')'}); event.preventDefault(); }); + + $("#toolbar").click(function() { + $("#toolbar").css({'background-image':'url(/images/logo-egg.png)'}); + }); }); From 6a3a8bdbcf8b25cb72102013a40123617252cd5b Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 00:55:40 -0700 Subject: [PATCH 248/714] egg hunt --- static/images/logo-egg.png | Bin 0 -> 3517 bytes static/js/experiments.js | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 static/images/logo-egg.png diff --git a/static/images/logo-egg.png b/static/images/logo-egg.png new file mode 100644 index 0000000000000000000000000000000000000000..805726a0f8fc0504605e14dfdc0d8fff5d77cfb0 GIT binary patch literal 3517 zcmV;u4MOsXP)4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH z9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK zVkc9?T=n|PIo~X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1 zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#mZ8eu=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7 zqW-CFs9&fT)ZaU5gc&=gBz-DaCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaER00009a7bBm000XU000XU0RWnu7ytkXKS@MER9Fe6 zSP4{=*A>41EdTto0W$*((P4>z608+e(1b-%kv7ppjW#G~wV_ZO8?9YWwf3aiw5d(n z z`a@-888C>ZY_&pj4ad!T(exLjCYqPmM=N{OK)lLSMD=R(YE(?xnOPsfsZIP2`L*Zn)N2B=MgwX@7D530ga~FMfr$=Ibrz%I{omu(;EDsaV>o>= z;}{3EVQ#sLc(r0s9nuglC~oE9jPO7}D2!ikolZAJA*d(;vUhTrhpdwnmFu;Fg;RqV z!nWr!b_BuFH!pruuj~F7PY(q2z{H5B;aOPW*8R!DT#DyY|K=WV1>u7)K7>=Z3c)Ca zf}%G;LqU2G#DoA8mVXNO+5y&OY*Cr@k9s=n4LN!#WIoEGq^ZP14c&${pVw7JDP$E& zV6mYYJc!>zE6}*_mtb4MUA1jd7oX3J+r+K(i*H`#uG5L83&_u1k95deH>AttO#o4n zqz<@B%74GO-AzFoXU*bDgLL66E-Z_s$Nqr5i@{B9njZPQ59n{7Y2|Z?wr};iP7_){ z2EaDtt>Z|EV_-wm(k54sc5Gg&{q}oFkt^NX7VlF2{d_qTo?HoeDF+~Zd^||%K08qt zC7tW{0rs5!6>9ICYVDw$Q4RGb|A*g9O1MT*EU^69xEocg)Mt=S`%~{6I$yo>^!j)etO_L&_xdk zwxozCg+(8YV zgC+#jN{PEFEw%vi()NNDsoXL2E! zI`~cw!M{SZGjl}+A6ce$*AUM(6ul;L=Z)1n!K6E%5_&7D@`>;HDDFjGieB9}1 z^sODO{3CpN@f@FzdnAijl5UT4x>t~&+lB-rD4Y{-*fhwq6Jse4F5-(eX)FjqS{t-<*G9y~KB=j!EOK~5#Am4?E#&byO$10Mnz6DX zKPn*|!5q9)0n6SOAbJ8*6O|)jOKV73=m&H6nuk%k}A$f>OekU({z^8>kXwCz?f&$7~BdiP{Co; zgmD1Xn3t$kHWY&$bF@}%!uPH>!+5FM?RE^Rd$2sj&4)EkCkb`9JNVs1OF>;TKz?d2 zR~#LZj<*TmQvD~89JLG;0>+OK>RSQUq^*WPX)MHs&ISoJRMZziYV2|nhjGJmVTU9I z(i`X321CgKfuOia+}H0P<5zQ0!dm#C{5Nm|FV^y;xiBR{gneg@!FIn z(A-(Yfnb(L9t=kWy#L&B*BeLDQ~_G+>#bdP)}t=J=flrsat1|Og|+kNciJklzPMHG znKM2@Y1RWQo%#fL*c1385zmI-f%9eX&gV~p-PHyy_9|GDz5|veXMzhIqDI@w&!-3N ze)!7E+}fBTJ|1Wp-xwA9l_0ptt3;z{eFpn1%97$LS;yE%d5pzd=d*;d*=$-uE7%*);cqqM+p&Uq@BqC9 znrPZO92i>j3u=NcH#z7;QJ1U)vDofxHKs%tIA56gXVoJ?Jmp|X(S=w262_Ga^GzUT z<9M(ddnFwF@~rFil36N;s~xKV^Nvn?V;-#x%+9htzUD>TcdlO_H}lF-BWNb7MExEt zALxyPg$aLfr$pzogm9xmg>Rj_37-|ushh{yKQ3)mUcU2BK5vlBTiUxDHxtirV7Ls~ zcf(O+Bv8aJ1xTSUA*E-Yy9-NP78V%K#sqzxg&1S7vQYWlh_Q9VD4Eh^*|>*$L>>=D zhjkeW@HN?eG+{J`g{nQOMRr%m?CzdcvKZAJ^wi+93pemb<4Fv_kDI!0AHy+Og|0jO z?}(p@=7wq`21EkL+(;M~8B+n})PBAhifd%I<2gYnC2L(x$Il7)B` r$)~34+^^v}C0sIy4|qR@VFv#NWc>9K4+q~x00000NkvXXu0mjfg>sYn literal 0 HcmV?d00001 diff --git a/static/js/experiments.js b/static/js/experiments.js index d0e7b8aa100..b2b6e6d479c 100644 --- a/static/js/experiments.js +++ b/static/js/experiments.js @@ -25,4 +25,8 @@ $(function() { $("#toolbar").css({'background-image':'url('+newIcon+')'}); event.preventDefault(); }); + + $("#toolbar").click(function() { + $("#toolbar").css({'background-image':'url(/images/logo-egg.png)'}); + }); }); From 94f75e3641c0c8c67cf3d352d295f5322c5e803c Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 01:01:46 -0700 Subject: [PATCH 249/714] harder hunting --- static/js/experiments.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/js/experiments.js b/static/js/experiments.js index b2b6e6d479c..aa8af55fc69 100644 --- a/static/js/experiments.js +++ b/static/js/experiments.js @@ -26,7 +26,11 @@ $(function() { event.preventDefault(); }); + var egghunt = 0; $("#toolbar").click(function() { - $("#toolbar").css({'background-image':'url(/images/logo-egg.png)'}); + if (egghunt > 6) { + $("#toolbar").css({'background-image': 'url(/images/logo-egg.png)'}); + } + egghunt++; }); }); From dd375becc446639f01b334f05c8dd67d9ed93f12 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 01:01:46 -0700 Subject: [PATCH 250/714] harder hunting --- static/js/experiments.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/static/js/experiments.js b/static/js/experiments.js index b2b6e6d479c..aa8af55fc69 100644 --- a/static/js/experiments.js +++ b/static/js/experiments.js @@ -26,7 +26,11 @@ $(function() { event.preventDefault(); }); + var egghunt = 0; $("#toolbar").click(function() { - $("#toolbar").css({'background-image':'url(/images/logo-egg.png)'}); + if (egghunt > 6) { + $("#toolbar").css({'background-image': 'url(/images/logo-egg.png)'}); + } + egghunt++; }); }); From 12fb927d0d19b7fe1e707f57fcb36df6eeab5c00 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 13:32:52 -0700 Subject: [PATCH 251/714] make sure we can handle without sgvs or sensors --- lib/mqtt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mqtt.js b/lib/mqtt.js index e917afb1df1..cfc4675f9f9 100644 --- a/lib/mqtt.js +++ b/lib/mqtt.js @@ -90,12 +90,12 @@ function sgvSensorMerge(packet) { return a.date - b.date; } - var sgvs = packet['sgv'].map(function(sgv) { + var sgvs = (packet['sgv'] || []).map(function(sgv) { var timestamped = timestamp(sgv); return toSGV(sgv, timestamped); }).sort(timeSort); - var sensors = packet['sensor'].map(function(sensor) { + var sensors = (packet['sensor'] || []).map(function(sensor) { var timestamped = timestamp(sensor); return toSensor(sensor, timestamped); }).sort(timeSort); From d456cbd5f8b5d2d7cc39254253cb84fbefe64dce Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sun, 5 Apr 2015 20:09:42 -0700 Subject: [PATCH 252/714] until next year... --- static/images/logo-egg.png | Bin 3517 -> 0 bytes static/js/experiments.js | 8 -------- 2 files changed, 8 deletions(-) delete mode 100644 static/images/logo-egg.png diff --git a/static/images/logo-egg.png b/static/images/logo-egg.png deleted file mode 100644 index 805726a0f8fc0504605e14dfdc0d8fff5d77cfb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3517 zcmV;u4MOsXP)4Tx05}naRo`#hR1`jmZ&IWdKOk5~hl<6oRa0BJ8yc;~21%2p?MfD<>DVeH z9(p*dx19w`~g7O0}n_%Aq@s%d)fBDv`JHkDym6Hd+5XuAtvnwRpGmK zVkc9?T=n|PIo~X-eVh__(Z?q}P9Z-Dj?gOW6|D%o20XmjW-qs4UjrD(li^iv8@eK9k+ZFm zVRFymFOPAzG5-%Pn|1W;U4vNroTa&AxDScmEA~{ri9gr1^c?U@uwSpaNnw8l_>cP1 zd;)kMQS_;jeRSUEM_*s96y65j1$)tOrwdK{YIQMt92l|D^(E_=$Rjw{b!QT@q!)ni zR`|5oW9X5n$Wv+HVc@|^eX5yXnsHX8PF3UX~a6)MwxDE0HaPjyrlI!;jX{6Kvuh*8ej?;85ekN$?5uuCiS zBTvvVG+XTxAO{m@bvM#Jr)z6J><&E22D|vq?Y?Vkbo_DijopiF$2PET#mZ8eu=y$(ArYkv7@Ex`GL?QCc!_*KFrd&;n1r7 zqW-CFs9&fT)ZaU5gc&=gBz-DaCw(vdOp0__x+47~U6sC(E(JNe@4cTT*n6*E zVH4eoU1-&7pEV~_PRe`a7v+@vy!^5}8?Y3)UmlaER00009a7bBm000XU000XU0RWnu7ytkXKS@MER9Fe6 zSP4{=*A>41EdTto0W$*((P4>z608+e(1b-%kv7ppjW#G~wV_ZO8?9YWwf3aiw5d(n z z`a@-888C>ZY_&pj4ad!T(exLjCYqPmM=N{OK)lLSMD=R(YE(?xnOPsfsZIP2`L*Zn)N2B=MgwX@7D530ga~FMfr$=Ibrz%I{omu(;EDsaV>o>= z;}{3EVQ#sLc(r0s9nuglC~oE9jPO7}D2!ikolZAJA*d(;vUhTrhpdwnmFu;Fg;RqV z!nWr!b_BuFH!pruuj~F7PY(q2z{H5B;aOPW*8R!DT#DyY|K=WV1>u7)K7>=Z3c)Ca zf}%G;LqU2G#DoA8mVXNO+5y&OY*Cr@k9s=n4LN!#WIoEGq^ZP14c&${pVw7JDP$E& zV6mYYJc!>zE6}*_mtb4MUA1jd7oX3J+r+K(i*H`#uG5L83&_u1k95deH>AttO#o4n zqz<@B%74GO-AzFoXU*bDgLL66E-Z_s$Nqr5i@{B9njZPQ59n{7Y2|Z?wr};iP7_){ z2EaDtt>Z|EV_-wm(k54sc5Gg&{q}oFkt^NX7VlF2{d_qTo?HoeDF+~Zd^||%K08qt zC7tW{0rs5!6>9ICYVDw$Q4RGb|A*g9O1MT*EU^69xEocg)Mt=S`%~{6I$yo>^!j)etO_L&_xdk zwxozCg+(8YV zgC+#jN{PEFEw%vi()NNDsoXL2E! zI`~cw!M{SZGjl}+A6ce$*AUM(6ul;L=Z)1n!K6E%5_&7D@`>;HDDFjGieB9}1 z^sODO{3CpN@f@FzdnAijl5UT4x>t~&+lB-rD4Y{-*fhwq6Jse4F5-(eX)FjqS{t-<*G9y~KB=j!EOK~5#Am4?E#&byO$10Mnz6DX zKPn*|!5q9)0n6SOAbJ8*6O|)jOKV73=m&H6nuk%k}A$f>OekU({z^8>kXwCz?f&$7~BdiP{Co; zgmD1Xn3t$kHWY&$bF@}%!uPH>!+5FM?RE^Rd$2sj&4)EkCkb`9JNVs1OF>;TKz?d2 zR~#LZj<*TmQvD~89JLG;0>+OK>RSQUq^*WPX)MHs&ISoJRMZziYV2|nhjGJmVTU9I z(i`X321CgKfuOia+}H0P<5zQ0!dm#C{5Nm|FV^y;xiBR{gneg@!FIn z(A-(Yfnb(L9t=kWy#L&B*BeLDQ~_G+>#bdP)}t=J=flrsat1|Og|+kNciJklzPMHG znKM2@Y1RWQo%#fL*c1385zmI-f%9eX&gV~p-PHyy_9|GDz5|veXMzhIqDI@w&!-3N ze)!7E+}fBTJ|1Wp-xwA9l_0ptt3;z{eFpn1%97$LS;yE%d5pzd=d*;d*=$-uE7%*);cqqM+p&Uq@BqC9 znrPZO92i>j3u=NcH#z7;QJ1U)vDofxHKs%tIA56gXVoJ?Jmp|X(S=w262_Ga^GzUT z<9M(ddnFwF@~rFil36N;s~xKV^Nvn?V;-#x%+9htzUD>TcdlO_H}lF-BWNb7MExEt zALxyPg$aLfr$pzogm9xmg>Rj_37-|ushh{yKQ3)mUcU2BK5vlBTiUxDHxtirV7Ls~ zcf(O+Bv8aJ1xTSUA*E-Yy9-NP78V%K#sqzxg&1S7vQYWlh_Q9VD4Eh^*|>*$L>>=D zhjkeW@HN?eG+{J`g{nQOMRr%m?CzdcvKZAJ^wi+93pemb<4Fv_kDI!0AHy+Og|0jO z?}(p@=7wq`21EkL+(;M~8B+n})PBAhifd%I<2gYnC2L(x$Il7)B` r$)~34+^^v}C0sIy4|qR@VFv#NWc>9K4+q~x00000NkvXXu0mjfg>sYn diff --git a/static/js/experiments.js b/static/js/experiments.js index aa8af55fc69..d0e7b8aa100 100644 --- a/static/js/experiments.js +++ b/static/js/experiments.js @@ -25,12 +25,4 @@ $(function() { $("#toolbar").css({'background-image':'url('+newIcon+')'}); event.preventDefault(); }); - - var egghunt = 0; - $("#toolbar").click(function() { - if (egghunt > 6) { - $("#toolbar").css({'background-image': 'url(/images/logo-egg.png)'}); - } - egghunt++; - }); }); From 423d1c1e42663e23002f8a3023d095174e793155 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 10 Apr 2015 01:55:30 -0700 Subject: [PATCH 253/714] first pass at client side alarm for stale data --- README.md | 2 ++ env.js | 6 ++++- lib/websocket.js | 11 +++++--- static/index.html | 2 ++ static/js/client.js | 58 +++++++++++++++++++++++++++++++++++++++---- static/js/ui-utils.js | 10 +++++++- 6 files changed, 79 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 07bf166f5c0..b0d0c08201f 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,8 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config. * `ALARM_HIGH` (`on`) - possible values `on` or `off` * `ALARM_LOW` (`on`) - possible values `on` or `off` * `ALARM_URGENT_LOW` (`on`) - possible values `on` or `off` + * `ALARM_TIMEAGO_WARN` (`on`) - possible values `on` or `off` + * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off` ## Setting environment variables diff --git a/env.js b/env.js index c8be09fb28c..12727dfc3f6 100644 --- a/env.js +++ b/env.js @@ -58,6 +58,8 @@ function config ( ) { , 'alarmHigh': true , 'alarmLow': true , 'alarmUrgentLow': true + , 'alarmTimeAgoWarn': true + , 'alarmTimeAgoUrgent': true , 'language': 'en' // not used yet } ; @@ -74,7 +76,9 @@ function config ( ) { env.defaults.alarmHigh = readENV('ALARM_HIGH', env.defaults.alarmHigh); env.defaults.alarmLow = readENV('ALARM_LOW', env.defaults.alarmLow); env.defaults.alarmUrgentLow = readENV('ALARM_URGENT_LOW', env.defaults.alarmUrgentLow); - + env.defaults.alarmTimeAgoWarn = readENV('ALARM_TIMEAGO_WARN', env.defaults.alarmTimeAgoWarn); + env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent); + //console.log(JSON.stringify(env.defaults)); env.SSL_KEY = readENV('SSL_KEY'); diff --git a/lib/websocket.js b/lib/websocket.js index 760aae25be2..c3da8be29a9 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -109,9 +109,14 @@ var alarms = { }; function ackAlarm(alarmType, silenceTime) { - alarms[alarmType].lastAckTime = new Date().getTime(); - alarms[alarmType].silenceTime = silenceTime ? silenceTime : FORTY_MINUTES; - delete alarms[alarmType].lastEmitTime; + 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 diff --git a/static/index.html b/static/index.html index 4f77b714dd5..a2bf5c78f1a 100644 --- a/static/index.html +++ b/static/index.html @@ -82,6 +82,8 @@

Nightscout

+
+
Night Mode
diff --git a/static/js/client.js b/static/js/client.js index c0a2dab5919..ba767eef330 100644 --- a/static/js/client.js +++ b/static/js/client.js @@ -26,8 +26,8 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , HOUR_IN_SECS = 3600 , DAY_IN_SECS = 86400 , WEEK_IN_SECS = 604800 - , MINUTES_SINCE_LAST_UPDATE_WARN = 10 - , MINUTES_SINCE_LAST_UPDATE_URGENT = 20; + , MINUTES_SINCE_LAST_UPDATE_WARN = 15 + , MINUTES_SINCE_LAST_UPDATE_URGENT = 30; var socket , isInitialData = false @@ -43,6 +43,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; , now = Date.now() , data = [] , foucusRangeMS = THREE_HOURS_MS + , clientAlarms = {} , audio = document.getElementById('audio') , alarmInProgress = false , currentAlarmType = null @@ -1186,13 +1187,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; }); $('#container').removeClass('alarming'); - brushed(true); // only emit ack if client invoke by button press if (isClient) { - socket.emit('ack', currentAlarmType || 'alarm', silenceTime); - brushed(false); + 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) { @@ -1458,6 +1467,35 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; $('#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 @@ -1471,6 +1509,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage; updateTitle(); } + if ( + (browserSettings.alarmTimeAgoWarn && ago.status == 'warn') + || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) { + checkTimeAgoAlarm(ago); + } + + if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) { + stopAlarm(true, ONE_MIN_IN_MS); + } + if (retroMode || !ago.value) { lastEntry.find('em').hide(); } else { diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js index 3736686a25a..464f60014e6 100644 --- a/static/js/ui-utils.js +++ b/static/js/ui-utils.js @@ -28,6 +28,8 @@ function getBrowserSettings(storage) { 'alarmHigh': storage.get('alarmHigh'), 'alarmLow': storage.get('alarmLow'), 'alarmUrgentLow': storage.get('alarmUrgentLow'), + 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'), + 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'), 'nightMode': storage.get('nightMode'), 'showRawbg': storage.get('showRawbg'), 'customTitle': storage.get('customTitle'), @@ -47,10 +49,14 @@ function getBrowserSettings(storage) { 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.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent); $('#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-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent); json.nightMode = setDefault(json.nightMode, app.defaults.nightMode); $('#nightmode-browser').prop('checked', json.nightMode); @@ -351,6 +357,8 @@ $('#save').click(function(event) { 'alarmHigh': $('#alarm-high-browser').prop('checked'), 'alarmLow': $('#alarm-low-browser').prop('checked'), 'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'), + 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'), + 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'), 'nightMode': $('#nightmode-browser').prop('checked'), 'showRawbg': $('input:radio[name=show-rawbg]:checked').val(), 'customTitle': $('input#customTitle').prop('value'), @@ -365,7 +373,7 @@ $('#save').click(function(event) { $('#useDefaults').click(function(event) { //remove all known settings, since there might be something else is in localstorage - var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat']; + var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoUrgent', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat']; settings.forEach(function(setting) { browserStorage.remove(setting); }); From 9a543105144afc3e55b047bb8cc89aa188c48f48 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Apr 2015 22:46:06 -0700 Subject: [PATCH 254/714] create a better default mqtt clientId by hashing the mongo host/db/collection --- env.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/env.js b/env.js index c8be09fb28c..ec612130540 100644 --- a/env.js +++ b/env.js @@ -32,13 +32,22 @@ function config ( ) { } env.version = software.version; env.name = software.name; - env.MQTT_MONITOR = readENV('MQTT_MONITOR', null); env.DISPLAY_UNITS = readENV('DISPLAY_UNITS', 'mg/dl'); env.PORT = readENV('PORT', 1337); 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) { - env.mqtt_client_id = [env.mongo.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); + env.mqtt_client_id = mongoHash.digest('hex'); + console.info('Using Mongo host/db/collection to create the default MQTT client_id', hostDbCollection); + 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); + } } env.settings_collection = readENV('MONGO_SETTINGS_COLLECTION', 'settings'); env.treatments_collection = readENV('MONGO_TREATMENTS_COLLECTION', 'treatments'); From 6eb1b62d73bc9a956af5a6f7d914fa24836ebf65 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Apr 2015 22:46:31 -0700 Subject: [PATCH 255/714] clean/sort up package.json --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fd31ca30ef9..b667af528db 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "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", @@ -51,6 +52,7 @@ "event-stream": "~3.1.5", "express": "^4.6.1", "express-extension-to-accept": "0.0.2", + "forever": "~0.13.0", "git-rev": "git://github.com/bewest/git-rev.git", "long": "~2.2.3", "mongodb": "^1.4.7", @@ -58,10 +60,7 @@ "mqtt": "~0.3.11", "pushover-notifications": "0.2.0", "sgvdata": "git://github.com/ktind/sgvdata.git#wip/protobuf", - "socket.io": "^0.9.17", - "git-rev": "git://github.com/bewest/git-rev.git", - "bootevent": "0.0.1", - "forever": "~0.13.0" + "socket.io": "^0.9.17" }, "devDependencies": { "istanbul": "~0.3.5", From 51462bb625104a8897fbb490f9b17da01a048d65 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 14 Apr 2015 23:06:53 -0700 Subject: [PATCH 256/714] added basic units module to unify all mg/dl to mmol display conversion also fixed Calibration Pushover message to use the configured DISPLAY_UNITS like the rest of the system --- bundle/bundle.source.js | 1 + lib/entries.js | 7 ++++++- lib/pebble.js | 3 ++- lib/units.js | 11 +++++++++++ static/clock.html | 3 ++- static/js/client.js | 2 +- static/js/ui-utils.js | 2 +- 7 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 lib/units.js diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js index e9c8f6e8dca..8c8df8ef7f3 100644 --- a/bundle/bundle.source.js +++ b/bundle/bundle.source.js @@ -4,6 +4,7 @@ window.Nightscout = { iob: require('../lib/iob')() + , units: require('../lib/units')() }; console.info("Nightscout bundle ready", window.Nightscout); diff --git a/lib/entries.js b/lib/entries.js index 77d5a45423b..240f56906bd 100644 --- a/lib/entries.js +++ b/lib/entries.js @@ -2,6 +2,7 @@ var es = require('event-stream'); var sgvdata = require('sgvdata'); +var units = require('./units')(); var TEN_MINS = 10 * 60 * 1000; @@ -134,9 +135,13 @@ function storage(name, storage, pushover) { if (offset > TEN_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: ' + doc.mbg, + message: '\nMeter BG: ' + mbg, title: 'Calibration', sound: 'magic', timestamp: new Date(doc.date), diff --git a/lib/pebble.js b/lib/pebble.js index 4f78cf68d21..ea9b9bbabc7 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -15,6 +15,7 @@ var DIRECTIONS = { var iob = require("./iob")(); var async = require('async'); +var units = require('./units')(); function directionToTrend (direction) { var trend = 8; @@ -34,7 +35,7 @@ function pebble (req, res) { function scaleBg(bg) { if (req.mmol) { - return (Math.round((bg / 18) * 10) / 10).toFixed(1); + return units.mgdlToMMOL(bg); } else { return bg; } diff --git a/lib/units.js b/lib/units.js new file mode 100644 index 00000000000..031685f2280 --- /dev/null +++ b/lib/units.js @@ -0,0 +1,11 @@ +function mgdlToMMOL(mgdl) { + return (Math.round((mgdl / 18) * 10) / 10).toFixed(1); +} + +function configure() { + return { + mgdlToMMOL: mgdlToMMOL + } +} + +module.exports = configure; \ No newline at end of file diff --git a/static/clock.html b/static/clock.html index ea7d56b2cfa..f633e177ec1 100644 --- a/static/clock.html +++ b/static/clock.html @@ -41,6 +41,7 @@

+ - - + + From 21ca447270360d32bf91c463cfb8ba9b77bae3d7 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Tue, 21 Apr 2015 22:44:15 -0700 Subject: [PATCH 274/714] 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 275/714] 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 276/714] 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 277/714] 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 278/714] 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 279/714] 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 280/714] 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 281/714] 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 282/714] 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 283/714] 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 284/714] 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 285/714] 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 286/714] 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 287/714] 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 288/714] 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 289/714] 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 290/714] 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 291/714] 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 292/714] 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 293/714] 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 294/714] 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 295/714] 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 296/714] 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 297/714] 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 298/714] 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 299/714] 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 300/714] 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 301/714] 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 302/714] 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 303/714] 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 304/714] 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 305/714] 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 306/714] 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 307/714] 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 308/714] 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 309/714] 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 310/714] 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 311/714] 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 312/714] 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 313/714] 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 314/714] 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 315/714] 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 316/714] 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 317/714] 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 318/714] 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 319/714] 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 320/714] 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 321/714] 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 322/714] 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 323/714] 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 324/714] 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 325/714] 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 326/714] 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 327/714] 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 328/714] 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 329/714] 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 330/714] 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 331/714] 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 332/714] 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 333/714] 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 334/714] 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 335/714] 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 336/714] 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 337/714] 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 338/714] 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 339/714] 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 340/714] 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 341/714] 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 342/714] 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 343/714] 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 344/714] 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 345/714] 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 346/714] 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 347/714] 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 348/714] 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 349/714] 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 350/714] 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 351/714] 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 352/714] 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 353/714] 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 354/714] 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 355/714] 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 356/714] 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 357/714] 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 358/714] 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 359/714] 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 360/714] 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 361/714] 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 362/714] 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 363/714] 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 364/714] 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 365/714] 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 366/714] 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 367/714] 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 368/714] 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 369/714] 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 370/714] 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 371/714] 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 372/714] 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 373/714] 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 374/714] 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 375/714] 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 376/714] 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 377/714] 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 378/714] 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 379/714] 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 380/714] 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 381/714] 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 382/714] 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 383/714] 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 384/714] 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 385/714] 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 386/714] 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 387/714] 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 388/714] 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 389/714] 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 390/714] 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 391/714] 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 392/714] 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 393/714] 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 394/714] 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 395/714] 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 396/714] 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 397/714] = 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 398/714] 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 399/714] 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 400/714] 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 401/714] 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 402/714] 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 403/714] 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 404/714] 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 405/714] 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 406/714] 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 407/714] 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 408/714] 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 409/714] 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 410/714] 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 411/714] 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 412/714] 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 413/714] 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 414/714] 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 415/714] 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 416/714] 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 417/714] 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 418/714] 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 419/714] 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 420/714] 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 421/714] 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 422/714] 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 423/714] 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 424/714] 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 425/714] 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 426/714] 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 427/714] 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 428/714] 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 429/714] 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 430/714] 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 431/714] 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 432/714] 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 433/714] 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 434/714] 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 435/714] 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 436/714] 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 437/714] 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 438/714] 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 439/714] 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 440/714] 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 441/714] 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 442/714] 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 443/714] 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 444/714] 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 445/714] 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 446/714] 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 447/714] 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 448/714] 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 449/714] 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 450/714] 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 451/714] 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 452/714] 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 453/714] 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 454/714] 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 455/714] 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 456/714] 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 457/714] 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 458/714] 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 459/714] 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 460/714] 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 461/714] 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 462/714] 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 463/714] 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 464/714] 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 465/714] 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 466/714] 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 467/714] 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 468/714] 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 469/714] 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 470/714] 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 471/714] 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 472/714] 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 473/714] 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 474/714] 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 475/714] 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 476/714] 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 477/714] 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 478/714] 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 479/714] 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 480/714] 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 481/714] 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 482/714] 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 483/714] 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 484/714] 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 485/714] 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 486/714] 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 487/714] 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 488/714] 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 489/714] 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 490/714] 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 491/714] 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 492/714] 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 493/714] 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 494/714] 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 495/714] 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 496/714] 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 497/714] 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 498/714] 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 499/714] 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 500/714] 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 501/714] 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 502/714] 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 503/714] 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 504/714] 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 505/714] 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 506/714] 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 507/714] 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 508/714] 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 509/714] 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 510/714] 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 511/714] 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 512/714] 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 513/714] 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 514/714] 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 515/714] 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 516/714] 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 517/714] 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 518/714] 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 519/714] 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 520/714] 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 521/714] 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 522/714] 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 523/714] 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 524/714] 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 525/714] 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 526/714] 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 527/714] 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 528/714] 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 646/714] 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 647/714] 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 648/714] 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 649/714] 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 650/714] 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 651/714] 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 652/714] 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 653/714] 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 654/714] 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 655/714] 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 656/714] 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 657/714] 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 658/714] 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 659/714] 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 660/714] 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 661/714] 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 662/714] 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 663/714] 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 664/714] 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 665/714] 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 666/714] 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 667/714] 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 668/714] ; --- 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 669/714] 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 670/714] 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 671/714] 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 672/714] 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 673/714] 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 674/714] 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 675/714] ;... --- 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 676/714] 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 677/714] 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 678/714] 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 679/714] 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 680/714] 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 681/714] 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 682/714] 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 683/714] 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 dec5a0f9e65af4bb351f9d9fce1c5e3850b36b24 Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Fri, 17 Jul 2015 19:21:24 -0700 Subject: [PATCH 684/714] 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 685/714] 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 f19774df169e7b1f605143db11da3aaf7ea5e1f3 Mon Sep 17 00:00:00 2001 From: Ben West Date: Sat, 18 Jul 2015 14:39:11 -0700 Subject: [PATCH 686/714] 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 687/714] 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 99ea9fa58e6191aa4f347a66ec5935b83eb7fb6d Mon Sep 17 00:00:00 2001 From: Jason Calabrese Date: Sat, 18 Jul 2015 16:26:08 -0700 Subject: [PATCH 688/714] 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 689/714] ; --- 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 690/714] 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 691/714] 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 692/714] 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 693/714] 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 694/714] 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 695/714] 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 696/714] 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 697/714] 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 698/714] 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 699/714] 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 700/714] 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 701/714] 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 702/714] 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 703/714] 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 704/714] 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 705/714] 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 706/714] 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 707/714] 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 708/714] 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 2c489dd8cdb67853507a199063ee0ec483e3b41e Mon Sep 17 00:00:00 2001 From: MilosKozak Date: Wed, 22 Jul 2015 16:45:33 +0200 Subject: [PATCH 709/714] 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 710/714] 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 @@ - + :