Skip to content

Commit

Permalink
Drop PhantomJS dependency, use Node instead
Browse files Browse the repository at this point in the history
  • Loading branch information
mddub committed Oct 9, 2015
1 parent 66391aa commit 8314ff1
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 178 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
config.js
node_modules/
115 changes: 115 additions & 0 deletions fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* jshint node: true */
"use strict";

var common = require('common'),
extend = require('extend'),
request = require('request'),
zlib = require('zlib');

var config = require('./config'),
logger = require('./logger');

module.exports = (function () {
var CARELINK_SECURITY_URL = 'https://carelink.minimed.com/patient/j_security_check';
var CARELINK_AFTER_LOGIN_URL = 'https://carelink.minimed.com/patient/main/login.do';
var CARELINK_LOGIN_COOKIE = '_WL_AUTHCOOKIE_JSESSIONID';

var carelinkJsonUrlNow = function() {
return 'https://carelink.minimed.com/patient/connect/ConnectViewerServlet?cpSerialNumber=NONE&msgType=last24hours&requestTime=' + Date.now();
};

var jar = request.jar();

function reqOptions(extra) {
var defaults = {
jar: true,
followRedirect: false,
headers: {
Host: 'carelink.minimed.com',
Connection: 'keep-alive',
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:41.0) Gecko/20100101 Firefox/41.0',
'Accept-Encoding': 'gzip,deflate,sdch',
'Accept-Language': 'en-US,en;q=0.8'
}
};
return extend(true, defaults, extra);
}

function haveLoginCookie() {
return jar.getCookies(CARELINK_SECURITY_URL).filter(function(c) { return c.key == CARELINK_LOGIN_COOKIE; }).length > 0;
}

function doLogin(next) {
request.post(
CARELINK_SECURITY_URL,
reqOptions({
jar: jar,
qs: {j_username: config.CARELINK_USERNAME, j_password: config.CARELINK_PASSWORD}
}),
next
);
}

function doFetchCookie(response, next) {
request.get(
CARELINK_AFTER_LOGIN_URL,
reqOptions({jar: jar}),
next
);
}

function getConnectData(response, next) {
var url = carelinkJsonUrlNow();
logger.log('GET ' + url);
var resp = request.get(
url,
reqOptions({jar: jar, gzip: true}),
next
);
}

function parseData(response, next) {
try {
next(undefined, JSON.parse(response.body));
} catch (e) {
next(e);
}
}

function firstFetch(callback) {
common.step(
[
doLogin,
doFetchCookie,
getConnectData,
parseData,
callback
]
);
}

function fetchLoggedIn(callback) {
common.step(
[
getConnectData,
parseData,
callback
],
function onError(err) {
logger.log('Fetch JSON failed; logging in again');
firstFetch(callback);
}
);
}

function fetch(callback) {
if (haveLoginCookie()) {
fetchLoggedIn(callback);
} else {
logger.log('Logging in to CareLink');
firstFetch(callback);
}
}
return fetch;
})();
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
fetch: require('./fetch'),
nightscout: require('./nightscout')
};
6 changes: 6 additions & 0 deletions logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* jshint node: true */
"use strict";

module.exports.log = function(str) {
console.log(new Date() + ' ' + str);
};
127 changes: 127 additions & 0 deletions nightscout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* jshint node: true */
"use strict";

var crypto = require('crypto'),
request = require('request');

var config = require('./config'),
logger = require('./logger');

module.exports = (function() {
var PUMP_STATUS_ENTRY_TYPE = 'pump_status';
var SENSOR_GLUCOSE_ENTRY_TYPE = 'sgv';

function addTimeToEntry(pumpTimeString, entry) {
var timeUTC = Date.parse(pumpTimeString + ' ' + config.PUMP_TIMEZONE);
entry['date'] = timeUTC;
entry['dateString'] = new Date(timeUTC).toISOString();
return entry;
}

function pumpStatusEntry(data) {
var entry = {'type': PUMP_STATUS_ENTRY_TYPE};

[
'conduitBatteryLevel',
'conduitInRange',
'conduitMedicalDeviceInRange',
'reservoirLevelPercent',
'reservoirAmount',
'medicalDeviceBatteryLevelPercent'
].forEach(function(key) {
if(data[key] !== undefined) {
entry[key] = data[key];
}
});

if(data['activeInsulin'] && data['activeInsulin']['amount'] >= 0) {
entry['activeInsulin'] = data['activeInsulin']['amount'];
}

return addTimeToEntry(data['sMedicalDeviceTime'], entry);
}

function sgvEntries(data) {
var out = [];

if(data['sgs'] && data['sgs'].length) {
var sgvs = data['sgs'].filter(function(entry) {
return entry['kind'] === 'SG' && entry['sg'] !== 0;
});
// TODO: don't assume minimed will continue giving sensor glucose values ordered by date ascending
for(var i = Math.max(0, sgvs.length - config.NUM_RECORDS_TO_SUBMIT); i < sgvs.length; i++) {
var sgv = sgvs[i];
out.push(
addTimeToEntry(
sgv['datetime'],
{
'type': SENSOR_GLUCOSE_ENTRY_TYPE,
'sgv': sgv['sg'],
}
)
);
}
}

return out;
}

function transformForNightscout(data) {
var entries = [];

entries.push(pumpStatusEntry(data));

sgvEntries(data).forEach(function(entry) {
entries.push(entry);
});

entries.forEach(function(entry) {
entry['device'] = 'MiniMed Connect ' + data['medicalDeviceFamily'] + ' ' + data['medicalDeviceSerialNumber'];
});

return entries;
}

function sendToNightscout(entries, callback) {
var endpoint = config.NIGHTSCOUT_HOST + '/api/v1/entries.json';

logger.log('POST ' + endpoint + ' ' + JSON.stringify(entries));
request.post(
endpoint,
{
body: entries,
json: true,
headers: {
'api-secret': crypto.createHash('sha1').update(config.NIGHTSCOUT_API_SECRET).digest('hex')
}
},
function(err, response) {
if(err) {
console.log("Error uploading to Nightscout: can't connect to Nightscout host");
process.exit(1);
} else if(response.statusCode !== 200) {
console.log("Error uploading to Nightscout: " + JSON.stringify(response.body));
process.exit(1);
} else {
callback(entries);
}
}
);
}

function transformAndUpload(data, callback) {
var entries = transformForNightscout(data);
if(entries.length === 0) {
logger.log('No valid data found in CareLink JSON: ' + JSON.stringify(data));
return callback();
} else {
sendToNightscout(entries, callback);
}
}

return {
transformForNightscout: transformForNightscout,
sendToNightscout: sendToNightscout,
transformAndUpload: transformAndUpload
};
})();
29 changes: 29 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "minimed-connect-to-nightscout",
"version": "0.1.0",
"description": "Send Medtronic pump and CGM data to Nightscout.",
"author": "Mark Wilson <[email protected]>",
"repository": {
"type": "git",
"url": "https://github.com/mddub/minimed-connect-to-nightscout.git"
},
"bugs": {
"url": "https://github.com/mddub/minimed-connect-to-nightscout/issues"
},
"main": "index.js",
"scripts": {
"start": "node run.js"
},
"license": "MIT",
"dependencies": {
"common": "0.2.5",
"extend": "3.0.0",
"request": "2.64.0"
},
"keywords": [
"medtronic",
"minimed",
"cgm",
"nightscout"
]
}
11 changes: 11 additions & 0 deletions run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var config = require('./config'),
fetch = require('./fetch'),
nightscout = require('./nightscout');

(function requestLoop() {
fetch(function(data) {
nightscout.transformAndUpload(data, function(entries) {
setTimeout(requestLoop, config.CARELINK_REQUEST_INTERVAL);
});
});
})();
Loading

0 comments on commit 8314ff1

Please sign in to comment.