sleepphasealarm

add logging, display with chart.js
master
Erik Andresen 2022-04-22 13:06:42 +02:00
parent 856164a58e
commit 02c4a1b9b7
4 changed files with 131 additions and 5 deletions

View File

@ -3,3 +3,4 @@
0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time 0.03: Add compatibility for Bangle.js 2 and new firmware, added "Alarm at " for the alarm time
0.04: Read alarms from new scheduling library, account for higher acceleration sensor noise on Bangle.js 2 0.04: Read alarms from new scheduling library, account for higher acceleration sensor noise on Bangle.js 2
0.05: Refactor decodeTime() to scheduling library 0.05: Refactor decodeTime() to scheduling library
0.06: Add logging

View File

@ -1,6 +1,8 @@
const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2 const BANGLEJS2 = process.env.HWVERSION == 2; //# check for bangle 2
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 active = alarms.filter(a=>a.on); const active = alarms.filter(a=>a.on);
let logs = [];
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS): // Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
// 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. // 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.
@ -108,12 +110,20 @@ function buzz() {
}); });
} }
function addLog(time, type) {
logs.push({time: time, type: type});
require("Storage").writeJSON("sleepphasealarm.json", config);
}
// run // run
var minAlarm = new Date(); var minAlarm = new Date();
var measure = true; var measure = true;
if (nextAlarm !== undefined) { if (nextAlarm !== undefined) {
Bangle.loadWidgets(); //# correct widget load draw order config.logs[nextAlarm.getDate()] = []; // overwrite log on each day of month
logs = config.logs[nextAlarm.getDate()];
Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
let swest_last;
// minimum alert 30 minutes early // minimum alert 30 minutes early
minAlarm.setTime(nextAlarm.getTime() - (30*60*1000)); minAlarm.setTime(nextAlarm.getTime() - (30*60*1000));
@ -124,7 +134,16 @@ if (nextAlarm !== undefined) {
if (swest !== undefined) { if (swest !== undefined) {
if (Bangle.isLCDOn()) { if (Bangle.isLCDOn()) {
drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180); //# remove x, adjust height drawString(swest ? "Sleep" : "Awake", BANGLEJS2 ? 150 : 180);
}
// log
if (swest_last != swest) {
if (swest) {
addLog(new Date(Date.now() - sleepthresh*13/12.5*1000), "sleep"); // calculate begin of no motion phase, 13 values/second at 12.5Hz
} else {
addLog(new Date(), "awake");
}
swest_last = swest;
} }
} }
@ -132,6 +151,7 @@ if (nextAlarm !== undefined) {
// The alarm widget should handle this one // The alarm widget should handle this one
setTimeout(load, 1000); setTimeout(load, 1000);
} else if (measure && now >= minAlarm && swest === false) { } else if (measure && now >= minAlarm && swest === false) {
addLog(new Date(), "alarm");
buzz(); buzz();
measure = false; measure = false;
} }

View File

@ -0,0 +1,103 @@
<html>
<head>
<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>
<body>
<p>Please select a wakeup day:</p>
<div class="form-group">
<select id="day" disabled class="form-select">
<option selected disabled>No day</option>
</select>
</div>
<div class="chart-container">
<canvas id="sleepChart"></canvas>
</div>
<script src="../../core/lib/interface.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@2.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<script>
function getData() {
const select = document.getElementById("day");
const ctx = document.getElementById('sleepChart').getContext('2d');
const yTicks = ["sleep", "awake", "alarm"];
// show loading window
Util.showModal("Loading...");
// get the data
Util.readStorageFile('sleepphasealarm.json',data=>{
data = '{"logs":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[{"time":"2022-04-21T19:03:02.130Z","type":"awake"},{"time":"2022-04-21T19:03:35.219Z","type":"sleep"},{"time":"2022-04-21T19:16:08.958Z","type":"awake"},{"time":"2022-04-21T19:16:08.994Z","type":"alarm"}]]}';
let logs = JSON.parse(data || "{}")?.logs || [];
// remove window
Util.hideModal();
logs = logs.filter(log => log != null);
logs.forEach((log, i) => {
const timeStr = log.filter(entry => entry.type === "alarm")[0]?.time;
if (timeStr) {
const date = new Date(timeStr);
let option = document.createElement("option");
option.text = date.toLocaleDateString();
option.value = i;
select.add(option);
select.disabled = false;
}
});
const chart = new Chart(ctx, {
type: 'line',
labels: [],
data: {
datasets: [
{
label: "No date selected",
data: [],
fill: false,
stepped: true,
borderColor: '#ff0000',
}
]
},
options: {
scales: {
x: {
type: 'time',
time: {
displayFormats: {
millisecond: 'HH:mm:ss.SSS',
second: 'HH:mm:ss',
minute: 'HH:mm',
hour: 'HH',
day: 'D MMM.',
},
},
},
y: {ticks: {callback: (value, index, values) => yTicks[value]}},
},
plugins: {
tooltip: {
enabled: false
},
}
}
});
select.onchange = () => {
const log = logs[select.value];
chart.data.labels = log.map(entry => new Date(entry.time));
chart.data.datasets[0].data = log.map(entry => yTicks.indexOf(entry.type));
const timeStr = log.filter(entry => entry.type === "alarm")[0]?.time;
chart.data.datasets[0].label = new Date(timeStr).toLocaleDateString();
chart.update();
}
});
}
// Called when app starts
function onInit() {
getData();
}
</script>
</body>
</html>

View File

@ -2,7 +2,7 @@
"id": "sleepphasealarm", "id": "sleepphasealarm",
"name": "SleepPhaseAlarm", "name": "SleepPhaseAlarm",
"shortName": "SleepPhaseAlarm", "shortName": "SleepPhaseAlarm",
"version": "0.05", "version": "0.06",
"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,5 +11,7 @@
"storage": [ "storage": [
{"name":"sleepphasealarm.app.js","url":"app.js"}, {"name":"sleepphasealarm.app.js","url":"app.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}],
"interface": "interface.html"
} }