mirror of https://github.com/espruino/BangleApps
parent
856164a58e
commit
02c4a1b9b7
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue