Permalink
Browse files

Move CL->NS data transformation into its own module

  • Loading branch information...
mddub committed Oct 14, 2015
1 parent 577da16 commit 437e9fa6607c601ff1f5ca8855b5eebe899997dc
Showing with 112 additions and 107 deletions.
  1. +0 −105 nightscout.js
  2. +3 −2 run.js
  3. +109 −0 transform.js
View
@@ -6,111 +6,6 @@ var crypto = require('crypto'),
var logger = require('./logger');
var STALE_DATA_THRESHOLD_MINUTES = 20;
var PUMP_STATUS_ENTRY_TYPE = 'pump_status';
var SENSOR_GLUCOSE_ENTRY_TYPE = 'sgv';
function parsePumpTime(pumpTimeString, offset) {
return Date.parse(pumpTimeString + ' ' + offset);
}
function addTimeToEntry(timestamp, entry) {
entry['date'] = timestamp;
entry['dateString'] = new Date(timestamp).toISOString();
return entry;
}
var guessPumpOffset = (function() {
var lastGuess;
// From my observations, sMedicalDeviceTime is advanced by the server even when the app is
// not reporting data or the pump is not connected, so its difference from server time is
// always close to a whole number of hours, and can be used to guess the pump's timezone:
// https://gist.github.com/mddub/f673570e6427c93784bf
return function(data) {
var pumpTimeAsIfUTC = Date.parse(data['sMedicalDeviceTime'] + ' +0');
var serverTimeUTC = data['currentServerTime'];
var hours = Math.round((pumpTimeAsIfUTC - serverTimeUTC) / (60*60*1000));
var offset = (hours >= 0 ? '+' : '-') + (Math.abs(hours) < 10 ? '0' : '') + Math.abs(hours) + '00';
if (offset !== lastGuess) {
logger.log('Guessed pump timezone ' + offset + ' (pump time: "' + data['sMedicalDeviceTime'] + '"; server time: ' + new Date(data['currentServerTime']) + ')');
}
lastGuess = offset;
return offset;
};
})();
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['lastMedicalDeviceDataUpdateServerTime'], entry);
}
function sgvEntries(data) {
var offset = guessPumpOffset(data);
if(data['sgs'] && data['sgs'].length) {
return data['sgs'].filter(function(entry) {
return entry['kind'] === 'SG' && entry['sg'] !== 0;
}).map(function(sgv) {
return addTimeToEntry(
parsePumpTime(sgv['datetime'], offset),
{
'type': SENSOR_GLUCOSE_ENTRY_TYPE,
'sgv': sgv['sg'],
}
);
});
} else {
return [];
}
}
var transform = module.exports.transform = function(data, sgvLimit) {
var recency = (data['currentServerTime'] - data['lastMedicalDeviceDataUpdateServerTime']) / (60 * 1000);
if (recency > STALE_DATA_THRESHOLD_MINUTES) {
logger.log('Stale CareLink data: ' + recency.toFixed(2) + ' minutes old');
return [];
}
if (sgvLimit === undefined) {
sgvLimit = Infinity;
}
var entries = [];
entries.push(pumpStatusEntry(data));
var sgvs = sgvEntries(data);
// TODO: this assumes sgvs are ordered by date ascending
for(var i = Math.max(0, sgvs.length - sgvLimit); i < sgvs.length; i++) {
entries.push(sgvs[i]);
}
entries.forEach(function(entry) {
entry['device'] = 'MiniMed Connect ' + data['medicalDeviceFamily'] + ' ' + data['medicalDeviceSerialNumber'];
});
return entries;
};
var upload = module.exports.upload = function(entries, endpoint, secret, callback) {
logger.log('POST ' + endpoint + ' ' + JSON.stringify(entries));
request.post(
View
5 run.js
@@ -2,7 +2,8 @@
"use strict";
var carelink = require('./carelink'),
nightscout = require('./nightscout');
nightscout = require('./nightscout'),
transform = require('./transform');
function readEnv(key, defaultVal) {
var val = process.env[key] ||
@@ -37,7 +38,7 @@ var endpoint = (config.nsBaseUrl ? config.nsBaseUrl : 'https://' + config.nsHost
if (err) {
throw new Error(err);
} else {
var entries = nightscout.transform(data, config.sgvLimit);
var entries = transform(data, config.sgvLimit);
if (entries.length > 0) {
nightscout.upload(entries, endpoint, config.nsSecret, function(err, response) {
if (err) {
View
@@ -0,0 +1,109 @@
/* jshint node: true */
"use strict";
var logger = require('./logger');
var STALE_DATA_THRESHOLD_MINUTES = 20;
var PUMP_STATUS_ENTRY_TYPE = 'pump_status';
var SENSOR_GLUCOSE_ENTRY_TYPE = 'sgv';
function parsePumpTime(pumpTimeString, offset) {
return Date.parse(pumpTimeString + ' ' + offset);
}
function addTimeToEntry(timestamp, entry) {
entry['date'] = timestamp;
entry['dateString'] = new Date(timestamp).toISOString();
return entry;
}
var guessPumpOffset = (function() {
var lastGuess;
// From my observations, sMedicalDeviceTime is advanced by the server even when the app is
// not reporting data or the pump is not connected, so its difference from server time is
// always close to a whole number of hours, and can be used to guess the pump's timezone:
// https://gist.github.com/mddub/f673570e6427c93784bf
return function(data) {
var pumpTimeAsIfUTC = Date.parse(data['sMedicalDeviceTime'] + ' +0');
var serverTimeUTC = data['currentServerTime'];
var hours = Math.round((pumpTimeAsIfUTC - serverTimeUTC) / (60*60*1000));
var offset = (hours >= 0 ? '+' : '-') + (Math.abs(hours) < 10 ? '0' : '') + Math.abs(hours) + '00';
if (offset !== lastGuess) {
logger.log('Guessed pump timezone ' + offset + ' (pump time: "' + data['sMedicalDeviceTime'] + '"; server time: ' + new Date(data['currentServerTime']) + ')');
}
lastGuess = offset;
return offset;
};
})();
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['lastMedicalDeviceDataUpdateServerTime'], entry);
}
function sgvEntries(data) {
var offset = guessPumpOffset(data);
if(data['sgs'] && data['sgs'].length) {
return data['sgs'].filter(function(entry) {
return entry['kind'] === 'SG' && entry['sg'] !== 0;
}).map(function(sgv) {
return addTimeToEntry(
parsePumpTime(sgv['datetime'], offset),
{
'type': SENSOR_GLUCOSE_ENTRY_TYPE,
'sgv': sgv['sg'],
}
);
});
} else {
return [];
}
}
var transform = module.exports = function(data, sgvLimit) {
var recency = (data['currentServerTime'] - data['lastMedicalDeviceDataUpdateServerTime']) / (60 * 1000);
if (recency > STALE_DATA_THRESHOLD_MINUTES) {
logger.log('Stale CareLink data: ' + recency.toFixed(2) + ' minutes old');
return [];
}
if (sgvLimit === undefined) {
sgvLimit = Infinity;
}
var entries = [];
entries.push(pumpStatusEntry(data));
var sgvs = sgvEntries(data);
// TODO: this assumes sgvs are ordered by date ascending
for(var i = Math.max(0, sgvs.length - sgvLimit); i < sgvs.length; i++) {
entries.push(sgvs[i]);
}
entries.forEach(function(entry) {
entry['device'] = 'MiniMed Connect ' + data['medicalDeviceFamily'] + ' ' + data['medicalDeviceSerialNumber'];
});
return entries;
};

0 comments on commit 437e9fa

Please sign in to comment.