diff --git a/.gitignore b/.gitignore index a54c7ec4f..4fcbc57e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ bower_components/ node_modules/ + +bundle/bundle.out.js + .idea/ *.iml my.env diff --git a/bundle/bundle.source.js b/bundle/bundle.source.js new file mode 100644 index 000000000..e9c8f6e8d --- /dev/null +++ b/bundle/bundle.source.js @@ -0,0 +1,12 @@ +(function () { + + window.Nightscout = window.Nightscout || {}; + + window.Nightscout = { + iob: require('../lib/iob')() + }; + + console.info("Nightscout bundle ready", window.Nightscout); + +})(); + diff --git a/bundle/index.js b/bundle/index.js new file mode 100644 index 000000000..5ea7ff424 --- /dev/null +++ b/bundle/index.js @@ -0,0 +1,17 @@ +'use strict'; + +var browserify_express = require('browserify-express'); + +function bundle() { + return browserify_express({ + entry: __dirname + '/bundle.source.js', + watch: [__dirname + '../lib/', __dirname + '/bundle.source.js'], + mount: '/public/js/bundle.js', + verbose: true, + //minify: true, + bundle_opts: { debug: true }, // enable inline sourcemap on js files + write_file: __dirname + '/bundle.out.js' + }); +} + +module.exports = bundle; \ No newline at end of file diff --git a/env.js b/env.js index 0218d6963..c063c2274 100644 --- a/env.js +++ b/env.js @@ -41,6 +41,7 @@ function config ( ) { env.mongo_collection = readENV('MONGO_COLLECTION', 'entries'); 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'); env.enable = readENV('ENABLE'); diff --git a/lib/api/index.js b/lib/api/index.js index 13ccb4c57..994306139 100644 --- a/lib/api/index.js +++ b/lib/api/index.js @@ -1,6 +1,6 @@ 'use strict'; -function create (env, entries, settings, treatments, devicestatus) { +function create (env, entries, settings, treatments, profile, devicestatus) { var express = require('express'), app = express( ) ; @@ -47,6 +47,7 @@ function create (env, entries, settings, treatments, devicestatus) { app.use('/', require('./entries/')(app, wares, entries)); app.use('/', require('./settings/')(app, wares, settings)); app.use('/', require('./treatments/')(app, wares, treatments)); + app.use('/', require('./profile/')(app, wares, profile)); app.use('/', require('./devicestatus/')(app, wares, devicestatus)); // Status diff --git a/lib/api/profile/index.js b/lib/api/profile/index.js new file mode 100644 index 000000000..5dae6a971 --- /dev/null +++ b/lib/api/profile/index.js @@ -0,0 +1,29 @@ +'use strict'; + +var consts = require('../../constants'); + +function configure (app, wares, profile) { + 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('/profile/', function(req, res) { + profile.list(function (err, attribute) { + return res.json(attribute); + }); + }); + + return api; +} + +module.exports = configure; + diff --git a/lib/iob.js b/lib/iob.js new file mode 100644 index 000000000..b4800f133 --- /dev/null +++ b/lib/iob.js @@ -0,0 +1,80 @@ +'use strict'; + +function calcTotal(treatments, profile, time) { + + console.info("trying to calc"); + + var iob = 0 + , activity = 0; + + 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(); + } + + 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; + } + }); + + return { + iob: iob, + display: iob.toFixed(2), + activity: activity + }; +} + +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 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 + }; + +} + +function IOB(opts) { + + return { + calcTotal: calcTotal + }; + +} + +module.exports = IOB; \ No newline at end of file diff --git a/lib/pebble.js b/lib/pebble.js index 7f5e8e64a..d918de564 100644 --- a/lib/pebble.js +++ b/lib/pebble.js @@ -11,6 +11,8 @@ var DIRECTIONS = { , 'RATE OUT OF RANGE': 9 }; +var iob = require("./iob")(); + function directionToTrend (direction) { var trend = 8; if (direction in DIRECTIONS) { @@ -23,6 +25,8 @@ function pebble (req, res) { var ONE_DAY = 24 * 60 * 60 * 1000; var useMetricBg = (req.query.units === "mmol"); var uploaderBattery; + var treatmentResults; + var profileResult; function scaleBg(bg) { if (useMetricBg) { @@ -74,8 +78,11 @@ function pebble (req, res) { 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 + //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; + } var result = { status: [ {now:now}], bgs: bgs, cals: calData.slice(0, count) }; res.setHeader('content-type', 'application/json'); @@ -91,15 +98,48 @@ function pebble (req, res) { } var earliest_data = Date.now() - ONE_DAY; - var q = { find: {"date": {"$gte": earliest_data}} }; - req.entries.list(q, get_latest); + 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 q = { find: {"date": {"$gte": earliest_data}} }; + req.entries.list(q, get_latest); + }); + }); }); } -function configure (entries, devicestatus, env) { + +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; next( ); } return [middle, pebble]; diff --git a/lib/profile.js b/lib/profile.js new file mode 100644 index 000000000..c7ba0e101 --- /dev/null +++ b/lib/profile.js @@ -0,0 +1,24 @@ +'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 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; + return api; +} +module.exports = configure; diff --git a/lib/websocket.js b/lib/websocket.js index 49d10bedb..6936ef173 100644 --- a/lib/websocket.js +++ b/lib/websocket.js @@ -1,5 +1,5 @@ -function websocket (env, server, entries, treatments) { +function websocket (env, server, entries, treatments, profiles) { "use strict"; // CONSTANTS var ONE_HOUR = 3600000, @@ -29,6 +29,7 @@ var dir2Char = { treatmentData = [], mbgData = [], calData = [], + profileData = [], patientData = []; function start ( ) { @@ -146,6 +147,7 @@ function update() { cgmData = []; treatmentData = []; mbgData = []; + profileData = []; var earliest_data = now - TWO_DAYS; var q = { find: {"date": {"$gte": earliest_data}} }; entries.list(q, function (err, results) { @@ -188,8 +190,19 @@ function update() { treatment.x = timestamp.getTime(); return treatment; }); - // all done, do loadData - loadData( ); + + 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; + } + } + }); + // all done, do loadData + loadData( ); + }); }); }); @@ -237,6 +250,10 @@ function loadData() { }); } + if (profileData) { + var profile = profileData; + } + if (actualCurrent && actualCurrent < 39) errorCode = actualCurrent; var actualLength = actual.length - 1; @@ -271,7 +288,7 @@ function loadData() { // consolidate and send the data to the client var shouldEmit = is_different(actual, predicted, mbg, treatment, cal); - patientData = [actual, predicted, mbg, treatment, cal]; + patientData = [actual, predicted, mbg, treatment, profile, cal]; console.log('patientData', patientData.length); if (shouldEmit) { emitData( ); diff --git a/package.json b/package.json index af40c524b..ec203d385 100644 --- a/package.json +++ b/package.json @@ -34,16 +34,17 @@ "dependencies": { "body-parser": "^1.4.3", "bower": "^1.3.8", + "browserify-express": "^0.1.4", "errorhandler": "^1.1.1", "event-stream": "~3.1.5", "express": "^4.6.1", "express-extension-to-accept": "0.0.2", + "git-rev": "git://github.com/bewest/git-rev.git", "mongodb": "^1.4.7", "moment": "2.8.1", "pushover-notifications": "0.2.0", "sgvdata": "0.0.2", - "socket.io": "^0.9.17", - "git-rev": "git://github.com/bewest/git-rev.git" + "socket.io": "^0.9.17" }, "devDependencies": { "istanbul": "~0.3.5", diff --git a/server.js b/server.js index b1a5dd8b2..2f56c7597 100644 --- a/server.js +++ b/server.js @@ -50,8 +50,9 @@ var express = require('express'); 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, devicestatusStorage); +var api = require('./lib/api/')(env, entriesStorage, settings, treatmentsStorage, profile, devicestatusStorage); var pebble = require('./lib/pebble'); /////////////////////////////////////////////////// @@ -71,7 +72,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, env)); +app.get('/pebble', pebble(entriesStorage, treatmentsStorage, profile, devicestatusStorage, env)); //app.get('/package.json', software); @@ -82,6 +83,9 @@ 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') { @@ -105,7 +109,7 @@ store(function ready ( ) { // setup socket io for data and message transmission /////////////////////////////////////////////////// var websocket = require('./lib/websocket'); - var io = websocket(env, server, entriesStorage, treatmentsStorage); + var io = websocket(env, server, entriesStorage, treatmentsStorage, profile); }); /////////////////////////////////////////////////// diff --git a/static/css/main.css b/static/css/main.css index ed5ef975b..020fbd4a7 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -68,25 +68,55 @@ body { .bgStatus .currentDetails { font-size: 25%; - text-decoration: line-through; - display: block; - position: relative; - top: -20px; + position: relative; + top: -10px; } -.bgStatus.current .currentBG { - text-decoration: none; + +.currentDetails > span { + border-radius: 5px; + border: 2px solid #bdbdbd; + font-size: 80%; } -.bgStatus.current .currentDetails { - font-size: 25%; + +#bgButton .currentDetails > span { + border-color: #000; +} + +.currentDetails > span:not(:first-child) { + margin-left: 5px; +} + +.currentDetails > span * { + padding-left: 2px; + padding-right: 2px; + padding-bottom: 1px; +} + +.currentDetails > span em { + color: #bdbdbd; + font-style: normal; + font-weight: bold; +} + +.currentDetails > span label { + color: #000; + background: #bdbdbd; +} + +#bgButton .currentDetails > span label { + color: #bdbdbd; + background: inherit; +} + +.bgStatus.current .currentBG { text-decoration: none; - display: block; - position: relative; - top: -10px; } + .currentDirection { font-weight: normal; text-decoration: none; - font-size: 80%; + font-size: 90%; + vertical-align: top; } .time { @@ -101,7 +131,7 @@ body { font-size: 25%; } #lastEntry { - background: #999; + background: #808080; border-radius: 5px; color: #000; padding: 0.25em; @@ -261,6 +291,7 @@ div.tooltip { #currentTime { font-size: 85%; } + } @media (max-width: 750px) { @@ -272,6 +303,10 @@ div.tooltip { font-size: 70%; } + .bgStatus .currentDetails { + top: 0; + } + #chartContainer { top: 165px; font-size: 80%; @@ -305,6 +340,11 @@ div.tooltip { text-align: center; width: 100vw; } + + .bgStatus .currentDetails { + top: -10px; + } + #bgButton { font-size: 100%; } diff --git a/static/index.html b/static/index.html index d121858dc..703c87db5 100644 --- a/static/index.html +++ b/static/index.html @@ -30,13 +30,13 @@