1
0
Fork 0

Merge pull request #2679 from storm64/sleeplog

[sleeplog] Improving triggers and web interface
master
Gordon Williams 2023-04-12 10:28:07 +01:00 committed by GitHub
commit 14cc283679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 31 deletions

View File

@ -8,3 +8,5 @@
0.10beta: Add interface.html to view saved log data, add "View log" function for debugging log, send data for gadgetbridge, change caching for global getStats 0.10beta: Add interface.html to view saved log data, add "View log" function for debugging log, send data for gadgetbridge, change caching for global getStats
0.11: Prevent module not found error 0.11: Prevent module not found error
0.12: Improve README, option to add functions triggered by status changes or time periods, remove old log (<0.10) conversion 0.12: Improve README, option to add functions triggered by status changes or time periods, remove old log (<0.10) conversion
0.13: Prevent to stay in consecutive sleep if not worn, correct trigger calling, add trigger object itself as argument to the fn function
0.14: Add "Delete all logfiles before" to interface.html, display all logfiles in the interface

View File

@ -19,7 +19,7 @@ Logfiles are not removed on un-/reinstall to prevent data loss.
| Filename (* _example_) | Content | Removeable in | | Filename (* _example_) | Content | Removeable in |
|------------------------------|-----------------|-------------------| |------------------------------|-----------------|-------------------|
| `sleeplog.log (StorageFile)` | recent logfile | App Web Interface | | `sleeplog.log (StorageFile)` | recent logfile | App Web Interface |
| `sleeplog_1234.log`* | old logfiles | App Web Interface | | `sleeplog_1234.log`* | past logfiles | App Web Interface |
| `sleeplog_123456.csv`* | debugging files | Web IDE | | `sleeplog_123456.csv`* | debugging files | Web IDE |
@ -100,13 +100,20 @@ Logfiles are not removed on un-/reinstall to prevent data loss.
Available through the App Loader when your watch is connected. Available through the App Loader when your watch is connected.
- __view data__ - A list of all found logfiles with following options for each file:
Display the data to each timestamp in a table. - __view data__
- __save csv-file__ Display the data to each timestamp in a table.
Download a csv-file with the data to each timestamp. - __save csv-file__
The time format is chooseable beneath the file list. Download a csv-file with the data to each timestamp.
- __delete file__ The time format is chooseable beneath the file list.
Deletes the logfile from the watch. __Please backup your data first!__ - __delete file__
Deletes the logfile from the watch. __Please backup your data first!__
- __csv time format__
__JavaScript (milliseconds since 1970)__ /
_UNIX (seconds since 1970)_ /
_Office (days since 1900)_
- __delete all logfiles before__
Deletes all logfile before the given date from the watch. __Please backup your data first!__
--- ---
### Timestamps and Files ### Timestamps and Files
@ -184,11 +191,12 @@ if (typeof (global.sleeplog || {}).trigger === "object") {
from: 0, // 0 as default, in ms, first time fn will be called from: 0, // 0 as default, in ms, first time fn will be called
to: 24*60*60*1000, // 24h as default, in ms, last time fn will be called to: 24*60*60*1000, // 24h as default, in ms, last time fn will be called
// reference time to from & to is rounded to full minutes // reference time to from & to is rounded to full minutes
fn: function(data) { print(data); } // function to be executed fn: function(data, thisTriggerEntry) { print(data); } // function to be executed
}; };
} }
``` ```
The passed data object has the following properties:
The passed __data__ object has the following properties:
- timestamp: of the status change as date object, - timestamp: of the status change as date object,
(should be around 10min. before "now", the actual call of the function) (should be around 10min. before "now", the actual call of the function)
- status: value of the new status (0-4), - status: value of the new status (0-4),
@ -199,10 +207,16 @@ The passed data object has the following properties:
- prevConsecutive: if changed the value of the previous status (0-2) else undefined - prevConsecutive: if changed the value of the previous status (0-2) else undefined
If you want to use other variables or functions from the trigger object inside the trigger fn function, you will find them inside the __thisTriggerEntry__ object, as the this keyword is not working in this scenario. The function itself (the fn property) is not passed inside the thisTriggerEntry object.
--- ---
### Worth Mentioning ### Worth Mentioning
--- ---
#### To do list #### To do list
- Optimize interface.html:
- Open logfile through require("Storage") instead of require("sleeplog").
- Give feedback how much files have been deleted on "delete all logfiles before".
- Check translations. - Check translations.
- Add more functionallities to interface.html. - Add more functionallities to interface.html.
- Enable receiving data on the Gadgetbridge side + testing. - Enable receiving data on the Gadgetbridge side + testing.

View File

@ -235,6 +235,8 @@ if (sleeplog.conf.enabled) {
// reset consecutive status // reset consecutive status
data.consecutive = 0; data.consecutive = 0;
} }
// reset consecutive sleep if not worn
if (data.status === 1) this.consecutive = 1;
// check if consecutive unknown // check if consecutive unknown
if (!this.consecutive) { if (!this.consecutive) {
// check if long enough asleep or too long awake // check if long enough asleep or too long awake
@ -265,10 +267,13 @@ if (sleeplog.conf.enabled) {
// go through all triggers // go through all triggers
triggers.forEach(key => { triggers.forEach(key => {
// read entry to key // read entry to key
var entry = this.trigger[key]; let entry = this.trigger[key];
// set from and to values to default if unset
let from = entry.from || 0;
let to = entry.to || 24 * 60 * 60 * 1000;
// check if the event matches the entries requirements // check if the event matches the entries requirements
if (typeof entry.fn === "function" && (changed || !entry.onChange) && if (typeof entry.fn === "function" && (changed || !entry.onChange) &&
(entry.from || 0) <= time && (entry.to || 24 * 60 * 60 * 1000) >= time) (from <= to ? from <= time && time <= to : time <= to || from <= time))
// and call afterwards with status data // and call afterwards with status data
setTimeout(entry.fn, 100, { setTimeout(entry.fn, 100, {
timestamp: new Date(data.timestamp), timestamp: new Date(data.timestamp),
@ -276,7 +281,7 @@ if (sleeplog.conf.enabled) {
consecutive: data.consecutive, consecutive: data.consecutive,
prevStatus: data.status === this.status ? undefined : this.status, prevStatus: data.status === this.status ? undefined : this.status,
prevConsecutive: data.consecutive === this.consecutive ? undefined : this.consecutive prevConsecutive: data.consecutive === this.consecutive ? undefined : this.consecutive
}); }, (e => {delete e.fn; return e;})(entry.clone()));
}); });
} }

View File

@ -84,7 +84,7 @@ function readLog(date, callback) {
function deleteFile(filename, callback) { function deleteFile(filename, callback) {
if (window.confirm("Do you really want to remove " + filename)) { if (window.confirm("Do you really want to remove " + filename)) {
Util.showModal("Deleting..."); Util.showModal("Deleting " + filename + " ...");
if (filename.endsWith(" (StorageFile)")) { if (filename.endsWith(" (StorageFile)")) {
Util.eraseStorageFile(filename, () => { Util.eraseStorageFile(filename, () => {
Util.hideModal(); Util.hideModal();
@ -99,15 +99,29 @@ function deleteFile(filename, callback) {
} }
} }
function deleteBefore(dateString, callback) {
date = new Date(dateString);
if (window.confirm("Do you really want to remove all data before " + date.toLocaleDateString(undefined))) {
Util.showModal("Deleting all data before" + date.toLocaleDateString(undefined) + " ...");
Puck.eval(`require("Storage").list(/^sleeplog_\\d+.log$/)` +
`.filter(file => (parseInt(file.match(/\\d+/)[0]) + 0.25) * 12096E5 < ` + date.valueOf() + ` - 12096E5)` +
`.map(file => require("Storage").erase(file)).length`, count => {
Util.hideModal();
window.alert(count + " files deleted");
callback();
})
}
}
function viewFiles() { function viewFiles() {
Util.showModal("Loading..."); Util.showModal("Loading...");
domTable.innerHTML = ""; domTable.innerHTML = "";
Puck.eval(`require("Storage").list(/^sleeplog_\\d\\d\\d\\d\\.log$/)`, files => { Puck.eval(`require("Storage").list(/^sleeplog_\\d+.log$/)`, files => {
// add active log // add active log
files.push("" + Math.floor(Date.now() / 12096E5 - 0.25)); files.push("" + Math.floor(Date.now() / 12096E5 - 0.25));
files = files.map(file => { return { files = files.map(file => { return {
filename: file.length === 4 ? "sleeplog.log (StorageFile)" : file, filename: file.length === 4 ? "sleeplog.log (StorageFile)" : file,
date: (parseInt(file.match(/\d{4}/)[0]) + 0.25) * 12096E5 date: (parseInt(file.match(/\d+/)[0]) + 0.25) * 12096E5
}}); }});
files = files.sort((a, b) => a.date - b.date); files = files.sort((a, b) => a.date - b.date);
var html = ` var html = `
@ -146,10 +160,10 @@ function viewFiles() {
<div class="container"> <div class="container">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="col-3 col-sm-12"> <div class="col-sm-12">
<label class="form-label"><b>csv time format</b></label> <label class="form-label"><b>csv time format</b></label>
</div> </div>
<div class="col-9 col-sm-12"> <div class="col-sm-12">
<select class="form-select" id="csvTime"> <select class="form-select" id="csvTime">
<option>JavaScript (milliseconds since 1970)</option> <option>JavaScript (milliseconds since 1970)</option>
<option>UNIX (seconds since 1970)</option> <option>UNIX (seconds since 1970)</option>
@ -158,6 +172,23 @@ function viewFiles() {
</div> </div>
</div> </div>
</form> </form>
</div>
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-sm-12">
<label class="form-label"><b>Delete all logfiles before</b></label>
</div>
<div class="col-sm-10">
<input class="form-input" id="delBeforeDate" type="date" value="2022-01-01">
</div>
<div class="col-mx-auto">
<button class="btn tooltip btn-error" data-tooltip="delete old files" task="delBefore">
<i class="icon icon-delete"></i>
</button>
</div>
</div>
</form>
</div>`; </div>`;
domTable.innerHTML = html; domTable.innerHTML = html;
Util.hideModal(); Util.hideModal();
@ -166,12 +197,16 @@ function viewFiles() {
buttons[i].addEventListener("click", event => { buttons[i].addEventListener("click", event => {
var button = event.currentTarget; var button = event.currentTarget;
var task = button.getAttribute("task"); var task = button.getAttribute("task");
var filename = button.getAttribute("filename"); if (task === "delBefore") {
var date = button.getAttribute("date") - 0; deleteBefore(document.getElementById("delBeforeDate").value, () => viewFiles());
if (!task || !filename || !date) return; } else {
if (task === "view") readLog(date, logData => viewLog(logData, filename)); var filename = button.getAttribute("filename");
else if (task === "csv") readLog(date, logData => saveCSV(logData, date, date + 12096E5)); var date = button.getAttribute("date") - 0;
else if (task === "del") deleteFile(filename, () => viewFiles()); if (!task || !filename || !date) return;
if (task === "view") readLog(date, logData => viewLog(logData, filename));
else if (task === "csv") readLog(date, logData => saveCSV(logData, date, date + 12096E5));
else if (task === "del") deleteFile(filename, () => viewFiles());
}
}); });
} }
}); });

View File

@ -2,7 +2,7 @@
"id":"sleeplog", "id":"sleeplog",
"name":"Sleep Log", "name":"Sleep Log",
"shortName": "SleepLog", "shortName": "SleepLog",
"version": "0.12", "version": "0.14",
"description": "Log and view your sleeping habits. This app is using the built in movement calculation.", "description": "Log and view your sleeping habits. This app is using the built in movement calculation.",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",