diff --git a/README.md b/README.md
index 07bf166f5..a635bc91d 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,10 @@ Use the [autoconfigure tool][autoconfigure] to sync an uploader to your config.
* `ALARM_HIGH` (`on`) - possible values `on` or `off`
* `ALARM_LOW` (`on`) - possible values `on` or `off`
* `ALARM_URGENT_LOW` (`on`) - possible values `on` or `off`
+ * `ALARM_TIMEAGO_WARN` (`on`) - possible values `on` or `off`
+ * `ALARM_TIMEAGO_WARN_MINS` (`15`) - minutes since the last reading to trigger a warning
+ * `ALARM_TIMEAGO_URGENT` (`on`) - possible values `on` or `off`
+ * `ALARM_TIMEAGO_URGENT_MINS` (`30`) - minutes since the last reading to trigger a urgent alarm
## Setting environment variables
diff --git a/env.js b/env.js
index 58692857d..5c06e5c9a 100644
--- a/env.js
+++ b/env.js
@@ -68,6 +68,10 @@ function config ( ) {
, 'alarmHigh': true
, 'alarmLow': true
, 'alarmUrgentLow': true
+ , 'alarmTimeAgoWarn': true
+ , 'alarmTimeAgoWarnMins': 15
+ , 'alarmTimeAgoUrgent': true
+ , 'alarmTimeAgoUrgentMins': 30
, 'language': 'en' // not used yet
} ;
@@ -84,7 +88,11 @@ function config ( ) {
env.defaults.alarmHigh = readENV('ALARM_HIGH', env.defaults.alarmHigh);
env.defaults.alarmLow = readENV('ALARM_LOW', env.defaults.alarmLow);
env.defaults.alarmUrgentLow = readENV('ALARM_URGENT_LOW', env.defaults.alarmUrgentLow);
-
+ env.defaults.alarmTimeAgoWarn = readENV('ALARM_TIMEAGO_WARN', env.defaults.alarmTimeAgoWarn);
+ env.defaults.alarmTimeAgoWarnMins = readENV('ALARM_TIMEAGO_WARN_MINS', env.defaults.alarmTimeAgoWarnMins);
+ env.defaults.alarmTimeAgoUrgent = readENV('ALARM_TIMEAGO_URGENT', env.defaults.alarmTimeAgoUrgent);
+ env.defaults.alarmTimeAgoUrgentMins = readENV('ALARM_TIMEAGO_URGENT_MINS', env.defaults.alarmTimeAgoUrgentMins);
+
//console.log(JSON.stringify(env.defaults));
env.SSL_KEY = readENV('SSL_KEY');
diff --git a/lib/websocket.js b/lib/websocket.js
index 760aae25b..c3da8be29 100644
--- a/lib/websocket.js
+++ b/lib/websocket.js
@@ -109,9 +109,14 @@ var alarms = {
};
function ackAlarm(alarmType, silenceTime) {
- alarms[alarmType].lastAckTime = new Date().getTime();
- alarms[alarmType].silenceTime = silenceTime ? silenceTime : FORTY_MINUTES;
- delete alarms[alarmType].lastEmitTime;
+ var alarm = alarms[alarmType];
+ if (!alarm) {
+ console.warn('Got an ack for an unknown alarm time');
+ return;
+ }
+ alarm.lastAckTime = new Date().getTime();
+ alarm.silenceTime = silenceTime ? silenceTime : FORTY_MINUTES;
+ delete alarm.lastEmitTime;
}
//should only be used when auto acking the alarms after going back in range or when an error corrects
diff --git a/static/css/drawer.css b/static/css/drawer.css
index 01e6905c6..559c69048 100644
--- a/static/css/drawer.css
+++ b/static/css/drawer.css
@@ -6,6 +6,7 @@
border-radius: 5px;
border: 2px solid #aaa;
box-shadow: 2px 2px 0 #eee;
+ width: 100%;
}
#drawer {
@@ -42,7 +43,8 @@ input[type=number]:invalid {
}
#drawer .actions a {
- padding-left: 20px;
+ float: right;
+ padding: 10px 0;
}
#treatmentDrawer {
@@ -85,6 +87,10 @@ input[type=number]:invalid {
display: block;
}
+input.timeago-mins {
+ width: 40px;
+}
+
#eventTime {
padding-bottom: 15px;
}
diff --git a/static/css/main.css b/static/css/main.css
index e30a555e6..de90b71f5 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -109,10 +109,28 @@ body {
color: red !important;
}
+.alarming-timeago #lastEntry.pill.warn {
+ border-color: yellow;
+}
+.alarming-timeago #lastEntry.pill.warn label {
+ background: yellow;
+}
+
+.alarming-timeago #lastEntry.pill.urgent {
+ border-color: red;
+}
+.alarming-timeago #lastEntry.pill.urgent label {
+ background: red;
+}
+
.bgStatus.current .currentBG {
text-decoration: none;
}
+.alarming-timeago .bgStatus.current .currentBG {
+ text-decoration: line-through;
+}
+
.time {
color: #808080;
font-size: 100px;
@@ -452,10 +470,8 @@ div.tooltip {
font-size: 15px;
}
- .dropdown-menu {
- font-size: 50px !important;
- margin-left: 2vw;
- width: 96vw;
+ #silenceBtn * {
+ font-size: 20px;
}
.time {
@@ -601,7 +617,11 @@ div.tooltip {
font-size: 15px;
}
- .focus-range {
+ #silenceBtn {
+ right: -25px;
+ }
+
+ .focus-range {
position: absolute;
top: 40px;
left: initial;
diff --git a/static/index.html b/static/index.html
index 4f77b714d..538339c89 100644
--- a/static/index.html
+++ b/static/index.html
@@ -82,6 +82,16 @@
Nightscout
+
+
+
+
+
+
+
+
+
+
- Night Mode
diff --git a/static/js/client.js b/static/js/client.js
index 6262cc3a6..dd35bd3c6 100644
--- a/static/js/client.js
+++ b/static/js/client.js
@@ -25,9 +25,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
, MINUTE_IN_SECS = 60
, HOUR_IN_SECS = 3600
, DAY_IN_SECS = 86400
- , WEEK_IN_SECS = 604800
- , MINUTES_SINCE_LAST_UPDATE_WARN = 10
- , MINUTES_SINCE_LAST_UPDATE_URGENT = 20;
+ , WEEK_IN_SECS = 604800;
var socket
, isInitialData = false
@@ -43,6 +41,7 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
, now = Date.now()
, data = []
, foucusRangeMS = THREE_HOURS_MS
+ , clientAlarms = {}
, audio = document.getElementById('audio')
, alarmInProgress = false
, currentAlarmType = null
@@ -1186,13 +1185,21 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
});
$('#container').removeClass('alarming');
- brushed(true);
// only emit ack if client invoke by button press
if (isClient) {
- socket.emit('ack', currentAlarmType || 'alarm', silenceTime);
- brushed(false);
+ if (isTimeAgoAlarmType(currentAlarmType)) {
+ $('#container').removeClass('alarming-timeago');
+ var alarm = getClientAlarm(currentAlarmType);
+ alarm.lastAckTime = Date.now();
+ alarm.silenceTime = silenceTime;
+ console.info('time ago alarm (' + currentAlarmType + ', not acking to server');
+ } else {
+ socket.emit('ack', currentAlarmType || 'alarm', silenceTime);
+ }
}
+
+ brushed(false);
}
function timeAgo(time) {
@@ -1213,9 +1220,9 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
if (offset > DAY_IN_SECS * 7) {
parts.status = 'warn';
- } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_URGENT)) {
+ } else if (offset < MINUTE_IN_SECS * -5 || offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoUrgentMins)) {
parts.status = 'urgent';
- } else if (offset > (MINUTE_IN_SECS * MINUTES_SINCE_LAST_UPDATE_WARN)) {
+ } else if (offset > (MINUTE_IN_SECS * browserSettings.alarmTimeAgoWarnMins)) {
parts.status = 'warn';
} else {
parts.status = 'current';
@@ -1458,6 +1465,35 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
$('#currentTime').text(formatTime(dateTime, true)).css('text-decoration', '');
}
+ function getClientAlarm(type) {
+ var alarm = clientAlarms[type];
+ if (!alarm) {
+ alarm = { type: type };
+ clientAlarms[type] = alarm;
+ }
+ return alarm;
+ }
+
+ function isTimeAgoAlarmType(alarmType) {
+ return alarmType == 'warnTimeAgo' || alarmType == 'urgentTimeAgo';
+ }
+
+ function checkTimeAgoAlarm(ago) {
+ var level = ago.status
+ , alarm = getClientAlarm(level + 'TimeAgo');
+
+ if (!alarmingNow() && Date.now() >= (alarm.lastAckTime || 0) + (alarm.silenceTime || 0)) {
+ currentAlarmType = alarm.type;
+ console.info('generating timeAgoAlarm', alarm.type);
+ $('#container').addClass('alarming-timeago');
+ if (level == 'warn') {
+ generateAlarm(alarmSound);
+ } else {
+ generateAlarm(urgentAlarmSound);
+ }
+ }
+ }
+
function updateTimeAgo() {
var lastEntry = $('#lastEntry')
, time = latestSGV ? new Date(latestSGV.x).getTime() : -1
@@ -1471,6 +1507,16 @@ var app = {}, browserSettings = {}, browserStorage = $.localStorage;
updateTitle();
}
+ if (
+ (browserSettings.alarmTimeAgoWarn && ago.status == 'warn')
+ || (browserSettings.alarmTimeAgoUrgent && ago.status == 'urgent')) {
+ checkTimeAgoAlarm(ago);
+ }
+
+ if (alarmingNow() && ago.status == 'current' && isTimeAgoAlarmType(currentAlarmType)) {
+ stopAlarm(true, ONE_MIN_IN_MS);
+ }
+
if (retroMode || !ago.value) {
lastEntry.find('em').hide();
} else {
diff --git a/static/js/ui-utils.js b/static/js/ui-utils.js
index dc4f77247..7b1c4a54a 100644
--- a/static/js/ui-utils.js
+++ b/static/js/ui-utils.js
@@ -28,6 +28,10 @@ function getBrowserSettings(storage) {
'alarmHigh': storage.get('alarmHigh'),
'alarmLow': storage.get('alarmLow'),
'alarmUrgentLow': storage.get('alarmUrgentLow'),
+ 'alarmTimeAgoWarn': storage.get('alarmTimeAgoWarn'),
+ 'alarmTimeAgoWarnMins': storage.get('alarmTimeAgoWarnMins'),
+ 'alarmTimeAgoUrgent': storage.get('alarmTimeAgoUrgent'),
+ 'alarmTimeAgoUrgentMins': storage.get('alarmTimeAgoUrgentMins'),
'nightMode': storage.get('nightMode'),
'showRawbg': storage.get('showRawbg'),
'customTitle': storage.get('customTitle'),
@@ -47,10 +51,18 @@ function getBrowserSettings(storage) {
json.alarmHigh = setDefault(json.alarmHigh, app.defaults.alarmHigh);
json.alarmLow = setDefault(json.alarmLow, app.defaults.alarmLow);
json.alarmUrgentLow = setDefault(json.alarmUrgentLow, app.defaults.alarmUrgentLow);
+ json.alarmTimeAgoWarn = setDefault(json.alarmTimeAgoWarn, app.defaults.alarmTimeAgoWarn);
+ json.alarmTimeAgoWarnMins = setDefault(json.alarmTimeAgoWarnMins, app.defaults.alarmTimeAgoWarnMins);
+ json.alarmTimeAgoUrgent = setDefault(json.alarmTimeAgoUrgent, app.defaults.alarmTimeAgoUrgent);
+ json.alarmTimeAgoUrgentMins = setDefault(json.alarmTimeAgoUrgentMins, app.defaults.alarmTimeAgoUrgentMins);
$('#alarm-urgenthigh-browser').prop('checked', json.alarmUrgentHigh).next().text('Urgent High Alarm' + appendThresholdValue(app.thresholds.bg_high));
$('#alarm-high-browser').prop('checked', json.alarmHigh).next().text('High Alarm' + appendThresholdValue(app.thresholds.bg_target_top));
$('#alarm-low-browser').prop('checked', json.alarmLow).next().text('Low Alarm' + appendThresholdValue(app.thresholds.bg_target_bottom));
$('#alarm-urgentlow-browser').prop('checked', json.alarmUrgentLow).next().text('Urgent Low Alarm' + appendThresholdValue(app.thresholds.bg_low));
+ $('#alarm-timeagowarn-browser').prop('checked', json.alarmTimeAgoWarn);
+ $('#alarm-timeagowarnmins-browser').val(json.alarmTimeAgoWarnMins);
+ $('#alarm-timeagourgent-browser').prop('checked', json.alarmTimeAgoUrgent);
+ $('#alarm-timeagourgentmins-browser').val(json.alarmTimeAgoUrgentMins);
json.nightMode = setDefault(json.nightMode, app.defaults.nightMode);
$('#nightmode-browser').prop('checked', json.nightMode);
@@ -351,6 +363,10 @@ $('#save').click(function(event) {
'alarmHigh': $('#alarm-high-browser').prop('checked'),
'alarmLow': $('#alarm-low-browser').prop('checked'),
'alarmUrgentLow': $('#alarm-urgentlow-browser').prop('checked'),
+ 'alarmTimeAgoWarn': $('#alarm-timeagowarn-browser').prop('checked'),
+ 'alarmTimeAgoWarnMins': parseInt($('#alarm-timeagowarnmins-browser').val()) || 15,
+ 'alarmTimeAgoUrgent': $('#alarm-timeagourgent-browser').prop('checked'),
+ 'alarmTimeAgoUrgentMins': parseInt($('#alarm-timeagourgentmins-browser').val()) || 30,
'nightMode': $('#nightmode-browser').prop('checked'),
'showRawbg': $('input:radio[name=show-rawbg]:checked').val(),
'customTitle': $('input#customTitle').prop('value'),
@@ -365,7 +381,7 @@ $('#save').click(function(event) {
$('#useDefaults').click(function(event) {
//remove all known settings, since there might be something else is in localstorage
- var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat'];
+ var settings = ['units', 'alarmUrgentHigh', 'alarmHigh', 'alarmLow', 'alarmUrgentLow', 'alarmTimeAgoWarn', 'alarmTimeAgoWarnMins', 'alarmTimeAgoUrgent', 'alarmTimeAgoUrgentMins', 'nightMode', 'showRawbg', 'customTitle', 'theme', 'timeFormat'];
settings.forEach(function(setting) {
browserStorage.remove(setting);
});