sleepphasealarm

add logging, display with chart.js
pull/1769/head
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.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.06: Add logging

View File

@ -1,6 +1,8 @@
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);
let logs = [];
// 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.
@ -108,12 +110,20 @@ function buzz() {
});
}
function addLog(time, type) {
logs.push({time: time, type: type});
require("Storage").writeJSON("sleepphasealarm.json", config);
}
// run
var minAlarm = new Date();
var measure = true;
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();
let swest_last;
// minimum alert 30 minutes early
minAlarm.setTime(nextAlarm.getTime() - (30*60*1000));
@ -124,7 +134,16 @@ if (nextAlarm !== undefined) {
if (swest !== undefined) {
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
setTimeout(load, 1000);
} else if (measure && now >= minAlarm && swest === false) {
addLog(new Date(), "alarm");
buzz();
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",
"name": "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.",
"icon": "app.png",
"tags": "alarm",
@ -11,5 +11,7 @@
"storage": [
{"name":"sleepphasealarm.app.js","url":"app.js"},
{"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
]
],
"data": [{"name":"sleepphasealarm.json","storageFile":true}],
"interface": "interface.html"
}