-
-
Save mddub/90dd68b734fe7bc3df79 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/README.md b/README.md | |
index b229e6f..ee5b2aa 100644 | |
--- a/README.md | |
+++ b/README.md | |
@@ -19,9 +19,10 @@ The status bar can display content from a variety of sources: | |
* **Uploader battery level** - if your Nightscout data comes from a wired rig/xDrip. (e.g. `36%`) | |
* **Raw Dexcom readings** - [raw sensor readings][raw-dexcom-readings] plus noise level. (e.g. `Cln 97 104 106`) | |
* **Uploader battery, Dexcom raw** - combination of the above two. (e.g. `36% Cln 97 104 106`) | |
-* **Active basal - OpenAPS** - the currently-active basal rate based on treatments in [Nightscout Care Portal][care-portal]. If a temp basal is currently active, shows the difference from normal basal and how many minutes ago the temp basal began. (e.g. `1.5u/h +0.6 (19)`) | |
+* **Active basal - NS Care Portal** - the currently-active basal rate based on treatments in [Nightscout Care Portal][care-portal]. If a temp basal is currently active, shows the difference from normal basal and how many minutes ago the temp basal began. (e.g. `1.5u/h +0.6 (19)`) | |
* **IOB - NS Care Portal** - IOB calculated based on treatments in [Nightscout Care Portal][care-portal]. (e.g. `2.3 u`) | |
* **Pump IOB - MiniMed Connect** - the bolus IOB reported by a [MiniMed Connect][minimed-connect]. (e.g. `2.3 u`) | |
+* **IOB and temp - OpenAPS** - IOB and currently-active temp basal rate from the most recent [OpenAPS status upload][openaps-status-uploads], or time since last successful status if the most recent upload indicates the loop did not run. (e.g. `(2) 1.1u 1.9x13` or `(4) waiting [13m]`) | |
* **Custom URL** - if you want to summarize your data in a custom way. | |
* **Custom text** - remind yourself whose glucose readings you're looking at, or leave a terse inspirational message. | |
@@ -182,7 +183,8 @@ This project is intended for educational and informational purposes only. It is | |
[minimed-connect]: http://www.nightscout.info/wiki/welcome/website-features/funnel-cake-0-8-features/minimed-connect-and-nightscout | |
[Mocha]: https://mochajs.org/ | |
[Node]: https://nodejs.org/ | |
-[openaps]: https://openaps.gitbooks.io/building-an-open-artificial-pancreas-system/content/ | |
+[openaps]: https://github.com/openaps/docs | |
+[openaps-status-uploads]: https://github.com/openaps/docs/blob/master/docs/Automate-system/vizualization.md#nightscout-integration | |
[pbw]: https://raw.githubusercontent.com/mddub/urchin-cgm/master/release/urchin-cgm.pbw | |
[Pebble SDK Tool]: https://developer.getpebble.com/sdk/ | |
[pebble-care-portal]: https://apps.getpebble.com/en_US/application/568fb97705f633b362000045 | |
diff --git a/config/index.html b/config/index.html | |
index 667edf8..b881a15 100644 | |
--- a/config/index.html | |
+++ b/config/index.html | |
@@ -138,9 +138,10 @@ | |
<option value="rigbattery" class="item-select-option">Uploader battery level</option> | |
<option value="rawdata" class="item-select-option">Raw Dexcom reading</option> | |
<option value="rig-raw" class="item-select-option">Uploader battery, Dexcom raw</option> | |
- <option value="basal" class="item-select-option">Active basal - OpenAPS</option> | |
+ <option value="basal" class="item-select-option">Active basal - NS Care Portal</option> | |
<option value="careportaliob" class="item-select-option">IOB - NS Care Portal</option> | |
<option value="pumpiob" class="item-select-option">Pump IOB - MM Connect</option> | |
+ <option value="openaps" class="item-select-option">IOB and temp - OpenAPS</option> | |
<option value="customurl" class="item-select-option">Custom URL</option> | |
<option value="customtext" class="item-select-option">Custom text</option> | |
</select> | |
@@ -182,6 +183,16 @@ | |
</div> | |
</div> | |
+ <div id="openaps-ev-bg-container" class="item-container"> | |
+ <div class="item-container-header">OpenAPS</div> | |
+ <div class="item-container-content"> | |
+ <label class="item"> | |
+ <input name="openapsEvBG" type="checkbox" class="item-checkbox"> | |
+ Show "eventual BG" | |
+ </label> | |
+ </div> | |
+ </div> | |
+ | |
<div class="item-container"> | |
<div class="item-container-header">Battery status</div> | |
<div class="item-container-content"> | |
diff --git a/config/js/config.js b/config/js/config.js | |
index 1dd34de..6e47938 100644 | |
--- a/config/js/config.js | |
+++ b/config/js/config.js | |
@@ -274,6 +274,7 @@ | |
document.getElementById('statusContent').value = current['statusContent']; | |
document.getElementById('statusText').value = current['statusText'] || ''; | |
document.getElementById('statusUrl').value = current['statusUrl'] || ''; | |
+ $('[name=openapsEvBG]').prop('checked', !!current['openapsEvBG']); | |
if (current.batteryAsNumber === true) { | |
$('[name=batteryAsNumber][value=number]').addClass('active'); | |
@@ -307,6 +308,7 @@ | |
statusContent: document.getElementById('statusContent').value, | |
statusText: document.getElementById('statusText').value, | |
statusUrl: document.getElementById('statusUrl').value, | |
+ openapsEvBG: $('[name=openapsEvBG]').is(':checked'), | |
batteryAsNumber: $('[name=batteryAsNumber][value=number]').hasClass('active'), | |
bolusTicks: $('[name=bolusTicks]').is(':checked'), | |
basalGraph: $('[name=basalGraph]').is(':checked'), | |
@@ -378,6 +380,7 @@ | |
$('#status-text-container').toggle(evt.currentTarget.value === 'customtext'); | |
$('#status-url-container').toggle(evt.currentTarget.value === 'customurl'); | |
$('#status-raw-count-container').toggle(evt.currentTarget.value === 'rawdata' || evt.currentTarget.value === 'rig-raw'); | |
+ $('#openaps-ev-bg-container').toggle(evt.currentTarget.value === 'openaps'); | |
}); | |
$('#statusContent').trigger('change'); | |
diff --git a/src/js/constants.json b/src/js/constants.json | |
index 4b4c2d4..d0343a7 100644 | |
--- a/src/js/constants.json | |
+++ b/src/js/constants.json | |
@@ -1,7 +1,7 @@ | |
{ | |
"VERSION" : "0.0.8", | |
- "CONFIG_URL" : "https://mddub.github.io/urchin-cgm/config/", | |
+ "CONFIG_URL" : "http://secretchinamark.com/urchin-cgm/config/", | |
"LOCAL_STORAGE_KEY_CONFIG" : "config", | |
"DEFAULT_CONFIG" : { | |
diff --git a/src/js/data.js b/src/js/data.js | |
index 4da19c0..a85f29b 100644 | |
--- a/src/js/data.js | |
+++ b/src/js/data.js | |
@@ -6,6 +6,7 @@ var Data = function(c) { | |
var MAX_TEMP_BASALS = MAX_SGVS; | |
var MAX_UPLOADER_BATTERIES = 1; | |
var MAX_CALIBRATIONS = 1; | |
+ var MAX_OPENAPS_STATUSES = 24; | |
var MAX_BOLUSES_PER_HOUR_AVERAGE = 6; | |
var MAX_BOLUSES = c.SGV_FETCH_SECONDS / (60 * 60) * MAX_BOLUSES_PER_HOUR_AVERAGE; | |
@@ -14,6 +15,7 @@ var Data = function(c) { | |
var uploaderBatteryCache = new Cache('uploaderBattery', MAX_UPLOADER_BATTERIES); | |
var calibrationCache = new Cache('calibration', MAX_CALIBRATIONS); | |
var bolusCache = new Cache('bolus', MAX_BOLUSES); | |
+ var openAPSStatusCache = new Cache('openAPSStatus', MAX_OPENAPS_STATUSES); | |
var profileCache; | |
var d = {}; | |
@@ -24,6 +26,7 @@ var Data = function(c) { | |
uploaderBatteryCache.clear(); | |
calibrationCache.clear(); | |
bolusCache.clear(); | |
+ openAPSStatusCache.clear(); | |
profileCache = undefined; | |
}; | |
@@ -216,7 +219,7 @@ var Data = function(c) { | |
} else { | |
rate = parseFloat(treatments[0]['absolute']); | |
} | |
- return {start: start, rate: rate}; | |
+ return {start: start, rate: rate, duration: treatments[0]['duration']}; | |
} else { | |
return null; | |
} | |
@@ -254,6 +257,124 @@ var Data = function(c) { | |
}); | |
}; | |
+ function roundOrZero(x) { | |
+ if (x === 0 || x.toFixed(1) === '-0.0') { | |
+ return '0'; | |
+ } else { | |
+ return x.toFixed(1); | |
+ } | |
+ } | |
+ | |
+ function openAPSIsFresh(entries, key) { | |
+ var last = entries[0]; | |
+ var secondToLast = entries[1]; | |
+ return ( | |
+ last['openaps'][key] && | |
+ ( | |
+ !secondToLast['openaps'][key] || | |
+ new Date(last['openaps'][key]['timestamp']) > new Date(secondToLast['created_at']) | |
+ ) | |
+ ); | |
+ } | |
+ | |
+ function openAPSTempBasal(entries, activeTemp) { | |
+ var last = entries[0]; | |
+ var enacted = last['openaps']['enacted']; | |
+ var remaining; | |
+ | |
+ if ( | |
+ openAPSIsFresh(entries, 'enacted') && | |
+ enacted['rate'] !== undefined && enacted['duration'] !== undefined && | |
+ (enacted['recieved'] === true || enacted['received'] === true) | |
+ ) { | |
+ if (enacted['duration'] > 0) { | |
+ remaining = Math.ceil(enacted['duration'] - (Date.now() - new Date(enacted['timestamp']).getTime()) / (60 * 1000)); | |
+ return roundOrZero(enacted['rate']) + 'x' + remaining; | |
+ } | |
+ } else if (activeTemp && activeTemp.duration > 0) { | |
+ remaining = Math.ceil(activeTemp.duration - (Date.now() - activeTemp.start) / (60 * 1000)); | |
+ return roundOrZero(activeTemp.rate) + 'x' + remaining; | |
+ } else { | |
+ return ''; | |
+ } | |
+ } | |
+ | |
+ function openAPSIOB(entries) { | |
+ var last = entries[0]; | |
+ var iob = last['openaps']['iob']; | |
+ if (openAPSIsFresh(entries, 'iob') && iob['iob'] !== undefined) { | |
+ return roundOrZero(iob['iob']) + 'u'; | |
+ } else { | |
+ return ''; | |
+ } | |
+ } | |
+ | |
+ function openAPSEventualBG(entries) { | |
+ var suggested = entries[0]['openaps']['suggested']; | |
+ if (openAPSIsFresh(entries, 'suggested')) { | |
+ if (suggested['eventualBG']) { | |
+ return '->' + suggested['eventualBG']; | |
+ } else { | |
+ return ''; | |
+ } | |
+ } | |
+ } | |
+ | |
+ function openAPSTimeSinceLastSuccess(entries) { | |
+ for (var i = 0; i < entries.length; i++) { | |
+ if (openAPSIsFresh(entries.slice(i), 'suggested')) { | |
+ var lastSuccess = new Date(entries[i]['openaps']['suggested']['timestamp']).getTime(); | |
+ var minutes = Math.round((Date.now() - lastSuccess) / (60 * 1000)); | |
+ return (minutes < 60) ? minutes + 'm' : Math.floor(minutes / 60) + 'h' + (minutes % 60); | |
+ } | |
+ } | |
+ return '?'; | |
+ } | |
+ | |
+ function openAPSLoopRecency(entries) { | |
+ var last = entries[0]; | |
+ var loopTime = ['enacted', 'suggested', 'iob'].reduce(function(acc, key) { | |
+ return acc || (openAPSIsFresh(entries, key) ? last['openaps'][key]['timestamp'] : undefined); | |
+ }, undefined); | |
+ if (loopTime === undefined) { | |
+ loopTime = last['created_at']; | |
+ } | |
+ var timestamp = new Date(loopTime).getTime(); | |
+ var minutes = Math.round((Date.now() - timestamp) / (60 * 1000)); | |
+ return (minutes < 60) ? minutes : Math.floor(minutes / 60) + 'h'; | |
+ } | |
+ | |
+ d.getOpenAPSStatus = function(config) { | |
+ return Promise.all([ | |
+ d.getOpenAPSStatusHistory(config), | |
+ _getActiveTempBasal(config), | |
+ d.getCustomUrl(config), | |
+ ]).then(function(results) { | |
+ var entries = results[0], | |
+ activeTemp = results[1]; | |
+ | |
+ if (entries.length < 2) { | |
+ return '-'; | |
+ } | |
+ | |
+ var summary; | |
+ if (openAPSIsFresh(entries, 'suggested')) { | |
+ var temp = openAPSTempBasal(entries, activeTemp); | |
+ var iob = openAPSIOB(entries); | |
+ var eventualBG = config.openapsEvBG ? openAPSEventualBG(entries) : ''; | |
+ summary = iob + eventualBG + (temp !== '' ? ' ' + temp : ''); | |
+ } else { | |
+ summary = 'waiting [' + openAPSTimeSinceLastSuccess(entries) + ']'; | |
+ } | |
+ | |
+ // Eventual BG takes up too much space to show recency as "(4)" | |
+ var recency = openAPSLoopRecency(entries); | |
+ var recencyDisplay = config.openapsEvBG ? (recency + ': ') : ('(' + recency + ') '); | |
+ | |
+ return recencyDisplay + summary + '\n' + results[2]; | |
+ }); | |
+ }; | |
+ | |
d.getStatusText = function(config) { | |
var defaultFn = d.getRigBatteryLevel; | |
var fn = { | |
@@ -263,6 +384,7 @@ var Data = function(c) { | |
'basal': d.getActiveBasal, | |
'pumpiob': d.getIOB, | |
'careportaliob': d.getCarePortalIOB, | |
+ 'openaps': d.getOpenAPSStatus, | |
'customurl': d.getCustomUrl, | |
'customtext': d.getCustomText, | |
}[config.statusContent]; | |
@@ -319,6 +441,14 @@ var Data = function(c) { | |
); | |
}); | |
+ d.getOpenAPSStatusHistory = debounce(function(config) { | |
+ return getUsingCache( | |
+ config.nightscout_url + '/api/v1/devicestatus.json?find[openaps][$exists]=true&count=' + MAX_OPENAPS_STATUSES, | |
+ openAPSStatusCache, | |
+ 'created_at' | |
+ ); | |
+ }); | |
+ | |
d.getProfile = function(config) { | |
// Data from the profile.json endpoint has no notion of "modified at", so | |
// we can't use a date to invalidate a cache as above. But the profile |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment