mirror of https://github.com/espruino/BangleApps
235 lines
7.6 KiB
JavaScript
235 lines
7.6 KiB
JavaScript
// set storage and define settings
|
|
var storage = require("Storage");
|
|
var breaktod, maxawake, minconsec;
|
|
|
|
// read required settings from storage
|
|
function readSettings(settings) {
|
|
breaktod = settings.breaktod || (settings.breaktod === 0 ? 0 : 10); // time of day when to start/end graphs
|
|
maxawake = settings.maxawake || 36E5; // 60min in ms
|
|
minconsec = settings.minconsec || 18E5; // 30min in ms
|
|
}
|
|
|
|
// define draw log function
|
|
function drawLog(topY, viewUntil) {
|
|
// set default view time
|
|
viewUntil = viewUntil || Date();
|
|
|
|
// define parameters
|
|
var statusValue = [0, 0.4, 0.6, 1]; // unknown, not worn, awake, sleeping, consecutive sleep
|
|
var statusColor = [0, 63488, 2016, 32799, 31]; // black, red, green, violet, blue
|
|
var period = 432E5; // 12h
|
|
var graphHeight = 18;
|
|
var labelHeight = 12;
|
|
var width = g.getWidth();
|
|
var timestamp0 = viewUntil.valueOf() - period;
|
|
var y = topY + graphHeight;
|
|
|
|
// read 12h wide log
|
|
var log = require("sleeplog").readLog(0, timestamp0, viewUntil.valueOf());
|
|
|
|
// format log array if not empty
|
|
if (log.length) {
|
|
// if the period goes into the future add unknown status at the beginning
|
|
if (viewUntil > Date()) log.unshift([Date().valueOf(), 0]);
|
|
|
|
// check if the period goes earlier than logged data
|
|
if (log[log.length - 1][0] < timestamp0) {
|
|
// set time of last entry according to period
|
|
log[log.length - 1][0] = timestamp0;
|
|
} else {
|
|
// add entry with unknown status at the end
|
|
log.push([timestamp0, 0]);
|
|
}
|
|
|
|
// remap each element to [status, relative beginning, relative end, duration]
|
|
log = log.map((element, index) => [
|
|
element[1],
|
|
element[0] - timestamp0,
|
|
(log[index - 1] || [viewUntil.valueOf()])[0] - timestamp0,
|
|
(log[index - 1] || [viewUntil.valueOf()])[0] - element[0]
|
|
]);
|
|
|
|
// start with the oldest entry to build graph left to right
|
|
log.reverse();
|
|
}
|
|
|
|
// clear area
|
|
g.reset().clearRect(0, topY, width, y + labelHeight);
|
|
// draw x axis
|
|
g.drawLine(0, y + 1, width, y + 1);
|
|
// draw x label
|
|
var hours = period / 36E5;
|
|
var stepwidth = width / hours;
|
|
var startHour = 24 + viewUntil.getHours() - hours;
|
|
for (var x = 0; x < hours; x++) {
|
|
g.fillRect(x * stepwidth, y + 2, x * stepwidth, y + 4);
|
|
g.setFontAlign(-1, -1).setFont("6x8")
|
|
.drawString((startHour + x) % 24, x * stepwidth + 1, y + 6);
|
|
}
|
|
|
|
// define variables for sleep calculation
|
|
var consecutive = 0;
|
|
var output = [0, 0]; // [estimated, true]
|
|
var i, nosleepduration;
|
|
|
|
// draw graph
|
|
log.forEach((element, index) => {
|
|
// set bar color depending on type
|
|
g.setColor(statusColor[consecutive ? 4 : element[0]]);
|
|
|
|
// check for sleeping status
|
|
if (element[0] === 3) {
|
|
// count true sleeping hours
|
|
output[1] += element[3];
|
|
// count duration of subsequent non sleeping periods
|
|
i = index + 1;
|
|
nosleepduration = 0;
|
|
while (log[i] !== undefined && log[i][0] < 3 && nosleepduration < maxawake) {
|
|
nosleepduration += log[i++][3];
|
|
}
|
|
// check if counted duration lower than threshold to start/stop counting
|
|
if (log[i] !== undefined && nosleepduration < maxawake) {
|
|
// start counting consecutive sleeping hours
|
|
consecutive += element[3];
|
|
// correct color to match consecutive sleeping
|
|
g.setColor(statusColor[4]);
|
|
} else {
|
|
// check if counted consecutive sleeping greater then threshold
|
|
if (consecutive >= minconsec) {
|
|
// write verified consecutive sleeping hours to output
|
|
output[0] += consecutive + element[3];
|
|
} else {
|
|
// correct color to display a canceled consecutive sleeping period
|
|
g.setColor(statusColor[3]);
|
|
}
|
|
// stop counting consecutive sleeping hours
|
|
consecutive = 0;
|
|
}
|
|
} else {
|
|
// count durations of non sleeping periods for consecutive sleeping
|
|
if (consecutive) consecutive += element[3];
|
|
}
|
|
|
|
// calculate points
|
|
var x1 = Math.ceil(element[1] / period * width);
|
|
var x2 = Math.floor(element[2] / period * width);
|
|
var y2 = y - graphHeight * statusValue[element[0]];
|
|
// draw bar
|
|
g.clearRect(x1, topY, x2, y);
|
|
g.fillRect(x1, y, x2, y2).reset();
|
|
if (y !== y2) g.fillRect(x1, y2, x2, y2);
|
|
});
|
|
|
|
// clear variables
|
|
log = undefined;
|
|
|
|
// return convert output into minutes
|
|
return output.map(value => value /= 6E4);
|
|
}
|
|
|
|
// define function to draw the analysis
|
|
function drawAnalysis(toDate) {
|
|
//var t0 = Date.now();
|
|
|
|
// get width
|
|
var width = g.getWidth();
|
|
|
|
// define variable for sleep calculation
|
|
var outputs = [0, 0]; // [estimated, true]
|
|
|
|
// 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(144, Date(toDate.valueOf() - 432E5)).forEach(
|
|
(value, index) => outputs[index] += value);
|
|
|
|
// draw outputs
|
|
g.reset(); // area: 0, 70, width, 105
|
|
g.setFont("6x8").setFontAlign(-1, -1);
|
|
g.drawString("consecutive\nsleeping", 10, 70);
|
|
g.drawString("true\nsleeping", 10, 90);
|
|
g.setFont("12x20").setFontAlign(1, -1);
|
|
g.drawString(Math.floor(outputs[0] / 60) + "h " +
|
|
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 states: service, loggging and powersaving
|
|
if (!sleeplog.enabled) {
|
|
// draw disabled service icon
|
|
g.setColor(1, 0, 0)
|
|
.drawImage(atob("FBSBAAH4AH/gH/+D//w/n8f5/nud7znP85z/f+/3/v8/z/P895+efGPj4Hw//8H/+Af+AB+A"), 2, 36);
|
|
} else if (!sleeplog.logfile) {
|
|
// draw disabled log icon
|
|
g.reset().drawImage(atob("EA6BAM//z/8AAAAAz//P/wAAAADP/8//AAAAAM//z/8="), 4, 40)
|
|
.setColor(1, 0, 0).fillPoly([2, 38, 4, 36, 22, 54, 20, 56]);
|
|
}
|
|
// draw power saving icon
|
|
if (sleeplog.powersaving) g.setColor(0, 1, 0)
|
|
.drawImage(atob("FBSBAAAAcAD/AH/wP/4P/+H//h//4//+fv/nj/7x/88//Of/jH/4j/8I/+Af+AH+AD8AA4AA"), width - 22, 36);
|
|
|
|
// draw headline
|
|
g.reset().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, 100, toDate);
|
|
}
|
|
|
|
// define function to draw and setup UI
|
|
function startApp() {
|
|
readSettings(storage.readJSON("sleeplog.json", true) || {});
|
|
drawNightTo(prevDays);
|
|
Bangle.setUI("leftright", (cb) => {
|
|
if (!cb) {
|
|
eval(storage.read("sleeplog.settings.js"))(startApp);
|
|
} else if (prevDays + cb >= -1) {
|
|
drawNightTo((prevDays += cb));
|
|
}
|
|
});
|
|
}
|
|
|
|
// define day to display and analysis timeout id
|
|
var prevDays = 0;
|
|
var ATID;
|
|
|
|
// setup app
|
|
g.clear();
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
// start app
|
|
startApp();
|