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
typeid=j_usernameYOURUSERNAME
typeid=j_passwordYOURPASSWORD
clickAndWaitid=loginButton
clicklink=Upload Data from My Device
waitForPageToLoad
pause180000180000
clickAndWaitid=toReports
clicklink=Data Export (CSV)
selectWindownull
clickAndWaitcss=#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