diff --git a/README.markdown b/README.markdown
index 83affef..c666d35 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,5 +1,7 @@
mmcsv
===========
+This is the ORIGINAL README file for Ben West's mmcsv repo. It should all still work if needed, hence I've left this in
+Instructions on mmcsv640g will go up soon :-)
Scraper and Parser for Medtronic pump, cgb and connected bg meter data.
diff --git a/Release.md b/Release.md
deleted file mode 100644
index cb37c7c..0000000
--- a/Release.md
+++ /dev/null
@@ -1,8 +0,0 @@
-
-v0.0.3 / 2014-02-27
-==================
-
- * make basals in desalinate stream match expectations
- * Merge pull request #21 from tidepool-org/release/v0.0.2
-
- This will help `pool-whisperer` product rate segments correctly.
diff --git a/config.sh b/config.sh
new file mode 100644
index 0000000..29ca799
--- /dev/null
+++ b/config.sh
@@ -0,0 +1,36 @@
+#! /bin/bash
+# Initialising Carelink Automation
+# Proof of concept ONLY - 640g csv to NightScout
+#
+# ****************************************************************************************
+# USER SPECIFIC Working Directories - Please enter your details here
+# ****************************************************************************************
+# ExamplePath='/c/Data/Path'
+# NB NO SPACES anywhere here, unless you know what you're doing please - Welcome to Shell Scripting :-)
+Mmcsv640gPath='/c/Users/matt/Nightscout/csv/mmcsv' #Installation Directory for mmscv640g stack
+CSVDataPath='/c/Users/matt/AutoCSV' # The directory you want to throw all the data around in
+DownloadPath='/c/Users/matt/Downloads' # Where your CSV file downloaded from CareLink will appear (without any IMPORTANT CSV files in it!)
+NodejsPath='/c/Program Files/nodejs' # Where Nodejs installed on your system
+MousePath='/c/Users/matt/Nightscout' #Where MiniMouseMacro is installed
+# ****************************************************************************************
+# USER SPECIFIC Variables - Please enter your values here
+# ****************************************************************************************
+api_secret_hash='2ce212ef676099da17ec5aff64db0c83bf3f7b4f' # This is the SHA-1 Hash of your API-SECRET string - eg "ASANEXAMPLE1" is transformed into...
+your_nightscout='https://yourwebsite.azurewebsites.net' #'https://something.azurewebsites.net'
+gap_mins=5 # max time to wait for CSV download. Suggest 5 or 10 minutes and always start higher
+gap_mins_delay=0 # use strict time for each upload cycle (if >0) or don't wait to minimise latency (0)
+# ****************************************************************************************
+# USER SPECIFIC Uploader Info - Please enter your values here
+# ****************************************************************************************
+uploader=1 #Firefox + Selenium (0) or .NET uploader (1): default is 1 from version 0.8 onwards
+CareLinkURL='https://carelink.minimed.eu' #CareLink site, .eu or .com
+CareLinkUsername='user' #CareLink Username
+CareLinkPassword='password' #CareLink Password
+# ****************************************************************************************
+# AUTOMATICALLY GENERATED Variables - No need to edit these yourself
+# ****************************************************************************************
+carelink_dateformat='dd/MM/yyyy' # Website date format from CareLink
+carelink_customerID='' # Customer ID from CareLink
+carelink_locale='en_GB' # Locale from CareLink
+carelink_timestamp='DD/MM/YYTHH:mm:ss' # CSV time and date format for CareLink locale
+#
\ No newline at end of file
diff --git a/depreciated/Selenium_Upload_Example.html b/depreciated/Selenium_Upload_Example.html
new file mode 100644
index 0000000..95d38f2
--- /dev/null
+++ b/depreciated/Selenium_Upload_Example.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+Upload_Only
+
+
+
+
+Upload_Only
+
+
+ open
+ /patient/signout.do
+
+
+
+ waitForPageToLoad
+
+
+
+
+ open
+ /patient/entry.jsp?bhcp=1
+
+
+
+ waitForPageToLoad
+
+
+
+
+ type
+ id=j_username
+ YOURUSERNAME
+
+
+ type
+ id=j_password
+ YOURPASSWORD
+
+
+ clickAndWait
+ id=loginButton
+
+
+
+ click
+ link=Upload Data from My Device
+
+
+
+ waitForPageToLoad
+
+
+
+
+ pause
+ 180000
+ 180000
+
+
+ clickAndWait
+ id=toReports
+
+
+
+ click
+ link=Data Export (CSV)
+
+
+
+ selectWindow
+ null
+
+
+
+ clickAndWait
+ css=#reportPicker11 > span.reportNav_right > #reportNav_button
+
+
+
+
+
diff --git a/depreciated/mmcsv640g.mmmacro b/depreciated/mmcsv640g.mmmacro
new file mode 100644
index 0000000..4400e0c
--- /dev/null
+++ b/depreciated/mmcsv640g.mmmacro
@@ -0,0 +1,5 @@
+1 | 612 | 663 | 2999 | Left Click Down
+
+2 | 612 | 663 | 104 | Left Click Release
+3 | 610 | 610 | 2999 | Left Click Down
+4 | 610 | 610 | 104 | Left Click Release
diff --git a/lib/parsers/basal.js b/lib/parsers/basal.js
index c205024..31b273b 100644
--- a/lib/parsers/basal.js
+++ b/lib/parsers/basal.js
@@ -2,27 +2,46 @@
module.exports = function configure (utils) {
function parse (row, callback) {
- var details = utils.details(utils.select(row, 'Raw-Values') || '');
- if (!details.RATE) {
- return callback( );
- }
+ // Using careportal "Temp" basal to add values of SCHEDULED basal change (not perfect I know...)
+ // Using this will over-ride your basal PROFILE set in Nightscout...
+ //'Temp Basal Start': { bg: true, duration: true, percent: true, absolute: true }
+ // Requires careportal plugin in Nightscout
+ // Just picking out SCHEDULED basal changes here
+ // eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ // Note Temp Basal event type (not start / end)
+ // Raw Values are BasalProfileStart at start / on
+ // Absolute values (units per hour) only in this script
+ // Duration is preset to 24 hours - ie tees this up until next rate is spotted...
+ // NOT used at present...
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+ var values = utils.details(utils.select(row, 'Raw-Values') || '');
+
+ if (value == 'BasalProfileStart') {
var data = {
- scheduleName: details.PATTERN_NAME,
- value: parseFloat(details.RATE),
- type: 'basal-rate-change',
- deliveryType: 'scheduled',
- deviceTime: utils.reformatISO(utils.select(row, 'Timestamp'))
- };
+ value: values.RATE,
+ eventType: 'Temp Basal',
+ duration: parseInt(1440),
+ absolute: parseFloat(values.RATE),
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ } ;
+
return callback(null, data);
}
function isValid (data) {
- return (!isNaN(data.value) && data.type == 'basal-rate-change');
+ return data.value || false;
}
-
- var pattern = /BasalProfileStart/g;
+
+ var pattern = /ChangeProgrammedTempBasal|ChangeTempBasal/g;
var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
var parser = { pattern: pattern, stream: stream, parse: parse };
return parser;
+
}
diff --git a/lib/parsers/basal_temp_absolute.js b/lib/parsers/basal_temp_absolute.js
new file mode 100644
index 0000000..1acb28b
--- /dev/null
+++ b/lib/parsers/basal_temp_absolute.js
@@ -0,0 +1,55 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ //'Temp Basal Start': { bg: true, duration: true, percent: true, absolute: true }
+ // 'Temp Basal End': { bg: true}
+ // Adapted to push in temporary basals
+ // Requires careportal plugin in Nightscout
+ // Currently just picking out manual temp basals, not smartguard (PLGM) - see plgm.js
+ // eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ // Note Temp Basal event type (not start / end)
+ // Raw Values are ChangeProgrammedTempBasalPercent at start / on
+ // Raw Values are ChangeTempBasalPercent at off /cancelation
+ // Absolute change only in this script
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+
+ var values = utils.details(utils.select(row, 'Raw-Values') || '');
+
+ if (value == 'ChangeProgrammedTempBasal') {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ duration: parseInt((values.DURATION)/(60.*1000.)),
+ absolute: parseFloat(values.RATE),
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ }
+ else {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /ChangeProgrammedTempBasal|ChangeTempBasal/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+
+}
diff --git a/lib/parsers/basal_temp_percent.js b/lib/parsers/basal_temp_percent.js
new file mode 100644
index 0000000..51d92f4
--- /dev/null
+++ b/lib/parsers/basal_temp_percent.js
@@ -0,0 +1,57 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ //'Temp Basal Start': { bg: true, duration: true, percent: true, absolute: true }
+ // 'Temp Basal End': { bg: true}
+ // Adapted to push in temporary basals
+ // Requires careportal plugin in Nightscout
+ // Currently just picking out manual temp basals, not smartguard (PLGM) - see plgm.js
+ // eventTime: 'hh:mm', 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ // Medtronic % change is % of original ie 0% = 0units, 150% = 1.5 units / hr on 1.0 unit/hr basal
+ // Nightscout % change is % change ie 0% = 1unit/hr (no change), -100% = 0 units/hr; convert from Medtronic to NS by -100
+ // Note Temp Basal event type (not start / end)
+ // Raw Values are ChangeProgrammedTempBasalPercent at start / on
+ // Raw Values are ChangeTempBasalPercent at off /cancelation
+ // Percent change only in this script
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+
+ var values = utils.details(utils.select(row, 'Raw-Values') || '');
+
+ if (value == 'ChangeProgrammedTempBasalPercent') {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ duration: parseInt((values.DURATION)/(60.*1000.)),
+ percent: parseInt(values.PERCENT_OF_RATE - 100),
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ }
+ else {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /ChangeProgrammedTempBasalPercent|ChangeTempBasalPercent/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+
+}
diff --git a/lib/parsers/battery.js b/lib/parsers/battery.js
new file mode 100644
index 0000000..f496d53
--- /dev/null
+++ b/lib/parsers/battery.js
@@ -0,0 +1,34 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ // Dummy - Not used at the mo...
+
+ // eventTime: 'hh:mm', 'YYYY-MM-DDTHH:mm:sssZ'
+
+ var details = utils.pluck(row, ['Prime Type', 'Timestamp']);
+ var value = details['Prime Type'];
+
+ if (value == 'Fill Canula') {
+ var data = {
+ value: value,
+ eventType: 'Insulin Change',
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /Prime/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/bolus.js b/lib/parsers/bolus.js
index f6b842c..7b52ee1 100644
--- a/lib/parsers/bolus.js
+++ b/lib/parsers/bolus.js
@@ -2,6 +2,17 @@ module.exports = function configure (utils) {
function exists (d) { return d; }
function lower (d) { return d.toLowerCase( ); }
+// CarePortal adjustments
+// Bolus will handle insulin delivery ONLY
+// Wizard will handle planned delivery, carbs and estimate
+// ie both wizard and bolus need to be called
+// eventType = ("Correction Bolus"), "Meal Bolus", ("Snack Bolus")
+// eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+// bg, carbs not used here!
+// insulin used
+// notes used to record if all planned bolus was delivered (or cancelled eg due to PLGM) - CHECK THIS ON THE PUMP OF COURSE!
+// rest are left for info and possible future use
+
function parse (row, callback) {
var keys = [ 'Bolus Type', 'Timestamp', 'Raw-Values',
'Raw-Upload ID', 'Index', 'Raw-Device Type' ];
@@ -11,17 +22,33 @@ module.exports = function configure (utils) {
var details = utils.details(fields['Raw-Values'] || '');
var delivered = details.AMOUNT;
var programmed = details.PROGRAMMED_AMOUNT;
- var duration = details.DURATION;
+ var gap_value = (programmed - delivered).toFixed(3); // ie to capture if anything was lost in eg pump suspend or other pump error...
+ var gap_message = "Delivered";
+ if (gap_value > 0) {
+ gap_message = gap_value.toString() + "U CANCELLED";
+ }
+ var duration = details.DURATION;
+ duration_mins = (duration / 60000.).toFixed(); // in minutes
+ var enteredText = 'Now';
+ if (lower(fields['Bolus Type'] || '') == 'dual/square') {
+ enteredText = (duration_mins).toString() + "mins ago";
+ }
var data = {
value: delivered,
- bolus: parseFloat(delivered),
+ insulin: parseFloat(delivered),
programmed: parseFloat(programmed),
type: 'bolus',
subType: lower(fields['Bolus Type'] || ''),
- deviceTime: utils.reformatISO(fields.Timestamp)
+ eventType: 'Meal Bolus',
+ enteredBy: enteredText,
+ notes: gap_message,
+ dateString: utils.reformatISO(fields.Timestamp),
+ created_at: utils.reformatCPTime(fields.Timestamp),
+ date: utils.reformatDate(fields.Timestamp)
+
};
if (duration) {
- data.duration = duration;
+ data.duration = parseInt(duration_mins);
}
if (details.IS_DUAL_COMPONENT === 'true') {
if (data.subType == 'dual/square') {
diff --git a/lib/parsers/cbg.js b/lib/parsers/cbg.js
index 070c744..b19ee22 100644
--- a/lib/parsers/cbg.js
+++ b/lib/parsers/cbg.js
@@ -1,13 +1,23 @@
module.exports = function configure (utils) {
+ // changed cbg to sgv for NS compatibility
+ // changed Devicetime to dateString
+ // added date
+ // changed to use GlucoseSensorData and AMOUNT (this is always mg/dl regardless of CareLink preference setting)
function parse (row, callback) {
- var o = utils.pluck(row, ['Sensor Glucose (mg/dL)', 'Timestamp']);
- var value = o['Sensor Glucose (mg/dL)'];
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+ var values = utils.details(utils.select(row, 'Raw-Values') || '');
+
+ if (value == 'GlucoseSensorData') {
var data = {
- value: value,
- cbg: parseInt(value),
- type: 'cbg',
- deviceTime: utils.reformatISO(o['Timestamp'])
- };
+ value: values.AMOUNT,
+ sgv: parseInt(values.AMOUNT),
+ type: 'sgv',
+ dateString: utils.reformatISO(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
return callback(null, data);
}
diff --git a/lib/parsers/index.js b/lib/parsers/index.js
index 54a54f6..17f0ab1 100644
--- a/lib/parsers/index.js
+++ b/lib/parsers/index.js
@@ -2,6 +2,15 @@
var es = require('event-stream');
// explicitly require each parser module, for the benefit of tools like
// browserify
+// Added plgm (predicted low glucose management, Smartguard)
+// Added basal_temp_percent
+// Added basal_temp_absolute
+// Added insulin (change)
+// Added sensor_start
+// Added battery (pump battery)
+// Added medpredict (Medtronic Predicted Value + 30 mins)
+// Added pump_alarms
+
var parsers = {
smbg: require('./smbg')
, cbg: require('./cbg')
@@ -9,9 +18,17 @@ var parsers = {
, carbs: require('./carbs')
, basal: require('./basal')
, wizard: require('./wizard')
+ , plgm: require('./plgm')
+ , basal_temp_percent: require('./basal_temp_percent')
+ , basal_temp_absolute: require('./basal_temp_absolute')
+ , insulin: require('./insulin')
+ , sensor_start: require('./sensor_start')
+ , battery: require('./battery')
+ , medpredict: require('./medpredict')
+ , pump_alarms: require('./pump_alarms')
};
-var TYPES = ['smbg', 'cbg', 'bolus', 'carbs', 'basal', 'wizard' ];
+var TYPES = ['smbg', 'cbg', 'bolus', 'carbs', 'basal', 'wizard', 'plgm', 'basal_temp_percent', 'basal_temp_absolute', 'insulin', 'sensor_start', 'battery','medpredict', 'pump_alarms'];
function init ( ) {
var stream = es.pipeline(es.split(), es.map(iter));
function iter (data, callback) {
diff --git a/lib/parsers/insulin.js b/lib/parsers/insulin.js
new file mode 100644
index 0000000..1d135de
--- /dev/null
+++ b/lib/parsers/insulin.js
@@ -0,0 +1,35 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ // Logs change of insulin
+ // Requires careportal plugin in Nightscout
+
+ // eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+
+ var details = utils.pluck(row, ['Prime Type', 'Timestamp']);
+ var value = details['Prime Type'];
+
+ if (value == 'Fill Cannula') {
+ var data = {
+ value: value,
+ eventType: 'Site Change',
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /Prime/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/medpredict.js b/lib/parsers/medpredict.js
new file mode 100644
index 0000000..f239e4a
--- /dev/null
+++ b/lib/parsers/medpredict.js
@@ -0,0 +1,41 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ // Place Medtronic's Predicted SG value in mmol/L (I know, I know...) to NS as a notes
+ // placing 5 minute note, 25 minutes in advance (so that it's visible)
+ // should represent Medtronic's estimate of SG in 30 minutes time post Timestamp
+ // Requires careportal plugin in Nightscout
+ // eventTime: 'hh:mm', 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+
+ var moment = require('moment');
+ var fields = utils.pluck(row, ['Raw-Values', 'Timestamp']);
+ var details = utils.details(fields['Raw-Values'] || '');
+ var predicted_mgdl = details.PREDICTED_SENSOR_GLUCOSE_AMOUNT;
+ predicted_value = (predicted_mgdl / 18.1).toFixed(1);
+ var current_date = utils.reformatDate(fields['Timestamp']);
+ new_date = current_date + 25.*60. * 1000.;
+ var new_m = moment(new_date);
+ var data = {
+ value: predicted_mgdl, // in mg/dL
+ eventType: 'Note',
+ enteredBy: 'CSV',
+ duration: 5,
+ notes: predicted_value.toString(), // Predicted Value in mmol/L
+ dateString: new_m.format('YYYY-MM-DDTHH:mm:ss'),
+ created_at: new_m.format('YYYY-MM-DDTHH:mm:ss.SSSZ'),
+ date: new_date
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return (!isNaN(data.value)) || false;
+ }
+
+ var pattern = /GlucoseSensorData/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/plgm.js b/lib/parsers/plgm.js
new file mode 100644
index 0000000..3342119
--- /dev/null
+++ b/lib/parsers/plgm.js
@@ -0,0 +1,49 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ //'Temp Basal Start': { bg: true, duration: true, percent: true, absolute: true }
+ // , 'Temp Basal End': { bg: true}
+ // Adapted to push in temporary basals
+ // Requires careportal plugin in Nightscout
+ // Currently just picking out smartguard (PLGM) OR any other reasons for suspend eg battery, reservoir...
+ // eventTime: 'hh:mm', 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ // Note Temp Basal event type (not start / end)
+
+ var details = utils.pluck(row, ['Suspend', 'Timestamp']);
+ var value = details['Suspend'];
+
+ if (value == 'Resume') {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ }
+ else {
+ var data = {
+ value: value,
+ eventType: 'Temp Basal',
+ duration: 120,
+ absolute: 0,
+ enteredBy: 'PLGM',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /ChangeSuspendState/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/pump_alarms.js b/lib/parsers/pump_alarms.js
new file mode 100644
index 0000000..a14b2bc
--- /dev/null
+++ b/lib/parsers/pump_alarms.js
@@ -0,0 +1,33 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ // Flag all pump alarms on NS (this will need some way of selecting out what the user wants...)
+ // Requires careportal plugin in Nightscout
+ // eventTime: 'hh:mm', 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+
+ var details = utils.pluck(row, ['Alarm', 'Timestamp']);
+ var value = details['Alarm'];
+
+ var data = {
+ value: value,
+ eventType: 'Announcement',
+ enteredBy: 'CSV',
+ notes: value,
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /AlarmPumpNGP/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/sensor_start.js b/lib/parsers/sensor_start.js
new file mode 100644
index 0000000..81affcf
--- /dev/null
+++ b/lib/parsers/sensor_start.js
@@ -0,0 +1,35 @@
+
+module.exports = function configure (utils) {
+ function parse (row, callback) {
+
+ // Logs change of insulin
+ // Requires careportal plugin in Nightscout
+ // SensorReplaced string in Raw-Type for new sensor (not restarted)
+ // eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+
+ if (value == 'SensorReplaced') {
+ var data = {
+ value: value,
+ eventType: 'Sensor Start',
+ enteredBy: 'CSV',
+ dateString: utils.reformatISO(details['Timestamp']),
+ created_at: utils.reformatCPTime(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
+ }
+
+ function isValid (data) {
+ return data.value || false;
+ }
+
+ var pattern = /SensorReplaced/g;
+ var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
+ var parser = { pattern: pattern, stream: stream, parse: parse };
+ return parser;
+}
diff --git a/lib/parsers/smbg.js b/lib/parsers/smbg.js
index 0f03ad2..46c7684 100644
--- a/lib/parsers/smbg.js
+++ b/lib/parsers/smbg.js
@@ -1,33 +1,34 @@
-
module.exports = function configure (utils) {
-
+ // changed smbg to mbg for NS compatibility
+ // changed Devicetime to dateString
+ // added date
+ // changed to use BGCapturedOnPump and AMOUNT (this is always mg/dl regardless of CareLink preference setting)
+
function parse (row, callback) {
- var bg = utils.select(row, 'BG Reading (mg/dL)');
- if (isNaN(parseFloat(bg))) {
- bg = utils.select(row, 'Sensor Calibration BG (mg/dL)');
- }
-
- if (bg) {
-
- var data = {
- value: bg,
- type: 'smbg',
- deviceTime: utils.reformatISO(utils.select(row, 'Timestamp'))
- };
- return callback(null, data);
- }
- return callback( );
-
+
+ var details = utils.pluck(row, ['Raw-Type', 'Timestamp']);
+ var value = details['Raw-Type'];
+ var values = utils.details(utils.select(row, 'Raw-Values') || '');
+
+ if (value == 'BGCapturedOnPump') {
+ var data = {
+ value: values.AMOUNT,
+ mbg: parseInt(values.AMOUNT),
+ type: 'mbg',
+ dateString: utils.reformatISO(details['Timestamp']),
+ date: utils.reformatDate(details['Timestamp'])
+ }
+ };
+
+ return callback(null, data);
}
-
function isValid (data) {
- return (data && !isNaN(data.value) && data.type == 'smbg');
+ return data.value || false;
}
- var pattern = /CalBGForPH/g;
+ var pattern = /BGCapturedOnPump/g;
var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
var parser = { pattern: pattern, stream: stream, parse: parse };
return parser;
-}
-
+}
\ No newline at end of file
diff --git a/lib/parsers/wizard.js b/lib/parsers/wizard.js
index 5ba16a5..007ff75 100644
--- a/lib/parsers/wizard.js
+++ b/lib/parsers/wizard.js
@@ -1,5 +1,16 @@
module.exports = function configure (utils) {
+// CarePortal adjustments
+// Wizard will handle BG and carb info ONLY
+// bolus will handle actual *delivered* insulin
+// ie both wizard and bolus need to be called
+// eventType = "Correction Bolus", "Meal Bolus", ("Snack Bolus")
+// eventTime: 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+// bg, carbs used
+// insulin not used here!
+// rest are left for info and possible future use
+
+
function parse (row, callback) {
var keys = [ 'Timestamp', 'Raw-Values',
'Raw-Upload ID', 'Index', 'Raw-Device Type' ];
@@ -8,31 +19,39 @@ module.exports = function configure (utils) {
var device = fields['Raw-Device Type'];
var index = parseInt(fields.Index) + 1;
var key = [ fields['Raw-Upload ID'], index, device ].join(' ');
- var data = {
- value: details.BOLUS_ESTIMATE
- , smbg: details.BG_INPUT
- , carbs: details.CARB_INPUT
- , carb_units: details.CARB_UNITS
- , carb_ratio: details.CARB_RATIO
- , sensitivity: details.INSULIN_SENSITIVITY
- , recommended: details.BOLUS_ESTIMATE
- , correction: details.CORRECTION_ESTIMATE
- , food: details.FOOD_ESTIMATE
- , joinKey: utils.hash(key)
- , type: 'wizard'
- , deviceTime: utils.reformatISO(fields.Timestamp)
- };
-
- return callback(null, data);
- }
+
+ if (details.CARB_INPUT != '') {
+ var data = {
+ value: details.BOLUS_ESTIMATE
+ , bg: details.BG_INPUT
+ , carbs: details.CARB_INPUT
+ , carb_units: details.CARB_UNITS
+ , carb_ratio: details.CARB_RATIO
+ , sensitivity: details.INSULIN_SENSITIVITY
+ , recommended: details.BOLUS_ESTIMATE
+ , correction: details.CORRECTION_ESTIMATE
+ , food: details.FOOD_ESTIMATE
+ , joinKey: utils.hash(key)
+ , type: 'wizard'
+ , eventType: 'Meal Bolus'
+ , enteredBy: 'CSV'
+ , dateString: utils.reformatISO(fields.Timestamp)
+ , created_at: utils.reformatCPTime(fields.Timestamp)
+ , date: utils.reformatDate(fields.Timestamp)
+ }
+ };
+ return callback(null, data);
+ }
+
function isValid (data) {
return (
!isNaN(data.value)
&& data.type == "wizard"
);
}
- var pattern = /BolusWizard/g;
+ // changed pattern from BolusWizard
+ var pattern = /BolusWizardBolusEstimate/g;
var stream = utils.pipeline(utils.split( ), utils.map(parse), utils.validator(isValid));
var parser = { pattern: pattern, stream: stream, parse: parse };
return parser;
diff --git a/lib/utils.js b/lib/utils.js
index 1d8d477..b540e0a 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -13,21 +13,51 @@ function validator (valid) {
}
return es.map(each);
}
-
-var CARELINK_TIME = 'MM/DD/YYTHH:mm:ss';
+// changed date format from MM/DD/YYTHH:mm:ss
+var CARELINK_TIME = 'DD/MM/YYTHH:mm:ss';
var OUTPUT_TIME_MASK = 'YYYY-MM-DDTHH:mm:ss';
+
+//Added for Careportal in NS
+var OUTPUT_CAREPORTAL_TIME = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
+var OUTPUT_CAREPORTAL_DATE = 'YYYY-MM-DD';
+
function reformatISO (str) {
- var m = moment(str, CARELINK_TIME);
- return m.format(OUTPUT_TIME_MASK);
+ var m = moment(str, CARELINK_TIME);
+ return m.format(OUTPUT_TIME_MASK);
+}
+
+// added reformatDate fn
+function reformatDate (str) {
+ var m = moment(str, CARELINK_TIME);
+ return m.valueOf();
+}
+
+// added reformatCPDate fn
+function reformatCPDate (str) {
+ var m = moment(str, CARELINK_TIME);
+ return m.format(OUTPUT_CAREPORTAL_DATE);
+}
+
+// added reformatCPTime fn
+function reformatCPTime (str) {
+ var m = moment(str, CARELINK_TIME);
+ return m.format(OUTPUT_CAREPORTAL_TIME);
+}
+
+// added reformatCL fn
+function reformatCL (str) {
+ var m = moment(str);
+ return m.format(CARELINK_TIME);
}
function validTime (str) {
return moment(str, OUTPUT_TIME_MASK).isValid( );
}
+// changed from dateString to date
function times (valid) {
function withTime (data, next) {
- if (valid(data.deviceTime)) {
+ if (valid(data.dateString)) {
return next(null, data);
}
next( );
@@ -118,6 +148,7 @@ function validate ( ) {
return es.pipeline(validTimes( ), validValues( ));
}
+// Added reformatDate, reformatCPDate, reformatCPTime, reformatCL below
var api = {
columns: columns
, validator: validator
@@ -126,6 +157,10 @@ var api = {
, pipeline: es.pipeline
, validTimes: validTimes
, reformatISO: reformatISO
+ , reformatDate: reformatDate
+ , reformatCPDate: reformatCPDate
+ , reformatCPTime: reformatCPTime
+ , reformatCL: reformatCL
, pluck: pluck
, split: split
, fields: splitIntoFields
diff --git a/mmcsv640g.sh b/mmcsv640g.sh
new file mode 100644
index 0000000..731bad3
--- /dev/null
+++ b/mmcsv640g.sh
@@ -0,0 +1,335 @@
+#! /bin/bash
+# Initialising Carelink Automation
+# Proof of concept ONLY - 640g csv to NightScout
+#
+echo '*****************************'
+echo '*** MMCSV640G ***'
+echo '*** FOR TEST PURPOSES ONLY***'
+echo '*Only Use If You Accept This*'
+echo '* Started 12th November 2015*'
+echo '*** Thanks - @LittleDMatt ***'
+echo '*****************************'
+VERSION='V0.87 11th October 2016'
+echo $VERSION
+#
+# Indebted to Ben West for mmcsv - these js are tweaks and additions to his original parsing options
+# CareLink Uploader (ie not using Firefox) is provided by Tom Collins - thanks Tom!
+# Currently using crude logic here to keep things moving, with limited error trapping...
+# Split up jsons for debug - no need, just split entries, treatments to be more efficient...
+# Please use with caution. There'll be bugs here...
+# You run this at your own risk.
+# Thank you.
+
+echo '*****************************'
+echo ' Known Issues TO (TRY TO) FIX'
+echo '*****************************'
+echo ' - Dropping the odd data point between uploads - overhaul data selection to use timestamp and not line number...'
+echo ' - Square / Dual Bolus Insulin is marked at the end of the bolus period ie IOB will be under-estimated during a dual / square bolus and over-estimated at the end of the the bolus onward. This needs to be properly implemented'
+echo ' - Every time you upload to CareLink your CareLink DB and CSV file becomes bloated by ~180 lines of summary information: that quickly mounts up. The long term impact of that on your CareLink dataset is unknown'
+echo '*****************************'
+# If you get stuck / accidentally upload a huge CSV file, delete Treatments (and possibly Entries) documents in your MongoDB to revive Nightscout...
+# Removed Medtronic Predicted SG from default upload - stability issues that need more time to look at.
+
+# ****************************************************************************************
+# Assumes running CareLink Uploader or running along with Selenium script (usually looking to upload data at 1, 6, 11, 16, ..., 56 minutes past)
+# Import variables from config.sh script
+# You must include the directory of your config script when calling
+echo Importing Varables...
+source "$1"/config.sh
+# ****************************************************************************************
+# Let's go...
+# ****************************************************************************************
+
+echo Looking for CSV File in Download Directory...
+echo $DownloadPath
+echo e.g. for manual upload
+
+# Get to the right place locally...
+export PATH=$PATH:$NodejsPath
+echo Using Data Directory
+cd "$CSVDataPath"
+pwd
+echo Clearing Up CSV Files...
+rm -f "$CSVDataPath"/*.csv
+
+# Capture empty JSON files later ie "[]"
+EMPTYSIZE=3 #bytes
+
+# Uploader setup
+START_TIME=0 #last time we ran the uploader (if at all)
+
+# Allow to run for ~240 hours (roughly), ~5 min intervals
+# This thing is bound to need some TLC and don't want it running indefinitely...
+COUNT=0
+MAXCNT=2880
+until [ $COUNT -gt $MAXCNT ]; do
+echo
+
+# Splits here - Firefox or CareLink Uploader module)
+if [ $uploader -eq 0 ]
+then
+echo "Using Firefox and Selenium..."
+echo "Clearing Up CSV Download Directory..."
+rm -f "$DownloadPath"/*.csv
+echo "Waiting for CareLink upload page..."
+# Extract minutes past each hours and start at preset time (10# forces base ten to avoid errors with leading 0 in returned value, eg at 08 mins)
+while [ $((10#$(date +'%M') % $gap_mins)) -ne 0 ] ;
+ do
+ sleep 30s # check every 30 seconds
+ done
+
+# Wait 1 minute post Selenium call up of upload page
+echo "Waiting for Mouse Click on upload page..."
+sleep 1m
+
+# Going to run MiniMouseMacro to perform mouse click on upload page
+# MiniMouseMacro and a valid mmcsv640g.mmmacro file must be present in MousePath
+echo "Uploading..."
+"$MousePath"$"/MiniMouseMacro" //e //m "$MousePath"$"/mmcsv640g.mmmacro"
+
+# mm640g sometimes panics on first connection attempt - try clicking again soon after first try...
+# (this will either do nothing - we're uploading - or click the Retry button)
+sleep 45s
+"$MousePath"$"/MiniMouseMacro" //e //m "$MousePath"$"/mmcsv640g.mmmacro"
+
+echo
+sleep 2m # at least a two minute wait for upload and transfer to CSV report page...
+
+echo "Waiting for valid CSV file download from Carelink"
+ while [ ! -s "$DownloadPath"/*.csv ] ; # changed to -s to check for empty csv files also
+ do
+ sleep 30s # check every 30 seconds
+ done
+
+else
+ echo "Using CareLink Uploader..."
+ while [ $((10#$(date +'%s')/60-$START_TIME)) -lt $gap_mins_delay ] ;
+ do
+ sleep 30s # check every 30 seconds
+ done
+ while [ ! -s "$DownloadPath"/*.csv ] ; # changed to -s to check for empty csv files also
+ do
+ if [ $((10#$(date +'%s')/60-$START_TIME)) -ge $gap_mins ]
+ then
+ START_TIME=$((10#$(date +'%s')/60))
+ "$Mmcsv640gPath"$"/uploader/CareLinkUploader" "$1"$"/config.sh" &
+ fi
+ if [ -s "$DownloadPath"/Announcement.json ]
+ then
+ echo "Checking Bayer Condition..."
+ mv "$DownloadPath"/Announcement.json Announcement.json
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @Announcement.json "$your_nightscout"$"/api/v1/treatments"
+ echo
+ fi
+ sleep 30s # check every 30 seconds
+ done
+ sleep 10s # in case we've just stumbled across the file before it's finished downloading... inotifywait would be better solution for another day...
+fi
+
+if [ $COUNT -eq 0 ]
+then
+ # Update date format if required - first cycle only
+ echo First Run - Checking Regional CareLink Settings
+ if [ -z "$carelink_timestamp" ]
+ then
+ echo Timestamp not found in config.sh - setting to default
+ carelink_timestamp='DD/MM/YYTHH:mm:ss' # UK Regional Settings
+ fi
+ CARELINK_LINE="var CARELINK_TIME = '"$"$carelink_timestamp"$"' ;"
+ echo $carelink_timestamp
+ sed -i "s+^var CARELINK_TIME.*+$CARELINK_LINE+g" "$Mmcsv640gPath"/lib/utils.js
+fi
+
+# We've found a CSV file... hooray
+uploaded_recent_file=$(ls -t "$DownloadPath"/*.csv | head -1)
+echo "$uploaded_recent_file"
+echo
+#move the file to the Data Directory and rename
+mv "$uploaded_recent_file" "$CSVDataPath"/latest640g.csv
+
+# ****************************************************************************************
+# Trim CSV to reflect only new entries since last poll (latest640gbutone.csv)
+# Important to do this to cut down on duplicate entries in NS
+# (these might cause issues esp in CarePortal, but also add to your data)
+# First lose the header preamble
+sed -i '1,11d' $CSVDataPath/latest640g.csv
+
+echo
+echo Number of Entries in latest CSV file
+if [ -s $CSVDataPath/latest640g.csv ]
+then
+ LAST_LINES=$(awk '{n+=1} END {print n}' $CSVDataPath/latest640g.csv)
+else
+ LAST_LINES=1
+fi
+echo $LAST_LINES
+
+echo
+echo Number of Entries in latest but one CSV file
+if [ -s $CSVDataPath/latest640gbutone.csv ]
+then
+ LAST_LINESBUTONE=$(awk '{n+=1} END {print n}' $CSVDataPath/latest640gbutone.csv)
+else
+ LAST_LINESBUTONE=1
+fi
+
+# Check for null return
+if [ $LAST_LINESBUTONE -le 0 ]
+then
+ LAST_LINESBUTONE=1
+fi
+echo $LAST_LINESBUTONE
+echo
+
+# If LAST_LINESBUTONE > LAST_LINES then must be new day's CSV ; set LAST_LINESBUTONE to one ie keep latest640g.csv intact
+if [ $LAST_LINESBUTONE -gt $LAST_LINES ]
+then
+ LAST_LINESBUTONE=1
+fi
+
+# If LAST_LINESBUTONE = LAST_LINES then same CSV file ie pump upload failed skip...
+if [ $LAST_LINESBUTONE -ne $LAST_LINES ]
+then
+echo Extract Newly Generated Entries Only
+sed -n $LAST_LINESBUTONE,'$p' $CSVDataPath/latest640g.csv > $CSVDataPath/use640g_orig.csv
+echo
+
+# Regional tweaks - multiple files generated for debug
+# Check for decimal comma within quotes and convert to decimal point (e.g. some euro regions)
+sed 's/"\([0-9]*\),\([0-9]*\)"/\1.\2/g' $CSVDataPath/use640g_orig.csv > $CSVDataPath/use640g_temp.csv
+
+# Check for decimal comma within ; and convert to decimal point (e.g. some euro regions)
+sed 's/;\([0-9]*\),\([0-9]*\)/\1.\2,/g' $CSVDataPath/use640g_temp.csv > $CSVDataPath/use640g_temp1.csv
+
+# Check for decimal comma with preceeding = and convert to decimal point (e.g. some euro regions)
+sed 's/=\([0-9]*\),\([0-9]\)/=\1.\2/g' $CSVDataPath/use640g_temp1.csv > $CSVDataPath/use640g_temp2.csv
+
+# Check for decimal comma with preceeding = and - and convert to decimal point (e.g. some euro regions)
+sed 's/=-\([0-9]*\),\([0-9]\)/=-\1.\2/g' $CSVDataPath/use640g_temp2.csv > $CSVDataPath/use640g_temp3.csv
+
+# Replace semicolon delimiter with a comma
+sed 's/;/,/g' $CSVDataPath/use640g_temp3.csv > $CSVDataPath/use640g.csv
+
+# ****************************************************************************************
+# Don't parse 'all' to entries as generates a ton of wasted entries in DB
+# Time to extract and upload entries (SG and BG)
+echo "Entries - SG and BG Data"
+echo CBG
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=cbg $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_sg.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_sg.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_sg.json "$your_nightscout"$"/api/v1/entries"
+fi
+echo
+echo SMBG
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=smbg $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_bg.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_bg.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_bg.json "$your_nightscout"$"/api/v1/entries"
+fi
+
+# ****************************************************************************************
+# Time to extract and upload treatments
+# Step-by-step to aid debugging, not very slick...
+echo
+echo "Treatments - Basal Changes (percent and absolute), PLGM, Cannula Change, Sensor Start, Bolus and Wizard"
+
+echo Basal - Absolute
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=basal $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_basal.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_basal.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_basal.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo PLGM - SmartGuard
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=plgm $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_plgm.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_plgm.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_plgm.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Temp Basal - Percentage
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=basal_temp_percent $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_temp_basal_percent.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_temp_basal_percent.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_temp_basal_percent.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Temp Basal - Absolute
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=basal_temp_absolute $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_temp_basal_absolute.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_temp_basal_absolute.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_temp_basal_absolute.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Site Change
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=insulin $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_insulin.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_insulin.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_insulin.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Sensor Change
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=sensor_start $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_sensor_start.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_sensor_start.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_sensor_start.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Bolus Wizard
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=wizard $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_wizard.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_wizard.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_wizard.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo Bolus Delivered
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=bolus $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_bolus.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_bolus.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_bolus.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+echo SKIPPING Medtronic Predictions - As Notes
+#"$Mmcsv640gPath"/bin/cmd.js parse --filter=medpredict $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_medpredict.json
+#filesize=$(wc -c <"$CSVDataPath"$"/latest640g_medpredict.json")
+#if [ $filesize -gt $EMPTYSIZE ]
+#then
+# curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_medpredict.json "$your_nightscout"$"/api/v1/treatments"
+#fi
+echo
+
+echo Pump Alarms - As Announcements
+"$Mmcsv640gPath"/bin/cmd.js parse --filter=pump_alarms $CSVDataPath/use640g.csv > $CSVDataPath/latest640g_pump_alarms.json
+filesize=$(wc -c <"$CSVDataPath"$"/latest640g_pump_alarms.json")
+if [ $filesize -gt $EMPTYSIZE ]
+then
+ curl -vs -X POST --header "Content-Type: application/json" --header "Accept: application/json" --header "api-secret:"$api_secret_hash --data-binary @latest640g_pump_alarms.json "$your_nightscout"$"/api/v1/treatments"
+fi
+echo
+
+fi # found a file to process
+echo
+let COUNT=COUNT+1
+echo $COUNT
+echo "Tidying Up..."
+mv $CSVDataPath/latest640g.csv $CSVDataPath/latest640gbutone.csv
+done
\ No newline at end of file
diff --git a/startNS.sh b/startNS.sh
new file mode 100644
index 0000000..09d4ae8
--- /dev/null
+++ b/startNS.sh
@@ -0,0 +1,3 @@
+./vhui32.exe &
+#./FirefoxPortable/FirefoxPortable.exe carelink.minimed.eu -chrome "chrome://selenium-ide/content" &
+./mmcsv-mmcsv640g/mmcsv640g.sh "/d"
\ No newline at end of file
diff --git a/uploader/CareLinkUploader.exe b/uploader/CareLinkUploader.exe
new file mode 100644
index 0000000..700fa11
Binary files /dev/null and b/uploader/CareLinkUploader.exe differ
diff --git a/uploader/README.md b/uploader/README.md
new file mode 100644
index 0000000..f6cacd5
--- /dev/null
+++ b/uploader/README.md
@@ -0,0 +1 @@
+# mmcsvfetch
\ No newline at end of file
diff --git a/uploader/dateformats.js b/uploader/dateformats.js
new file mode 100644
index 0000000..d5b0377
--- /dev/null
+++ b/uploader/dateformats.js
@@ -0,0 +1,30 @@
+var dateformats = [
+{'locale': 'ar', 'dateformat': 'DD/MM/YY'},
+{'locale': 'eu', 'dateformat': 'M/D/YY'},
+{'locale': 'ca_ES', 'dateformat': 'DD/MM/YY'},
+{'locale': 'da_DK', 'dateformat': 'DD-MM-YY'},
+{'locale': 'en_AU', 'dateformat': 'DD/MM/YY'},
+{'locale': 'en_GB', 'dateformat': 'DD/MM/YY'},
+{'locale': 'en_NZ', 'dateformat': 'D/MM/YY'},
+{'locale': 'en_PL', 'dateformat': 'DD/MM/YY'},
+{'locale': 'en_SE', 'dateformat': 'YYYY-MM-DD'},
+{'locale': 'en_US', 'dateformat': 'MM/DD/YY'},
+{'locale': 'en_ZA', 'dateformat': 'YYYY/MM/DD'},
+{'locale': 'es_MX', 'dateformat': 'D/MM/YY'},
+{'locale': 'es_ES', 'dateformat': 'D/MM/YY'},
+{'locale': 'fr_CA', 'dateformat': 'YY-MM-DD'},
+{'locale': 'fr_FR', 'dateformat': 'DD/MM/YY'},
+{'locale': 'ja_JP', 'dateformat': 'YY/MM/DD'},
+{'locale': 'ko_KR', 'dateformat': 'YY. M. D'},
+{'locale': 'mn', 'dateformat': 'M/D/YY'},
+{'locale': 'nl_NL', 'dateformat': 'D-M-YY'},
+{'locale': 'pl_PL', 'dateformat': 'DD/MM/YY'},
+{'locale': 'pt_BR', 'dateformat': 'DD/MM/YY'},
+{'locale': 'pt_PT', 'dateformat': 'DD-MM-YYYY'},
+{'locale': 'ru_RU', 'dateformat': 'DD.MM.YY'},
+{'locale': 'sv_SE', 'dateformat': 'YYYY-MM-DD'},
+{'locale': 'tr_TR', 'dateformat': 'DD.MM.YYYY'},
+{'locale': 'vi_VN', 'dateformat': 'DD/MM/YYYY'},
+{'locale': 'zh_CN', 'dateformat': 'YY-M-D'},
+{'locale': 'zh_TW', 'dateformat': 'YYYY/M/D'},
+];
\ No newline at end of file
diff --git a/vh_cron.sh b/vh_cron.sh
new file mode 100644
index 0000000..8aafa71
--- /dev/null
+++ b/vh_cron.sh
@@ -0,0 +1,180 @@
+#! /bin/bash
+################################
+# Simple script to check
+# - if Virtual Here is running
+# - if the USB device has disconnected
+# - if the battery is too low or too hot
+# Work in progress to control CHIP / Pi with VirtualHere
+# @LittleDMatt
+# V0.21 15th April 2016
+
+################################
+# Run as root / as cron job - */5 * * * * /bin/bash -lc /root/vh_cron.sh will run every five minutes
+# !!Warning: Shutsdown and Reboots your machine if required!!
+# Battery and power script adapted from battery.sh by RzBo, Bellesserre, France
+
+################################
+# Housekeeping
+cd /root
+rm -f vh.log
+rm -f vh_check.log
+rm -f vh_disconnect.log
+
+grep vhusbdarm /var/log/syslog | tail -1 > /root/vh.log
+
+################################
+# Check if we crashed and burned starting up...
+grep '>>> Shutdown <<<' /root/vh.log > /root/vh_check.log
+# last line of vh.log will have >>> Shutdown <<< if we're stuck
+# Action (if required): try restarting in background
+if [ -s /root/vh_check.log ]
+then
+echo '/usr/sbin/vhusbdarm -b' > /root/vh_action.log
+/usr/sbin/vhusbdarm -b
+fi
+
+################################
+# Check if we've lost the Bayer Stick from the OS...
+lsusb > /root/vh_lsusb.log
+grep 'Bayer' /root/vh_lsusb.log > /root/vh_usb.log
+# Bayer will be listed - "Bayer Health Care LLC"
+# Action (if required): reboot (ffs, got to be a better way :o )
+if [ ! -s /root/vh_usb.log ]
+then
+echo 'reboot - USB Loss' > /root/vh_action.log
+/sbin/reboot
+fi
+
+################################
+# Check if we've lost the Bayer Stick from VirtualHere...
+grep 'Unmanaging device' /root/vh.log | tail -1 > /root/vh_disconnect.log
+# last line of vh.log will have Unmanaging device if we're stuck
+# Action (if required): reboot (ffs, got to be a better way :o )
+if [ -s /root/vh_disconnect.log ]
+then
+echo 'reboot - VH Loss' > /root/vh_action.log
+/sbin/reboot
+fi
+
+################################
+# Check Power status of CHIP
+# Adapted from battery.sh by RzBo, Bellesserre, France
+# force ADC enable for battery voltage and current
+/usr/sbin/i2cset -y -f 0 0x34 0x82 0xC3
+
+################################
+#read Power status register @00h
+POWER_STATUS=$(i2cget -y -f 0 0x34 0x00)
+#echo $POWER_STATUS
+
+BAT_STATUS=$(($(($POWER_STATUS&0x02))/2)) # divide by 2 is like shifting rigth 1 times
+# echo $(($POWER_STATUS&0x02))
+##echo "BAT_STATUS="$BAT_STATUS
+# echo $BAT_STATUS
+
+################################
+#read Power OPERATING MODE register @01h
+POWER_OP_MODE=$(i2cget -y -f 0 0x34 0x01)
+#echo $POWER_OP_MODE
+
+CHARG_IND=$(($(($POWER_OP_MODE&0x40))/64)) # divide by 64 is like shifting rigth 6 times
+#echo $(($POWER_OP_MODE&0x40))
+## echo "CHARG_IND="$CHARG_IND
+# echo $CHARG_IND
+
+BAT_EXIST=$(($(($POWER_OP_MODE&0x20))/32)) # divide by 32 is like shifting rigth 5 times
+#echo $(($POWER_OP_MODE&0x20))
+## echo "BAT_EXIST="$BAT_EXIST
+# echo $BAT_EXIST
+
+################################
+#read Charge control register @33h
+CHARGE_CTL=$(i2cget -y -f 0 0x34 0x33)
+## echo "CHARGE_CTL="$CHARGE_CTL
+# echo $CHARGE_CTL
+
+
+################################
+#read Charge control register @34h
+CHARGE_CTL2=$(i2cget -y -f 0 0x34 0x34)
+## echo "CHARGE_CTL2="$CHARGE_CTL2
+# echo $CHARGE_CTL2
+
+
+################################
+#read battery voltage 79h, 78h 0 mV -> 000h, 1.1 mV/bit FFFh -> 4.5045 V
+BAT_VOLT_MSB=$(i2cget -y -f 0 0x34 0x78)
+BAT_VOLT_LSB=$(i2cget -y -f 0 0x34 0x79)
+
+#echo $BAT_VOLT_MSB $BAT_VOLT_LSB
+# bash math -- converts hex to decimal so `bc` won't complain later...
+# MSB is 8 bits, LSB is lower 4 bits
+BAT_BIN=$(( $(($BAT_VOLT_MSB << 4)) | $(($(($BAT_VOLT_LSB & 0x0F)) )) ))
+
+BAT_VOLT=$(echo "($BAT_BIN*1.1)"|bc)
+## echo "Battery voltage = "$BAT_VOLT"mV"
+
+###################
+#read Battery Discharge Current 7Ch, 7Dh 0 mV -> 000h, 0.5 mA/bit 1FFFh -> 1800 mA
+#AXP209 datasheet is wrong, discharge current is in registers 7Ch 7Dh
+#13 bits
+BAT_IDISCHG_MSB=$(i2cget -y -f 0 0x34 0x7C)
+BAT_IDISCHG_LSB=$(i2cget -y -f 0 0x34 0x7D)
+
+#echo $BAT_IDISCHG_MSB $BAT_IDISCHG_LSB
+
+BAT_IDISCHG_BIN=$(( $(($BAT_IDISCHG_MSB << 5)) | $(($(($BAT_IDISCHG_LSB & 0x1F)) )) ))
+
+BAT_IDISCHG=$(echo "($BAT_IDISCHG_BIN*0.5)"|bc)
+## echo "Battery discharge current = "$BAT_IDISCHG"mA"
+
+###################
+#read Battery Charge Current 7Ah, 7Bh 0 mV -> 000h, 0.5 mA/bit FFFh -> 1800 mA
+#AXP209 datasheet is wrong, charge current is in registers 7Ah 7Bh
+#(12 bits)
+BAT_ICHG_MSB=$(i2cget -y -f 0 0x34 0x7A)
+BAT_ICHG_LSB=$(i2cget -y -f 0 0x34 0x7B)
+
+#echo $BAT_ICHG_MSB $BAT_ICHG_LSB
+
+BAT_ICHG_BIN=$(( $(($BAT_ICHG_MSB << 4)) | $(($(($BAT_ICHG_LSB & 0x0F)) )) ))
+
+BAT_ICHG=$(echo "($BAT_ICHG_BIN*0.5)"|bc)
+## echo "Battery charge current = "$BAT_ICHG"mA"
+
+###################
+#read internal temperature 5eh, 5fh -144.7c -> 000h, 0.1c/bit FFFh -> 264.8c
+TEMP_MSB=$(i2cget -y -f 0 0x34 0x5e)
+TEMP_LSB=$(i2cget -y -f 0 0x34 0x5f)
+
+# bash math -- converts hex to decimal so `bc` won't complain later...
+# MSB is 8 bits, LSB is lower 4 bits
+TEMP_BIN=$(( $(($TEMP_MSB << 4)) | $(($(($TEMP_LSB & 0x0F)) )) ))
+
+TEMP_C=$(echo "($TEMP_BIN*0.1-144.7)"|bc)
+echo "Internal temperature = "$TEMP_C"c" > /root/vh_temp.log
+
+###################
+#read fuel gauge B9h
+BAT_GAUGE_HEX=$(i2cget -y -f 0 0x34 0xb9)
+
+# bash math -- converts hex to decimal so `bc` won't complain later...
+# MSB is 8 bits, LSB is lower 4 bits
+BAT_GAUGE_DEC=$(($BAT_GAUGE_HEX))
+
+echo "Battery gauge = "$BAT_GAUGE_DEC"%" > /root/vh_battery.log
+
+################################
+# Power Action
+if [ $BAT_GAUGE_DEC -le 5 ]
+then
+echo 'Low Battery' > /root/vh_action.log
+/sbin/shutdown
+fi
+
+# Heat Action - this is temp of AXP209 power management chip, not the battery, which rests ~5mm above it. R8 chip is on reverse of PCB.
+if [ ${TEMP_C%.*} -gt 75 ]
+then
+echo 'Overheat' > /root/vh_action.log
+/sbin/shutdown
+fi