Skip to content

Commit

Permalink
Merge pull request nightscout#2 from mddub/wip/mmconnect-more-data
Browse files Browse the repository at this point in the history
Add trend arrows from MiniMed Connect
  • Loading branch information
lcmatoush committed Oct 22, 2015
2 parents aac3871 + b657c8b commit f8924a1
Show file tree
Hide file tree
Showing 24 changed files with 740 additions and 92 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm
* `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.
* `MMCONNECT_STORE_RAW_DATA` - Set this to any truthy value to store raw data returned from CareLink as `type: "carelink_raw"` database entries (useful for development).

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

Expand Down
79 changes: 77 additions & 2 deletions lib/client/careportal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ function init (client, $) {
var storage = $.localStorage;

careportal.events = [
{ val: 'BG Check', name: 'BG Check' }
{ val: '<none>', name: '<none>' }
, { val: 'BG Check', name: 'BG Check' }
, { val: 'Snack Bolus', name: 'Snack Bolus' }
, { val: 'Meal Bolus', name: 'Meal Bolus' }
, { val: 'Correction Bolus', name: 'Correction Bolus' }
Expand All @@ -23,6 +24,8 @@ function init (client, $) {
, { val: 'Sensor Start', name: 'Dexcom Sensor Start' }
, { val: 'Sensor Change', name: 'Dexcom Sensor Change' }
, { val: 'Insulin Change', name: 'Insulin Cartridge Change' }
, { val: 'Temp Basal Start', name: 'Temp Basal Start' }
, { val: 'Temp Basal End', name: 'Temp Basal End' }
, { val: 'D.A.D. Alert', name: 'D.A.D. Alert' }
];

Expand Down Expand Up @@ -50,11 +53,70 @@ function init (client, $) {
}
}

careportal.filterInputs = function filterInputs ( event ) {
var inputMatrix = {
'<none>': { bg: true, insulin: true, carbs: true, prebolus: true, duration: false, percent: false, absolute: false }
, 'BG Check': { bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Snack Bolus': { bg: true, insulin: true, carbs: true, prebolus: true, duration: false, percent: false, absolute: false }
, 'Meal Bolus': { bg: true, insulin: true, carbs: true, prebolus: true, duration: false, percent: false, absolute: false }
, 'Correction Bolus': { bg: true, insulin: true, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Carb Correction': { bg: true, insulin: false, carbs: true, prebolus: false, duration: false, percent: false, absolute: false }
, 'Announcement': { bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Note': { bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false }
, 'Question': { bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Exercise': { bg: false, insulin: false, carbs: false, prebolus: false, duration: true, percent: false, absolute: false }
, 'Site Change': { bg: false, insulin: true, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Sensor Start': { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Sensor Change': { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Insulin Change': { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'Temp Basal Start': { bg: true, insulin: false, carbs: false, prebolus: false, duration: true, percent: true, absolute: true }
, 'Temp Basal End': { bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
, 'D.A.D. Alert': { bg: true, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false }
};

var eventType = $('#eventType').val();

function displayType (enabled) {
if (enabled) {
return '';
} else {
return 'none';
}
}

function resetIfHidden(visible, id) {
if (!visible) {
$(id).val('');
}
}

$('#bg').css('display',displayType(inputMatrix[eventType]['bg']));
$('#insulinGivenLabel').css('display',displayType(inputMatrix[eventType]['insulin']));
$('#carbsGivenLabel').css('display',displayType(inputMatrix[eventType]['carbs']));
$('#durationLabel').css('display',displayType(inputMatrix[eventType]['duration']));
$('#percentLabel').css('display',displayType(inputMatrix[eventType]['percent'] && $('#absolute').val() === ''));
$('#absoluteLabel').css('display',displayType(inputMatrix[eventType]['absolute'] && $('#percent').val() === ''));
$('#preBolusLabel').css('display',displayType(inputMatrix[eventType]['prebolus']));

resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven');
resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven');
resetIfHidden(inputMatrix[eventType]['duration'], '#duration');
resetIfHidden(inputMatrix[eventType]['absolute'], '#absolute');
resetIfHidden(inputMatrix[eventType]['percent'], '#percent');
resetIfHidden(inputMatrix[eventType]['prebolus'], '#preBolus');

maybePrevent(event);
};

careportal.prepareEvents = function prepareEvents ( ) {
$('#eventType').empty();
_.each(careportal.events, function eachEvent(event) {
$('#eventType').append('<option value="' + event.val+ '">' + translate(event.name) + '</option>');
});
$('#eventType').change(careportal.filterInputs);
$('#percentLabel').change(careportal.filterInputs);
$('#absoluteLabel').change(careportal.filterInputs);
careportal.filterInputs();
};

careportal.resolveEventName = function resolveEventName(value) {
Expand All @@ -68,11 +130,14 @@ function init (client, $) {

careportal.prepare = function prepare ( ) {
careportal.prepareEvents();
$('#eventType').val('BG Check');
$('#eventType').val('<none>');
$('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units);
$('#meter').prop('checked', true);
$('#carbsGiven').val('');
$('#insulinGiven').val('');
$('#duration').val('');
$('#percent').val('');
$('#absolute').val('');
$('#preBolus').val(0);
$('#notes').val('');
$('#enteredBy').val(storage.get('enteredBy') || '');
Expand All @@ -88,6 +153,9 @@ function init (client, $) {
, glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val()
, carbs: $('#carbsGiven').val()
, insulin: $('#insulinGiven').val()
, duration: $('#duration').val()
, percent: $('#percent').val()
, absolute: $('#absolute').val()
, preBolus: parseInt($('#preBolus').val())
, notes: $('#notes').val()
, units: client.settings.units
Expand All @@ -96,6 +164,10 @@ function init (client, $) {
if ($('#othertime').is(':checked')) {
data.eventTime = mergeDateAndTime().toDate();
}

if (data.eventType.indexOf('Temp Basal') > -1) {
data.eventType = 'Temp Basal';
}

return data;
}
Expand Down Expand Up @@ -123,6 +195,9 @@ function init (client, $) {

pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs);
pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin);
pushIf(data.duration, translate('Duration') + ': ' + data.duration);
pushIf(data.percent, translate('Percent') + ': ' + data.percent);
pushIf(data.absolute, translate('Basal value') + ': ' + data.absolute);
pushIf(data.preBolus, translate('Carb Time') + ': ' + data.preBolus + ' ' + translate('mins'));
pushIf(data.notes, translate('Notes') + ': ' + data.notes);
pushIf(data.enteredBy, translate('Entered By') + ': ' + data.enteredBy);
Expand Down
57 changes: 46 additions & 11 deletions lib/client/chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ function init (client, d3, $) {
var yScale2 = chart.yScale2 = d3.scale.log()
.domain([utils.scaleMgdl(36), utils.scaleMgdl(420)]);

chart.xScaleBasals = d3.time.scale().domain(extent);

chart.yScaleBasals = d3.scale.linear()
.domain([0, 5]);

var tickFormat = localeFormatter.timeFormat.multi( [
['.%L', function(d) { return d.getMilliseconds(); }],
[':%S', function(d) { return d.getSeconds(); }],
Expand Down Expand Up @@ -103,6 +108,13 @@ function init (client, d3, $) {
.tickValues(tickValues)
.orient('right');

// chart.yAxisBasals = d3.svg.axis()
// .scale(yScaleBasals)
// .tickFormat(d3.format('d'))
//.tickValues(tickValues)
// .ticks(4)
// .orient('right');

// setup a brush
chart.brush = d3.svg.brush()
.x(xScale2)
Expand All @@ -119,7 +131,7 @@ function init (client, d3, $) {
.append('g')
.attr('class', 'chartContainer');

chart.focus = chart.charts.append('g');
chart.focus = chart.charts.append('g').attr('class', 'chart-focus');

// create the x axis container
chart.focus.append('g')
Expand All @@ -129,7 +141,7 @@ function init (client, d3, $) {
chart.focus.append('g')
.attr('class', 'y axis');

chart.context = chart.charts.append('g');
chart.context = chart.charts.append('g').attr('class', 'chart-context');

// create the x axis container
chart.context.append('g')
Expand All @@ -139,6 +151,13 @@ function init (client, d3, $) {
chart.context.append('g')
.attr('class', 'y axis');

chart.basals = chart.charts.append('g').attr('class', 'chart-basals');
chart.basals.attr('display','none');

// create the y axis container
// chart.basals.append('g')
// .attr('class', 'y axis');

function createAdjustedRange() {
var range = chart.brush.extent().slice();

Expand Down Expand Up @@ -187,6 +206,7 @@ function init (client, d3, $) {
// get the height of each chart based on its container size ratio
var focusHeight = chart.focusHeight = chartHeight * .7;
var contextHeight = chart.contextHeight = chartHeight * .2;
chart.basalsHeight = focusHeight / 4;

// get current brush extent
var currentBrushExtent = createAdjustedRange();
Expand All @@ -204,8 +224,10 @@ function init (client, d3, $) {
// ranges are based on the width and height available so reset
chart.xScale.range([0, chartWidth]);
chart.xScale2.range([0, chartWidth]);
chart.xScaleBasals.range([0, chartWidth]);
chart.yScale.range([focusHeight, 0]);
chart.yScale2.range([chartHeight, chartHeight - contextHeight]);
chart.yScaleBasals.range([0, focusHeight / 4]);

if (init) {

Expand All @@ -223,6 +245,10 @@ function init (client, d3, $) {
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(chart.xAxis2);

// chart.basals.select('.y')
// .attr('transform', 'translate(0,' + 0 + ')')
// .call(chart.yAxisBasals);

chart.context.append('g')
.attr('class', 'x brush')
.call(d3.svg.brush().x(chart.xScale2).on('brush', client.brushed))
Expand Down Expand Up @@ -294,18 +320,18 @@ function init (client, d3, $) {
.attr('stroke', '#777');

// add a y-axis line that opens up the brush extent from the context to the focus
chart.focus.append('line')
chart.context.append('line')
.attr('class', 'open-top')
.attr('stroke', '#111')
.attr('stroke-width', 15);

// add a x-axis line that closes the the brush container on left side
chart.focus.append('line')
chart.context.append('line')
.attr('class', 'open-left')
.attr('stroke', 'white');

// add a x-axis line that closes the the brush container on right side
chart.focus.append('line')
chart.context.append('line')
.attr('class', 'open-right')
.attr('stroke', 'white');

Expand Down Expand Up @@ -358,6 +384,12 @@ function init (client, d3, $) {
.attr('transform', 'translate(0,' + chartHeight + ')')
.call(chart.xAxis2);

chart.basals.transition();

// basalsTransition.select('.y')
// .attr('transform', 'translate(0,' + 0 + ')')
// .call(chart.yAxisBasals);

if (chart.clip) {
// reset clip to new dimensions
chart.clip.transition()
Expand Down Expand Up @@ -407,23 +439,23 @@ function init (client, d3, $) {
.attr('y2', chart.yScale(utils.scaleMgdl(client.settings.thresholds.bgLow)));

// transition open-top line to correct location
chart.focus.select('.open-top')
chart.context.select('.open-top')
.transition()
.attr('x1', chart.xScale2(currentBrushExtent[0]))
.attr('y1', chart.yScale(utils.scaleMgdl(30)))
.attr('x2', chart.xScale2(currentBrushExtent[1]))
.attr('y2', chart.yScale(utils.scaleMgdl(30)));

// transition open-left line to correct location
chart.focus.select('.open-left')
chart.context.select('.open-left')
.transition()
.attr('x1', chart.xScale2(currentBrushExtent[0]))
.attr('y1', focusHeight)
.attr('x2', chart.xScale2(currentBrushExtent[0]))
.attr('y2', chartHeight);

// transition open-right line to correct location
chart.focus.select('.open-right')
chart.context.select('.open-right')
.transition()
.attr('x1', chart.xScale2(currentBrushExtent[1]))
.attr('y1', focusHeight)
Expand All @@ -450,6 +482,7 @@ function init (client, d3, $) {

// update domain
chart.xScale2.domain(dataRange);
chart.xScaleBasals.domain(dataRange);

var updateBrush = d3.select('.brush').transition();
updateBrush
Expand All @@ -465,26 +498,27 @@ function init (client, d3, $) {

chart.scroll = function scroll (nowDate) {
chart.xScale.domain(createAdjustedRange());
chart.xScaleBasals.domain(createAdjustedRange());

// remove all insulin/carb treatment bubbles so that they can be redrawn to correct location
d3.selectAll('.path').remove();

// transition open-top line to correct location
chart.focus.select('.open-top')
chart.context.select('.open-top')
.attr('x1', chart.xScale2(chart.brush.extent()[0]))
.attr('y1', chart.yScale(utils.scaleMgdl(30)))
.attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime)))
.attr('y2', chart.yScale(utils.scaleMgdl(30)));

// transition open-left line to correct location
chart.focus.select('.open-left')
chart.context.select('.open-left')
.attr('x1', chart.xScale2(chart.brush.extent()[0]))
.attr('y1', chart.focusHeight)
.attr('x2', chart.xScale2(chart.brush.extent()[0]))
.attr('y2', chart.prevChartHeight);

// transition open-right line to correct location
chart.focus.select('.open-right')
chart.context.select('.open-right')
.attr('x1', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime)))
.attr('y1', chart.focusHeight)
.attr('x2', chart.xScale2(new Date(chart.brush.extent()[1].getTime() + client.forecastTime)))
Expand All @@ -509,6 +543,7 @@ function init (client, d3, $) {

renderer.addFocusCircles();
renderer.addTreatmentCircles();
renderer.addBasals(client);

// add treatment bubbles
chart.focus.selectAll('circle')
Expand Down
37 changes: 36 additions & 1 deletion lib/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,6 @@ client.init = function init(serverSettings, plugins) {
$('#treatmentDrawerToggle').toggle(serverSettings.careportalEnabled);
container.toggleClass('has-minor-pills', plugins.hasShownType('pill-minor', client.settings));


function prepareData ( ) {
// Post processing after data is in
var temp1 = [ ];
Expand Down Expand Up @@ -810,6 +809,42 @@ client.init = function init(serverSettings, plugins) {
MBGdata = mergeDataUpdate(d.delta,MBGdata, d.mbgs);
client.treatments = mergeDataUpdate(d.delta, client.treatments, d.treatments);

// filter & prepare temp basals
var tempbasaltreatments = client.treatments.filter( function filterBasals(t) {
return t.eventType.indexOf('Temp Basal') > -1;
});
// cut temp basals by end events
// better to do it only on data update
var endevents = tempbasaltreatments.filter(function filterEnd(t) {
return ! t.duration;
});

function cutIfInInterval(base, end) {
if (base.mills < end.mills && base.mills + times.mins(base.duration).msecs > end.mills) {
base.duration = times.msecs(end.mills-base.mills).mins;
}
}

// cut by end events
tempbasaltreatments.forEach(function allTreatments(t) {
endevents.forEach(function allEndevents(e) {
cutIfInInterval(t, e);
});
});

// cut by overlaping events
tempbasaltreatments.forEach(function allTreatments(t) {
tempbasaltreatments.forEach(function allEndevents(e) {
cutIfInInterval(t, e);
});
});

// store prepared temp basal treatments
client.tempbasaltreatments = tempbasaltreatments.filter(function filterEnd(t) {
return t.duration;
});


// Do some reporting on the console
console.log('Total SGV data size', SGVdata.length);
console.log('Total treatment data size', client.treatments.length);
Expand Down
Loading

0 comments on commit f8924a1

Please sign in to comment.