sleeplog: Correct fix #1445, display loading info while calculating sleep data
Update app.js - move complex data analysis into separate function - add displaying a loading info - faster feedback by calling the analysis through a timeout Update boot.js - correct fix #1445 as mentioned by splitting the `stop()` function to only remove the kill listener on a manual stop - optimize `start()` and `stop()` functions Update README.md - change beautified menu entries according to changes in settings.js Update settings.js - replace `circulate()` function with internal `wrap: true` - move delay for menu redraw as suggested in espruino/Espruino#2149 - beautify menu entries for new touchscreen E.showMenu system (> fw 2v.13.32)master
parent
ab42b6555f
commit
8c4f2f18c9
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Fix crash on start
|
||||
0.03: Added power saving mode, move all read/write log actions into lib/module, fix #1445
|
||||
0.02: Fix crash on start #1423
|
||||
0.03: Added power saving mode, move all read/write log actions into lib/module
|
||||
0.04: Fix #1445, display loading info while calculating sleep data
|
||||
|
|
|
|||
|
|
@ -34,31 +34,31 @@ also provides a power saving mode using the built in movement calculation. The i
|
|||
---
|
||||
### Settings
|
||||
---
|
||||
* __BreakTod__ | break at time of day
|
||||
* __Break Tod__ | break at time of day
|
||||
_0_ / _1_ / _..._ / __10__ / _..._ / _12_
|
||||
Change time of day on wich the lower graph starts and the upper graph ends.
|
||||
* __MaxAwake__ | maximal awake duration
|
||||
* __Max Awake__ | maximal awake duration
|
||||
_15min_ / _20min_ / _..._ / __60min__ / _..._ / _120min_
|
||||
Adjust the maximal awake duration upon the exceeding of which aborts the consecutive sleep period.
|
||||
* __MinConsec__ | minimal consecutive sleep duration
|
||||
* __Min Consec__ | minimal consecutive sleep duration
|
||||
_15min_ / _20min_ / _..._ / __30min__ / _..._ / _120min_
|
||||
Adjust the minimal consecutive sleep duration that will be considered for the consecutive sleep value.
|
||||
* __TempThresh__ | temperature threshold
|
||||
* __Temp Thresh__ | temperature threshold
|
||||
_20°C_ / _20.5°C_ / _..._ / __25°C__ / _..._ / _40°C_
|
||||
The internal temperature must be greater than this threshold to log _sleeping_, otherwise it is _not worn_.
|
||||
* __PowerSaving__
|
||||
* __Power Saving__
|
||||
_on_ / __off__
|
||||
En-/Disable power saving mode. _Saves battery, but might decrease accurracy._
|
||||
* __MaxMove__ | maximal movement threshold
|
||||
* __Max Move__ | maximal movement threshold
|
||||
(only available when on power saving mode)
|
||||
_50_ / _51_ / _..._ / __100__ / _..._ / _200_
|
||||
On power saving mode the watch is considered resting if this threshold is lower or equal to the movement value of bangle's health event.
|
||||
* __NoMoThresh__ | no movement threshold
|
||||
* __NoMo Thresh__ | no movement threshold
|
||||
(only available when not on power saving mode)
|
||||
_0.006_ / _0.007_ / _..._ / __0.012__ / _..._ / _0.020_
|
||||
The standard deviation over the measured values needs to be lower then this threshold to count as not moving.
|
||||
The defaut threshold value worked best for my watch. A threshold value below 0.008 may get triggert by noise.
|
||||
* __MinDuration__ | minimal no movement duration
|
||||
* __Min Duration__ | minimal no movement duration
|
||||
(only available when not on power saving mode)
|
||||
_5min_ / _6min_ / _..._ / __10min__ / _..._ / _15min_
|
||||
If no movement is detected for this duration, the watch is considered as resting.
|
||||
|
|
|
|||
|
|
@ -127,34 +127,24 @@ function drawLog(topY, viewUntil) {
|
|||
return output.map(value => value /= 6E4);
|
||||
}
|
||||
|
||||
// define draw night to function
|
||||
function drawNightTo(prevDays) {
|
||||
// calculate 10am of this or a previous day
|
||||
var date = Date();
|
||||
date = Date(date.getFullYear(), date.getMonth(), date.getDate() - prevDays, breaktod);
|
||||
// define function to draw the analysis
|
||||
function drawAnalysis(toDate) {
|
||||
//var t0 = Date.now();
|
||||
|
||||
// get width
|
||||
var width = g.getWidth();
|
||||
|
||||
// clear app area
|
||||
g.clearRect(0, 24, width, width);
|
||||
|
||||
// define variable for sleep calculation
|
||||
var outputs = [0, 0]; // [estimated, true]
|
||||
// draw log graphs and read outputs
|
||||
drawLog(110, date).forEach(
|
||||
(value, index) => outputs[index] += value);
|
||||
drawLog(145, Date(date.valueOf() - 432E5)).forEach(
|
||||
(value, index) => outputs[index] += value);
|
||||
|
||||
// reduce date by 1s to ensure correct headline
|
||||
date = Date(date.valueOf() - 1E3);
|
||||
// draw headline, on red bg if service or loggging disabled or green bg if powersaving enabled
|
||||
g.setColor(global.sleeplog && sleeplog.enabled && sleeplog.logfile ? sleeplog.powersaving ? 2016 : g.theme.bg : 63488);
|
||||
g.fillRect(0, 30, width, 66).reset();
|
||||
g.setFont("12x20").setFontAlign(0, -1);
|
||||
g.drawString("Night to " + require('locale').dow(date, 1) + "\n" +
|
||||
require('locale').date(date, 1), width / 2, 30);
|
||||
// clear analysis area
|
||||
g.clearRect(0, 71, width, width);
|
||||
|
||||
// draw log graphs and read outputs
|
||||
drawLog(110, toDate).forEach(
|
||||
(value, index) => outputs[index] += value);
|
||||
drawLog(145, Date(toDate.valueOf() - 432E5)).forEach(
|
||||
(value, index) => outputs[index] += value);
|
||||
|
||||
// draw outputs
|
||||
g.reset(); // area: 0, 70, width, 105
|
||||
|
|
@ -166,8 +156,47 @@ function drawNightTo(prevDays) {
|
|||
Math.floor(outputs[0] % 60) + "min", width - 10, 70);
|
||||
g.drawString(Math.floor(outputs[1] / 60) + "h " +
|
||||
Math.floor(outputs[1] % 60) + "min", width - 10, 90);
|
||||
|
||||
//print("analysis processing seconds:", Math.round(Date.now() - t0) / 1000);
|
||||
}
|
||||
|
||||
// define draw night to function
|
||||
function drawNightTo(prevDays) {
|
||||
// calculate 10am of this or a previous day
|
||||
var toDate = Date();
|
||||
toDate = Date(toDate.getFullYear(), toDate.getMonth(), toDate.getDate() - prevDays, breaktod);
|
||||
|
||||
// get width
|
||||
var width = g.getWidth();
|
||||
var center = width / 2;
|
||||
|
||||
// reduce date by 1s to ensure correct headline
|
||||
toDate = Date(toDate.valueOf() - 1E3);
|
||||
|
||||
// clear heading area
|
||||
g.clearRect(0, 24, width, 70);
|
||||
|
||||
// display service statuses: service, loggging and powersaving
|
||||
g.setColor(global.sleeplog && sleeplog.enabled && sleeplog.logfile ? sleeplog.powersaving ? 2016 : g.theme.bg : 63488);
|
||||
g.fillRect(0, 30, width, 66).reset();
|
||||
|
||||
// draw headline
|
||||
g.setFont("12x20").setFontAlign(0, -1);
|
||||
g.drawString("Night to " + require('locale').dow(toDate, 1) + "\n" +
|
||||
require('locale').date(toDate, 1), center, 30);
|
||||
|
||||
// show loading info
|
||||
var info = "calculating data ...\nplease be patient :)";
|
||||
var y0 = center + 30;
|
||||
var bounds = [center - 80, y0 - 20, center + 80, y0 + 20];
|
||||
g.clearRect.apply(g, bounds).drawRect.apply(g, bounds);
|
||||
g.setFont("6x8").setFontAlign(0, 0);
|
||||
g.drawString(info, center, y0);
|
||||
|
||||
// calculate and draw analysis after timeout for faster feedback
|
||||
if (ATID) ATID = clearTimeout(ATID);
|
||||
ATID = setTimeout(drawAnalysis, 50, toDate);
|
||||
}
|
||||
|
||||
// define function to draw and setup UI
|
||||
function startApp() {
|
||||
|
|
@ -182,8 +211,9 @@ function startApp() {
|
|||
});
|
||||
}
|
||||
|
||||
// define day to display
|
||||
// define day to display and analysis timeout id
|
||||
var prevDays = 0;
|
||||
var ATID;
|
||||
|
||||
// setup app
|
||||
g.clear();
|
||||
|
|
|
|||
|
|
@ -28,36 +28,43 @@ if (sleeplog.enabled) {
|
|||
resting: undefined,
|
||||
status: undefined,
|
||||
|
||||
// define stop function (logging will restart if enabled and boot file is executed)
|
||||
stop: function() {
|
||||
// define function to handle stopping the service, it will be restarted on reload if enabled
|
||||
stopHandler: function() {
|
||||
// remove all listeners
|
||||
Bangle.removeListener('accel', sleeplog.accel);
|
||||
Bangle.removeListener('health', sleeplog.health);
|
||||
E.removeListener('kill', () => sleeplog.stop());
|
||||
// exit on missing global object
|
||||
if (!global.sleeplog) return;
|
||||
// write log with undefined sleeping status
|
||||
require("sleeplog").writeLog(0, [Math.floor(Date.now()), 0]);
|
||||
// reset always used cached values
|
||||
sleeplog.resting = undefined;
|
||||
sleeplog.status = undefined;
|
||||
sleeplog.ess_values = [];
|
||||
sleeplog.nomocount = 0;
|
||||
sleeplog.firstnomodate = undefined;
|
||||
// reset cached values if sleeplog is defined
|
||||
if (global.sleeplog) {
|
||||
sleeplog.resting = undefined;
|
||||
sleeplog.status = undefined;
|
||||
// reset cached ESS calculation values
|
||||
if (!sleeplog.powersaving) {
|
||||
sleeplog.ess_values = [];
|
||||
sleeplog.nomocount = 0;
|
||||
sleeplog.firstnomodate = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// define restart function (also use for initial starting)
|
||||
// define function to remove the kill listener and stop the service
|
||||
// https://github.com/espruino/BangleApps/issues/1445
|
||||
stop: function() {
|
||||
E.removeListener('kill', sleeplog.stopHandler);
|
||||
sleeplog.stopHandler();
|
||||
},
|
||||
|
||||
// define function to initialy start or restart the service
|
||||
start: function() {
|
||||
// exit on missing global object
|
||||
if (!global.sleeplog) return;
|
||||
// add kill listener
|
||||
E.on('kill', sleeplog.stopHandler);
|
||||
// add health listener if defined and
|
||||
if (sleeplog.health) Bangle.on('health', sleeplog.health);
|
||||
// add acceleration listener if defined and set status to unknown
|
||||
if (sleeplog.accel) Bangle.on('accel', sleeplog.accel);
|
||||
// add kill listener
|
||||
E.on('kill', () => sleeplog.stop());
|
||||
// read log since 5min ago and restore status to last known state or unknown
|
||||
sleeplog.status = (require("sleeplog").readLog(0, Date.now() - 3E5)[1] || [0, 0])[1]
|
||||
sleeplog.status = (require("sleeplog").readLog(0, Date.now() - 3E5)[1] || [0, 0])[1];
|
||||
// update resting according to status
|
||||
sleeplog.resting = sleeplog.status % 2;
|
||||
// write restored status to log
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id":"sleeplog",
|
||||
"name":"Sleep Log",
|
||||
"shortName": "SleepLog",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Log and view your sleeping habits. This app derived from SleepPhaseAlarm and uses also the principe of Estimation of Stationary Sleep-segments (ESS). It also provides a power saving mode using the built in movement calculation.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@
|
|||
storage.writeJSON(filename, settings);
|
||||
}
|
||||
|
||||
// define circulate function
|
||||
function circulate(min, max, value) {
|
||||
return value > max ? min : value < min ? max : value;
|
||||
}
|
||||
|
||||
// define function to change values that need a restart of the service
|
||||
function changeRestart() {
|
||||
require("sleeplog").setEnabled(settings.enabled, settings.logfile, settings.powersaving);
|
||||
|
|
@ -49,77 +44,79 @@
|
|||
title: "Sleep Log",
|
||||
selected: selected
|
||||
},
|
||||
"< Exit": () => load(),
|
||||
"Exit": () => load(),
|
||||
"< Back": () => back(),
|
||||
"BreakTod": {
|
||||
"Break Tod": {
|
||||
value: settings.breaktod,
|
||||
step: 1,
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(0, 23, v);
|
||||
writeSetting("breaktod", v);
|
||||
}
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: v => writeSetting("breaktod", v),
|
||||
},
|
||||
"MaxAwake": {
|
||||
"Max Awake": {
|
||||
value: settings.maxawake / 6E4,
|
||||
step: 5,
|
||||
min: 15,
|
||||
max: 120,
|
||||
wrap: true,
|
||||
format: v => v + "min",
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(15, 120, v);
|
||||
writeSetting("maxawake", v * 6E4);
|
||||
}
|
||||
onchange: v => writeSetting("maxawake", v * 6E4),
|
||||
},
|
||||
"MinConsec": {
|
||||
"Min Consec": {
|
||||
value: settings.minconsec / 6E4,
|
||||
step: 5,
|
||||
min: 15,
|
||||
max: 120,
|
||||
wrap: true,
|
||||
format: v => v + "min",
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(15, 120, v);
|
||||
writeSetting("minconsec", v * 6E4);
|
||||
}
|
||||
onchange: v => writeSetting("minconsec", v * 6E4),
|
||||
},
|
||||
"TempThresh": {
|
||||
"Temp Thresh": {
|
||||
value: settings.tempthresh,
|
||||
step: 0.5,
|
||||
min: 20,
|
||||
max: 40,
|
||||
wrap: true,
|
||||
format: v => v + "°C",
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(20, 40, v);
|
||||
writeSetting("tempthresh", v);
|
||||
}
|
||||
onchange: v => writeSetting("tempthresh", v),
|
||||
},
|
||||
"PowerSaving": {
|
||||
"Power Saving": {
|
||||
value: settings.powersaving,
|
||||
format: v => v ? "on" : "off",
|
||||
onchange: function(v) {
|
||||
settings.powersaving = v;
|
||||
changeRestart();
|
||||
showMain(7);
|
||||
// redraw menu with changed entries subsequent to onchange
|
||||
// https://github.com/espruino/Espruino/issues/2149
|
||||
setTimeout(showMain, 1, 6);
|
||||
}
|
||||
},
|
||||
"MaxMove": {
|
||||
"Max Move": {
|
||||
value: settings.maxmove,
|
||||
step: 1,
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(50, 200, v);
|
||||
writeSetting("maxmove", v);
|
||||
}
|
||||
min: 50,
|
||||
max: 200,
|
||||
wrap: true,
|
||||
onchange: v => writeSetting("maxmove", v),
|
||||
},
|
||||
"NoMoThresh": {
|
||||
"NoMo Thresh": {
|
||||
value: settings.nomothresh,
|
||||
step: 0.001,
|
||||
min: 0.006,
|
||||
max: 0.02,
|
||||
wrap: true,
|
||||
format: v => ("" + v).padEnd(5, "0"),
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(0.006, 0.02, v);
|
||||
writeSetting("nomothresh", v);
|
||||
}
|
||||
onchange: v => writeSetting("nomothresh", v),
|
||||
},
|
||||
"MinDuration": {
|
||||
"Min Duration": {
|
||||
value: Math.floor(settings.sleepthresh * stFactor),
|
||||
step: 1,
|
||||
min: 5,
|
||||
max: 15,
|
||||
wrap: true,
|
||||
format: v => v + "min",
|
||||
onchange: function(v) {
|
||||
this.value = v = circulate(5, 15, v);
|
||||
writeSetting("sleepthresh", Math.ceil(v / stFactor));
|
||||
}
|
||||
onchange: v => writeSetting("sleepthresh", Math.ceil(v / stFactor)),
|
||||
},
|
||||
"Enabled": {
|
||||
value: settings.enabled,
|
||||
|
|
@ -141,11 +138,8 @@
|
|||
}
|
||||
};
|
||||
// check power saving mode to delete unused entries
|
||||
(settings.powersaving ? ["NoMoThresh", "MinDuration"] : ["MaxMove"]).forEach(property => delete mainMenu[property]);
|
||||
(settings.powersaving ? ["NoMo Thresh", "Min Duration"] : ["Max Move"]).forEach(property => delete mainMenu[property]);
|
||||
var menu = E.showMenu(mainMenu);
|
||||
// workaround to display changed entries correct
|
||||
// https://github.com/espruino/Espruino/issues/2149
|
||||
if (selected) setTimeout(m => m.draw(), 1, menu);
|
||||
}
|
||||
|
||||
// draw main menu
|
||||
|
|
|
|||
Loading…
Reference in New Issue