Skip to content

Commit

Permalink
Merge pull request nightscout#1 from mddub/wip/minimed-connect
Browse files Browse the repository at this point in the history
Wip/minimed connect
  • Loading branch information
lcmatoush committed Oct 17, 2015
2 parents 63283e8 + e71476f commit aac3871
Show file tree
Hide file tree
Showing 39 changed files with 1,334 additions and 105 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,13 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm
* `BRIDGE_FIRST_FETCH_COUNT` (`3`) - Changes max count during the very first update only.
* `BRIDGE_MAX_FAILURES` (`3`) - How many failures before giving up.
* `BRIDGE_MINUTES` (`1400`) - The time window to search for new data per update (default is one day in minutes).
* `mmconnect` (MiniMed Connect bridge) - Transfer real-time MiniMed Connect data from the Medtronic CareLink server into Nightscout ([read more](https://github.com/mddub/minimed-connect-to-nightscout))
* `MMCONNECT_USER_NAME` - Your user name for CareLink Connect.
* `MMCONNECT_PASSWORD` - Your password for CareLink Connect.
* `MMCONNECT_INTERVAL` (`60000` *1 minute*) - Number of milliseconds to wait between requests to the CareLink server.
* `MMCONNECT_MAX_RETRY_DURATION` (`32`) - Maximum number of total seconds to spend retrying failed requests before giving up.
* `MMCONNECT_SGV_LIMIT` (`24`) - Maximum number of recent sensor glucose values to send to Nightscout on each request.
* `MMCONNECT_VERBOSE` - Set this to any truthy value to log CareLink request information to the console.

Also see [Pushover](#pushover) and [IFTTT Maker](#ifttt-maker).

Expand Down
1 change: 1 addition & 0 deletions bundle/bundle.source.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
, units: require('../lib/units')()
, plugins: require('../lib/plugins/')().registerClientDefaults()
, report_plugins: require('../lib/report_plugins/')()
, admin_plugins: require('../lib/admin_plugins/')()
};

console.info('Nightscout bundle ready');
Expand Down
70 changes: 70 additions & 0 deletions lib/admin_plugins/cleanstatusdb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use strict';

var cleanstatusdb = {
name: 'cleanstatusdb'
, label: 'Clean Mongo status database'
, pluginType: 'admin'
};

function init() {
return cleanstatusdb;
}

module.exports = init;

cleanstatusdb.actions = [
{
name: 'Delete all documents from devicestatus collection'
, description: 'This task removes all documents from devicestatus collection. Useful when uploader battery status is not properly updated.'
, buttonLabel: 'Delete all documents'
, confirmText: 'Delete all documents from devicestatus collection?'
}
];

cleanstatusdb.actions[0].init = function init(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + cleanstatusdb.name + '_0_status');

$status.hide().text(translate('Loading database ...')).fadeIn('slow');
$.ajax('/api/v1/devicestatus.json?count=500', {
success: function (records) {
var recs = (records.length === 500 ? '500+' : records.length);
$status.hide().text(translate('Database contains %1 records',{ params: [recs] })).fadeIn('slow');
},
error: function () {
$status.hide().text(translate('Error loading database')).fadeIn('slow');
}
}).done(function () { if (callback) { callback(); } });
};

cleanstatusdb.actions[0].code = function deleteRecords(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + cleanstatusdb.name + '_0_status');

if (!client.hashauth.isAuthenticated()) {
alert(translate('Your device is not authenticated yet'));
if (callback) {
callback();
}
return;
};

$status.hide().text(translate('Deleting records ...')).fadeIn('slow');
$.ajax({
method: 'DELETE'
, url: '/api/v1/devicestatus/*'
, headers: {
'api-secret': client.hashauth.hash()
}
}).done(function success () {
$status.hide().text(translate('All records removed ...')).fadeIn('slow');
if (callback) {
callback();
}
}).fail(function fail() {
$status.hide().text(translate('Error')).fadeIn('slow');
if (callback) {
callback();
}
});
};
172 changes: 172 additions & 0 deletions lib/admin_plugins/futureitems.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
'use strict';

var futureitems = {
name: 'futureitems'
, label: 'Remove future items from mongo database'
, pluginType: 'admin'
};

function init() {
return futureitems;
}

module.exports = init;

futureitems.actions = [
{
name: 'Find and remove treatments in the future'
, description: 'This task find and remove treatments in the future.'
, buttonLabel: 'Remove treatments in the future'
}
, {
name: 'Find and remove entries in the future'
, description: 'This task find and remove CGM data in the future created by uploader with wrong date/time.'
, buttonLabel: 'Remove entries in the future'
}
];

futureitems.actions[0].init = function init(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + futureitems.name + '_0_status');

function valueOrEmpty (value) {
return value ? value : '';
}

function showOneTreatment (tr, table) {
table.append($('<tr>').css('background-color','#0f0f0f')
.append($('<td>').attr('width','20%').append(new Date(tr.created_at).toLocaleString().replace(/([\d]+:[\d]{2})(:[\d]{2})(.*)/, '$1$3')))
.append($('<td>').attr('width','20%').append(tr.eventType ? translate(client.careportal.resolveEventName(tr.eventType)) : ''))
.append($('<td>').attr('width','10%').attr('align','center').append(tr.glucose ? tr.glucose + ' ('+translate(tr.glucoseType)+')' : ''))
.append($('<td>').attr('width','10%').attr('align','center').append(valueOrEmpty(tr.insulin)))
.append($('<td>').attr('width','10%').attr('align','center').append(valueOrEmpty(tr.carbs)))
.append($('<td>').attr('width','10%').append(valueOrEmpty(tr.enteredBy)))
.append($('<td>').attr('width','20%').append(valueOrEmpty(tr.notes)))
);
}

function showTreatments(treatments, table) {
table.append($('<tr>').css('background','#040404')
.append($('<th>').css('width','80px').attr('align','left').append(translate('Time')))
.append($('<th>').css('width','150px').attr('align','left').append(translate('Event Type')))
.append($('<th>').css('width','150px').attr('align','left').append(translate('Blood Glucose')))
.append($('<th>').css('width','50px').attr('align','left').append(translate('Insulin')))
.append($('<th>').css('width','50px').attr('align','left').append(translate('Carbs')))
.append($('<th>').css('width','150px').attr('align','left').append(translate('Entered By')))
.append($('<th>').css('width','300px').attr('align','left').append(translate('Notes')))
);
for (var t=0; t<treatments.length; t++) {
showOneTreatment (treatments[t], table);
}
};

$status.hide().text(translate('Loading database ...')).fadeIn('slow');
var nowiso = new Date().toISOString();
$.ajax('/api/v1/treatments.json?&find[created_at][$gte]=' + nowiso, {
success: function (records) {
futureitems.treatmentrecords = records;
$status.hide().text(translate('Database contains %1 future records',{ params: [records.length] })).fadeIn('slow');
var table = $('<table>').css('margin-top','10px');
$('#admin_' + futureitems.name + '_0_html').append(table);
showTreatments(records, table);
futureitems.actions[0].confirmText = translate('Remove %1 selected records?', { params: [records.length] });
},
error: function () {
$status.hide().text(translate('Error loading database')).fadeIn('slow');
futureitems.treatmentrecords = [];
}
}).done(function () { if (callback) { callback(); } });
};

futureitems.actions[0].code = function deleteRecords(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + futureitems.name + '_0_status');

if (!client.hashauth.isAuthenticated()) {
alert(translate('Your device is not authenticated yet'));
if (callback) {
callback();
}
return;
};

function deleteRecordById (_id) {
$.ajax({
method: 'DELETE'
, url: '/api/v1/treatments/' + _id
, headers: {
'api-secret': client.hashauth.hash()
}
}).done(function success () {
$status.text(translate('Record %1 removed ...', { params: [_id] }));
}).fail(function fail() {
$status.text(translate('Error removing record %1', { params: [_id] }));
});
}

$status.hide().text(translate('Deleting records ...')).fadeIn('slow');
for (var i = 0; i < futureitems.treatmentrecords.length; i++) {
deleteRecordById(futureitems.treatmentrecords[i]._id);
}
$('#admin_' + futureitems.name + '_0_html').html('');

if (callback) {
callback();
}
};

futureitems.actions[1].init = function init(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + futureitems.name + '_1_status');

$status.hide().text(translate('Loading database ...')).fadeIn('slow');
var now = new Date().getTime();
$.ajax('/api/v1/entries.json?&find[date][$gte]=' + now, {
success: function (records) {
futureitems.entriesrecords = records;
$status.hide().text(translate('Database contains %1 future records',{ params: [records.length] })).fadeIn('slow');
futureitems.actions[1].confirmText = translate('Remove %1 selected records?', { params: [records.length] });
},
error: function () {
$status.hide().text(translate('Error loading database')).fadeIn('slow');
futureitems.entriesrecords = [];
}
}).done(function () { if (callback) { callback(); } });
};

futureitems.actions[1].code = function deleteRecords(client, callback) {
var translate = client.translate;
var $status = $('#admin_' + futureitems.name + '_1_status');

if (!client.hashauth.isAuthenticated()) {
alert(translate('Your device is not authenticated yet'));
if (callback) {
callback();
}
return;
};

function deteleteRecordById (_id) {
$.ajax({
method: 'DELETE'
, url: '/api/v1/entries/' + _id
, headers: {
'api-secret': client.hashauth.hash()
}
}).done(function success () {
$status.text(translate('Record %1 removed ...', { params: [_id] }));
}).fail(function fail() {
$status.text(translate('Error removing record %1', { params: [_id] }));
});
}


$status.hide().text(translate('Deleting records ...')).fadeIn('slow');
for (var i = 0; i < futureitems.entriesrecords.length; i++) {
deteleteRecordById(futureitems.entriesrecords[i]._id);
}

if (callback) {
callback();
}
};
79 changes: 79 additions & 0 deletions lib/admin_plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

var _ = require('lodash');

function init() {
var allPlugins = [
require('./cleanstatusdb')()
, require('./futureitems')()
];

function plugins(name) {
if (name) {
return _.find(allPlugins, {name: name});
} else {
return plugins;
}
}

plugins.eachPlugin = function eachPlugin(f) {
_.each(allPlugins, f);
};

plugins.createHTML = function createHTML(client) {
var translate = client.translate;
plugins.eachPlugin(function addHtml(p) {
var fs = $('<fieldset>');
$('#admin_placeholder').append(fs);
fs.append($('<legend>').append(translate(p.label)));
for (var i = 0; i < p.actions.length; i++) {
if (i !== 0) {
fs.append('<hr>');
}
var a = p.actions[i];
// add main plugin html
fs.append($('<b>').css('text-decoration','underline').append(translate(a.name)));
fs.append('<br>');
fs.append($('<i>').append(translate(a.description)));
fs.append($('<div>').attr('id','admin_' + p.name + '_' + i + '_html'));
fs.append($('<button>').addClass('adminButton').attr('plugin',p.name).attr('action',i).append(translate(a.buttonLabel)));
fs.append($('<span>').attr('id','admin_' + p.name + '_' + i + '_status'));
if (a.init) {
a.init(client);
}
}
// add css
if (p.css) {
$('<style>')
.prop('type', 'text/css')
.html(p.css)
.appendTo('head');
}
});
$('.adminButton').click(plugins.doAction);
};

plugins.doAction = function doAction(event) {
var Nightscout = window.Nightscout;
var plugin = $(this).attr('plugin');
var action = $(this).attr('action');
var a = plugins(plugin).actions[action];
var ok = true;
if (a.confirmText) {
ok = window.confirm(Nightscout.client.translate(a.confirmText));
}
if (ok) {
console.log('Running action', action, 'on plugin', plugin);
a.code(Nightscout.client);
$(this).css('display','none');
}
if (event) {
event.preventDefault();
}
};

return plugins();

}

module.exports = init;
10 changes: 10 additions & 0 deletions lib/api/devicestatus/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ function configure (app, wares, ctx) {
}
});
});
// delete record
api.delete('/devicestatus/:_id', wares.verifyAuthorization, function(req, res) {
ctx.devicestatus.remove(req.params._id, function (err, removed) {
if (err) {
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
} else {
res.json(removed);
}
});
});

}

Expand Down
2 changes: 1 addition & 1 deletion lib/api/entries/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ function configure (app, wares, ctx) {
if (entry) {
req.query.find.type = entry.type;
} else {
res.entries_err = "No such id: '" + req.params.spec + "'";
res.entries_err = 'No such id: \'' + req.params.spec + '\'';
}
next();
});
Expand Down
9 changes: 9 additions & 0 deletions lib/bootevent.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ function boot (env) {
next( );
}

function setupMMConnect (ctx, next) {
ctx.mmconnect = require('./plugins/mmconnect').init(env, ctx.entries);
if (ctx.mmconnect) {
ctx.mmconnect.run();
}
next( );
}

function finishBoot (ctx, next) {
ctx.bus.uptime( );

Expand All @@ -99,6 +107,7 @@ function boot (env) {
.acquire(ensureIndexes)
.acquire(setupListeners)
.acquire(setupBridge)
.acquire(setupMMConnect)
.acquire(finishBoot);
}

Expand Down
Loading

0 comments on commit aac3871

Please sign in to comment.