BangleApps/apps/sleepphasealarm/app.js

148 lines
4.9 KiB
JavaScript
Raw Normal View History

const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2
2020-05-27 05:50:48 +00:00
const alarms = require("Storage").readJSON("alarm.json",1)||[];
const active = alarms.filter(a=>a.on);
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
2020-05-29 15:18:12 +00:00
// Marko Borazio, Eugen Berlin, Nagihan Kücükyildiz, Philipp M. Scholl and Kristof Van Laerhoven, "Towards a Benchmark for Wearable Sleep Analysis with Inertial Wrist-worn Sensing Units", ICHI 2014, Verona, Italy, IEEE Press, 2014.
2020-05-27 05:50:48 +00:00
// https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en
//
// Function needs to be called for every measurement but returns a value at maximum once a second (see winwidth)
// start of sleep marker is delayed by sleepthresh due to continous data reading
const winwidth=13;
const nomothresh=0.006;
const sleepthresh=600;
var ess_values = [];
var slsnds = 0;
function calc_ess(val) {
2020-05-28 13:38:46 +00:00
ess_values.push(val);
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
if (ess_values.length == winwidth) {
// calculate standard deviation over ~1s
const mean = ess_values.reduce((prev,cur) => cur+prev) / ess_values.length;
const stddev = Math.sqrt(ess_values.map(val => Math.pow(val-mean,2)).reduce((prev,cur) => prev+cur)/ess_values.length);
ess_values = [];
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
// check for non-movement according to the threshold
const nonmot = stddev < nomothresh;
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
// amount of seconds within non-movement sections
if (nonmot) {
slsnds+=1;
if (slsnds >= sleepthresh) {
2022-03-21 10:03:32 +00:00
return true; // sleep
2020-05-28 13:38:46 +00:00
}
} else {
slsnds=0;
2022-03-21 10:03:32 +00:00
return false; // awake
2020-05-28 13:38:46 +00:00
}
}
2020-05-27 05:50:48 +00:00
}
// locate next alarm
var nextAlarm;
active.forEach(alarm => {
2020-05-28 13:38:46 +00:00
const now = new Date();
const alarmHour = alarm.hr/1;
const alarmMinute = Math.round((alarm.hr%1)*60);
var dateAlarm = new Date(now.getFullYear(), now.getMonth(), now.getDate(), alarmHour, alarmMinute);
if (dateAlarm < now) { // dateAlarm in the past, add 24h
dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
}
if (nextAlarm === undefined || dateAlarm < nextAlarm) {
nextAlarm = dateAlarm;
}
2020-05-27 05:50:48 +00:00
});
function drawString(s, y) { //# replaced x: always centered
g.reset(); //# moved up to prevent blue background
g.clearRect(0, y - 12, 239, y + 8); //# minimized upper+lower clearing
g.setFont("Vector", 20);
g.setFontAlign(0, 0); // align centered
g.drawString(s, g.getWidth() / 2, y); //# set x to center
2020-05-27 05:50:48 +00:00
}
function drawApp() {
g.clearRect(0,24,239,215); //# no problem
2020-05-28 13:38:46 +00:00
var alarmHour = nextAlarm.getHours();
var alarmMinute = nextAlarm.getMinutes();
if (alarmHour < 10) alarmHour = "0" + alarmHour;
if (alarmMinute < 10) alarmMinute = "0" + alarmMinute;
const s = "Alarm at " + alarmHour + ":" + alarmMinute + "\n\n"; //# make distinct to time
2020-05-28 13:38:46 +00:00
E.showMessage(s, "Sleep Phase Alarm");
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
function drawTime() {
if (Bangle.isLCDOn()) {
const now = new Date();
var nowHour = now.getHours();
var nowMinute = now.getMinutes();
var nowSecond = now.getSeconds();
if (nowHour < 10) nowHour = "0" + nowHour;
if (nowMinute < 10) nowMinute = "0" + nowMinute;
if (nowSecond < 10) nowSecond = "0" + nowSecond;
const time = nowHour + ":" + nowMinute + (BANGLEJS2 ? "" : ":" + nowSecond); //# hide seconds on bangle 2
drawString(time, BANGLEJS2 ? 85 : 105); //# remove x, adjust height for bangle 2 an newer firmware
2020-05-28 13:38:46 +00:00
}
}
2020-05-27 05:50:48 +00:00
if (BANGLEJS2) {
drawTime();
setTimeout(_ => {
drawTime();
setInterval(drawTime, 60000);
}, 60000 - Date.now() % 60000); //# every new minute on bangle 2
} else {
setInterval(drawTime, 500); // 2Hz
}
2020-05-27 05:50:48 +00:00
}
var buzzCount = 19;
function buzz() {
2021-03-23 18:56:34 +00:00
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
2020-05-28 13:38:46 +00:00
Bangle.setLCDPower(1);
Bangle.buzz().then(()=>{
if (buzzCount--) {
setTimeout(buzz, 500);
} else {
// back to main after finish
setTimeout(load, 1000);
}
});
2020-05-27 05:50:48 +00:00
}
// run
var minAlarm = new Date();
var measure = true;
if (nextAlarm !== undefined) {
Bangle.loadWidgets(); //# correct widget load draw order
2020-05-28 13:38:46 +00:00
Bangle.drawWidgets();
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
// minimum alert 30 minutes early
minAlarm.setTime(nextAlarm.getTime() - (30*60*1000));
setInterval(function() {
const now = new Date();
const acc = Bangle.getAccel().mag;
const swest = calc_ess(acc);
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
if (swest !== undefined) {
if (Bangle.isLCDOn()) {
drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180); //# remove x, adjust height
2020-05-28 13:38:46 +00:00
}
}
2020-05-27 05:50:48 +00:00
2020-05-28 13:38:46 +00:00
if (now >= nextAlarm) {
// The alarm widget should handle this one
setTimeout(load, 1000);
} else if (measure && now >= minAlarm && swest === false) {
buzz();
measure = false;
}
}, 80); // 12.5Hz
drawApp();
2020-05-27 05:50:48 +00:00
} else {
2020-05-28 13:38:46 +00:00
E.showMessage('No Alarm');
setTimeout(load, 1000);
2020-05-27 05:50:48 +00:00
}
// BTN2 to menu, BTN3 to main # on bangle 2 only BTN to main
if (!BANGLEJS2) setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
setWatch(() => load(), BANGLEJS2 ? BTN : BTN3, { repeat: false, edge: "falling" });