sleepphasealarm: add settings file
- Vibrate with configured pattern - Add setting to defer start of algorithmmaster
parent
418d14a156
commit
c28d85ad1f
|
|
@ -7,3 +7,5 @@
|
||||||
use Layout library and display ETA
|
use Layout library and display ETA
|
||||||
0.07: Add check for day of week
|
0.07: Add check for day of week
|
||||||
0.08: Update to new time_utils module
|
0.08: Update to new time_utils module
|
||||||
|
0.09: Vibrate with configured pattern
|
||||||
|
Add setting to defer start of algorithm
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,19 @@ The alarm must be in the next 24h.
|
||||||
|
|
||||||
The display shows:
|
The display shows:
|
||||||
|
|
||||||
- the current time
|
- The current time.
|
||||||
- time of the next alarm or timer
|
- Time of the next alarm or timer.
|
||||||
- time difference between current time and alarm time (ETA)
|
- Time difference between current time and alarm time (ETA).
|
||||||
- current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging
|
- Current state of the ESS algorithm, "Sleep" or "Awake", useful for debugging. State can also be "Deferred", see the "Run before alarm"-option.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
* **Keep alarm enabled**
|
||||||
|
- Yes: (default) Alert will stay enabled, e.g. for an alarm at 7:00 the clock will buzz at 6:45 (the calculated time from the ESS algorithm) and again at 7:00.
|
||||||
|
- No: No action at configured alarm time from scheduler.
|
||||||
|
* **Run before alarm**
|
||||||
|
- disabled: (default) The ESS algorithm starts immediately when the application starts.
|
||||||
|
- 1..23: The ESS algorithm starts the configured time before the alarm. E.g. when set to 1h for an alarm at 7:00 the ESS algorithm will start at 6:00. This improves battery life.
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,18 @@
|
||||||
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
const BANGLEJS2 = process.env.HWVERSION == 2; // check for bangle 2
|
||||||
|
const CONFIGFILE = "sleepphasealarm.json";
|
||||||
const Layout = require("Layout");
|
const Layout = require("Layout");
|
||||||
const locale = require('locale');
|
const locale = require('locale');
|
||||||
const alarms = require("Storage").readJSON("sched.json",1) || [];
|
const alarms = require("Storage").readJSON("sched.json",1) || [];
|
||||||
const config = require("Storage").readJSON("sleepphasealarm.json",1) || {logs: []};
|
const config = Object.assign({
|
||||||
|
logs: [], // array of length 31 with one entry for each day of month
|
||||||
|
settings: {
|
||||||
|
startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time
|
||||||
|
disableAlarm: false,
|
||||||
|
}
|
||||||
|
}, require("Storage").readJSON(CONFIGFILE,1) || {});
|
||||||
const active = alarms.filter(a=>a.on);
|
const active = alarms.filter(a=>a.on);
|
||||||
|
const schedSettings = require("sched").getSettings();
|
||||||
|
let buzzCount = schedSettings.buzzCount;
|
||||||
let logs = [];
|
let logs = [];
|
||||||
|
|
||||||
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
|
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
|
||||||
|
|
@ -43,7 +52,8 @@ function calc_ess(acc_magn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// locate next alarm
|
// locate next alarm
|
||||||
var nextAlarm;
|
var nextAlarmDate;
|
||||||
|
var nextAlarmConfig;
|
||||||
active.forEach(alarm => {
|
active.forEach(alarm => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const time = require("time_utils").decodeTime(alarm.t);
|
const time = require("time_utils").decodeTime(alarm.t);
|
||||||
|
|
@ -52,8 +62,9 @@ active.forEach(alarm => {
|
||||||
dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
|
dateAlarm.setTime(dateAlarm.getTime() + (24*60*60*1000));
|
||||||
}
|
}
|
||||||
if ((alarm.dow >> dateAlarm.getDay()) & 1) { // check valid day of week
|
if ((alarm.dow >> dateAlarm.getDay()) & 1) { // check valid day of week
|
||||||
if (nextAlarm === undefined || dateAlarm < nextAlarm) {
|
if (nextAlarmDate === undefined || dateAlarm < nextAlarmDate) {
|
||||||
nextAlarm = dateAlarm;
|
nextAlarmDate = dateAlarm;
|
||||||
|
nextAlarmConfig = alarm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -69,8 +80,8 @@ var layout = new Layout({
|
||||||
}, {lazy:true});
|
}, {lazy:true});
|
||||||
|
|
||||||
function drawApp() {
|
function drawApp() {
|
||||||
var alarmHour = nextAlarm.getHours();
|
var alarmHour = nextAlarmDate.getHours();
|
||||||
var alarmMinute = nextAlarm.getMinutes();
|
var alarmMinute = nextAlarmDate.getMinutes();
|
||||||
if (alarmHour < 10) alarmHour = "0" + alarmHour;
|
if (alarmHour < 10) alarmHour = "0" + alarmHour;
|
||||||
if (alarmMinute < 10) alarmMinute = "0" + alarmMinute;
|
if (alarmMinute < 10) alarmMinute = "0" + alarmMinute;
|
||||||
layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute;
|
layout.alarm_date.label = "Alarm at " + alarmHour + ":" + alarmMinute;
|
||||||
|
|
@ -80,7 +91,7 @@ function drawApp() {
|
||||||
if (Bangle.isLCDOn()) {
|
if (Bangle.isLCDOn()) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2
|
layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2
|
||||||
const diff = nextAlarm - now;
|
const diff = nextAlarmDate - now;
|
||||||
const diffHour = Math.floor((diff % 86400000) / 3600000).toString();
|
const diffHour = Math.floor((diff % 86400000) / 3600000).toString();
|
||||||
const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString();
|
const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString();
|
||||||
layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0');
|
layout.eta.label = "ETA: -"+ diffHour + ":" + diffMinutes.padStart(2, '0');
|
||||||
|
|
@ -92,38 +103,41 @@ function drawApp() {
|
||||||
setInterval(drawTime, 500); // 2Hz
|
setInterval(drawTime, 500); // 2Hz
|
||||||
}
|
}
|
||||||
|
|
||||||
var buzzCount = 19;
|
|
||||||
function buzz() {
|
function buzz() {
|
||||||
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
|
if ((require('Storage').readJSON('setting.json',1)||{}).quiet>1) return; // total silence
|
||||||
Bangle.setLCDPower(1);
|
Bangle.setLCDPower(1);
|
||||||
Bangle.buzz().then(()=>{
|
require("buzz").pattern(nextAlarmConfig.vibrate || ";");
|
||||||
if (buzzCount--) {
|
if (buzzCount--) {
|
||||||
setTimeout(buzz, 500);
|
setTimeout(buzz, schedSettings.buzzIntervalMillis);
|
||||||
} else {
|
} else {
|
||||||
// back to main after finish
|
// back to main after finish
|
||||||
setTimeout(load, 1000);
|
setTimeout(load, 1000);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLog(time, type) {
|
function addLog(time, type) {
|
||||||
logs.push({time: time, type: type});
|
logs.push({time: time, type: type});
|
||||||
require("Storage").writeJSON("sleepphasealarm.json", config);
|
if (logs.length > 1) { // Do not write if there is only one state
|
||||||
|
require("Storage").writeJSON(CONFIGFILE, config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// run
|
// run
|
||||||
var minAlarm = new Date();
|
var minAlarm = new Date();
|
||||||
var measure = true;
|
var measure = true;
|
||||||
if (nextAlarm !== undefined) {
|
if (nextAlarmDate !== undefined) {
|
||||||
config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month
|
config.logs[nextAlarmDate.getDate()] = []; // overwrite log on each day of month
|
||||||
logs = config.logs[nextAlarm.getDate()];
|
logs = config.logs[nextAlarmDate.getDate()];
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
let swest_last;
|
let swest_last;
|
||||||
|
|
||||||
// minimum alert 30 minutes early
|
// minimum alert 30 minutes early
|
||||||
minAlarm.setTime(nextAlarm.getTime() - (30*60*1000));
|
minAlarm.setTime(nextAlarmDate.getTime() - (30*60*1000));
|
||||||
|
run = () => {
|
||||||
|
layout.state.label = "Start";
|
||||||
|
layout.render();
|
||||||
Bangle.on('accel', (accelData) => { // 12.5Hz
|
Bangle.on('accel', (accelData) => { // 12.5Hz
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const acc = accelData.mag;
|
const acc = accelData.mag;
|
||||||
|
|
@ -145,17 +159,35 @@ if (nextAlarm !== undefined) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now >= nextAlarm) {
|
if (now >= nextAlarmDate) {
|
||||||
// The alarm widget should handle this one
|
// The alarm widget should handle this one
|
||||||
addLog(now, "alarm");
|
addLog(now, "alarm");
|
||||||
setTimeout(load, 1000);
|
setTimeout(load, 1000);
|
||||||
} else if (measure && now >= minAlarm && swest === false) {
|
} else if (measure && now >= minAlarm && swest_last === false) {
|
||||||
addLog(now, "alarm");
|
addLog(now, "alarm");
|
||||||
buzz();
|
buzz();
|
||||||
measure = false;
|
measure = false;
|
||||||
|
if (config.settings.disableAlarm) {
|
||||||
|
// disable alarm for scheduler
|
||||||
|
nextAlarmConfig.last = now.getDate();
|
||||||
|
require("Storage").writeJSON("sched.json", alarms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
drawApp();
|
drawApp();
|
||||||
|
if (config.settings.startBeforeAlarm === 0) {
|
||||||
|
// Start immediately
|
||||||
|
run();
|
||||||
|
} else {
|
||||||
|
// defer start
|
||||||
|
layout.state.label = "Deferred";
|
||||||
|
layout.render();
|
||||||
|
const diff = nextAlarmDate - Date.now();
|
||||||
|
let timeout = diff-config.settings.startBeforeAlarm*60*60*1000;
|
||||||
|
if (timeout < 0) timeout = 0;
|
||||||
|
setTimeout(run, timeout);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
E.showMessage('No Alarm');
|
E.showMessage('No Alarm');
|
||||||
setTimeout(load, 1000);
|
setTimeout(load, 1000);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chart.js@2.8.0/dist/Chart.min.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>Please select a wakeup day:</p>
|
<p>Please select a wakeup day:</p>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "sleepphasealarm",
|
"id": "sleepphasealarm",
|
||||||
"name": "SleepPhaseAlarm",
|
"name": "SleepPhaseAlarm",
|
||||||
"shortName": "SleepPhaseAlarm",
|
"shortName": "SleepPhaseAlarm",
|
||||||
"version": "0.08",
|
"version": "0.09",
|
||||||
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "alarm",
|
"tags": "alarm",
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {"scheduler":"type"},
|
"dependencies": {"scheduler":"type"},
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"sleepphasealarm.app.js","url":"app.js"},
|
{"name":"sleepphasealarm.app.js","url":"app.js"},
|
||||||
|
{"name":"sleepphasealarm.settings.js","url":"settings.js"},
|
||||||
{"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
|
{"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"sleepphasealarm.json","storageFile":true}],
|
"data": [{"name":"sleepphasealarm.json","storageFile":true}],
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
(function(back) {
|
||||||
|
const CONFIGFILE = "sleepphasealarm.json";
|
||||||
|
// Load settings
|
||||||
|
const config = Object.assign({
|
||||||
|
logs: [], // array of length 31 with one entry for each day of month
|
||||||
|
settings: {
|
||||||
|
startBeforeAlarm: 0, // 0 = start immediately, 1..23 = start 1h..23h before alarm time
|
||||||
|
disableAlarm: false,
|
||||||
|
}
|
||||||
|
}, require("Storage").readJSON(CONFIGFILE,1) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(CONFIGFILE, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the menu
|
||||||
|
E.showMenu({
|
||||||
|
"" : { "title" : "SleepPhaseAlarm" },
|
||||||
|
'Keep alarm enabled': {
|
||||||
|
value: !!config.settings.disableAlarm,
|
||||||
|
format: v => v?"No":"Yes",
|
||||||
|
onchange: v => {
|
||||||
|
config.settings.disableAlarm = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
}, "< Back" : () => back(),
|
||||||
|
'Run before alarm': {
|
||||||
|
format: v => v === 0 ? 'disabled' : v+'h',
|
||||||
|
value: config.settings.startBeforeAlarm,
|
||||||
|
min: 0, max: 23,
|
||||||
|
onchange: v => {
|
||||||
|
config.settings.startBeforeAlarm = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue