mirror of https://github.com/espruino/BangleApps
Merge pull request #2322 from storm64/sleeplog
[sleeplog] Improved README + option to add functions triggered by sleeplogpull/2333/head
commit
0cc01f584b
|
@ -7,3 +7,4 @@
|
|||
0.10: Complete rework off this app!
|
||||
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.12: Improve README, option to add functions triggered by status changes or time periods, remove old log (<0.10) conversion
|
|
@ -6,6 +6,14 @@ This app logs and displays the following states:
|
|||
|
||||
It is using the built in movement calculation to decide your sleeping state. While charging it is assumed that you are not wearing the watch and if the status changes to _deep sleep_ the internal heartrate sensor is used to detect if you are wearing the watch.
|
||||
|
||||
#### Explanations
|
||||
* __Detection of Sleep__
|
||||
The movement value of bangle's build in health event that is triggered every 10 minutes is checked against the thresholds for light and deep sleep. If the measured movement is lower or equal to the __Deep Sleep__-threshold a deep sleep phase is detected for the last 10 minutes. If the threshold is exceeded but not the __Light Sleep__-threshold than the last timeperiod is detected as light sleep phase. On exceeding even this threshold it is assumed that you were awake.
|
||||
* __True Sleep__
|
||||
The true sleep value is a simple addition of all registered sleeping periods.
|
||||
* __Consecutive Sleep__
|
||||
In addition the consecutive sleep value tries to predict the complete time you were asleep, even the very light sleeping periods when an awake period is detected based on the registered movements. All periods after a sleeping period will be summarized until the first following non sleeping period that is longer then the maximal awake duration (__Max Awake__). If this sum is lower than the minimal consecutive sleep duration (__Min Consecutive__) it is not considered, otherwise it will be added to the consecutive sleep value.
|
||||
|
||||
Logfiles are not removed on un-/reinstall to prevent data loss.
|
||||
|
||||
| Filename (* _example_) | Content | Removeable in |
|
||||
|
@ -16,10 +24,10 @@ Logfiles are not removed on un-/reinstall to prevent data loss.
|
|||
|
||||
|
||||
---
|
||||
### App Usage
|
||||
### Main App Usage
|
||||
---
|
||||
|
||||
#### On the main app screen:
|
||||
#### Controls:
|
||||
- __swipe left & right__
|
||||
to change the displayed day
|
||||
- __touch the "title"__ (e.g. `Night to Fri 20/05/2022`)
|
||||
|
@ -32,7 +40,21 @@ Logfiles are not removed on un-/reinstall to prevent data loss.
|
|||
- __use back button widget__ (upper left corner)
|
||||
exit the app
|
||||
|
||||
#### Inside the settings:
|
||||
#### View:
|
||||
| Status | Color | Height |
|
||||
|-------------|:------:|----------:|
|
||||
| unknown | black | 0% |
|
||||
| not worn | red | 40% |
|
||||
| awake | green | 60% |
|
||||
| light sleep | cyan | 80% |
|
||||
| deep sleep | blue | 100% |
|
||||
| consecutive | violet | as status |
|
||||
|
||||
|
||||
---
|
||||
### Settings Usage
|
||||
---
|
||||
|
||||
- __Thresholds__ submenu
|
||||
Changes take effect from now on, not retrospective!
|
||||
- __Max Awake__ | maximal awake duration
|
||||
|
@ -87,7 +109,7 @@ Available through the App Loader when your watch is connected.
|
|||
Deletes the logfile from the watch. __Please backup your data first!__
|
||||
|
||||
---
|
||||
### Timestamps and files
|
||||
### Timestamps and Files
|
||||
---
|
||||
|
||||
1. externally visible/usable timestamps (in `global.sleeplog`) are formatted as Bangle timestamps:
|
||||
|
@ -110,8 +132,10 @@ Available through the App Loader when your watch is connected.
|
|||
|
||||
|
||||
---
|
||||
### Access statistics (developer information)
|
||||
### Developer Information
|
||||
---
|
||||
|
||||
#### Access statistics
|
||||
- Last Asleep Time [Date]:
|
||||
`Date(sleeplog.awakeSince)`
|
||||
- Last Awake Duration [ms]:
|
||||
|
@ -149,6 +173,31 @@ Available through the App Loader when your watch is connected.
|
|||
require("sleeplog").getStats(0, 0, require("sleeplog").readLog());
|
||||
```
|
||||
|
||||
#### Add functions triggered by status changes or inside a specified time period
|
||||
With the following code it is possible to add functions that will be called every 10 minutes after new movement data when meeting the specified parameters on each :
|
||||
```
|
||||
// first ensure that the sleeplog trigger object is available (sleeplog is enabled)
|
||||
if (typeof (global.sleeplog || {}).trigger === "object") {
|
||||
// then add your parameters with the function to call as object into the trigger object
|
||||
sleeplog.trigger["my app name"] = {
|
||||
onChange: false, // false as default, if true call fn only on a status change
|
||||
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
|
||||
// reference time to from & to is rounded to full minutes
|
||||
fn: function(data) { print(data); } // function to be executed
|
||||
};
|
||||
}
|
||||
```
|
||||
The passed data object has the following properties:
|
||||
- timestamp: of the status change as date object,
|
||||
(should be around 10min. before "now", the actual call of the function)
|
||||
- status: value of the new status (0-4),
|
||||
(0 = unknown, 1 = not worn, 2 = awake, 3 = light sleep, 4 = deep sleep)
|
||||
- consecutive: value of the new status (0-2),
|
||||
(0 = unknown, 1 = no consecutive sleep, 2 = consecutive sleep)
|
||||
- prevStatus: if changed the value of the previous status (0-4) else undefined,
|
||||
- prevConsecutive: if changed the value of the previous status (0-2) else undefined
|
||||
|
||||
|
||||
---
|
||||
### Worth Mentioning
|
||||
|
@ -156,14 +205,14 @@ Available through the App Loader when your watch is connected.
|
|||
#### To do list
|
||||
- Check translations.
|
||||
- Add more functionallities to interface.html.
|
||||
- Enable recieving data on the Gadgetbridge side + testing.
|
||||
- Enable receiving data on the Gadgetbridge side + testing.
|
||||
__Help appreciated!__
|
||||
|
||||
#### Requests, Bugs and Feedback
|
||||
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) (or send me a [mail](mailto:banglejs@storm64.de)).
|
||||
|
||||
#### Creator
|
||||
Storm64 ([Mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64))
|
||||
Storm64 ([mail](mailto:banglejs@storm64.de), [github](https://github.com/storm64))
|
||||
|
||||
#### Contributors
|
||||
myxor ([github](https://github.com/myxor))
|
||||
|
|
|
@ -253,11 +253,38 @@ if (sleeplog.conf.enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
// check if the status has changed
|
||||
var changed = data.status !== this.status || data.consecutive !== this.consecutive;
|
||||
|
||||
// read and check trigger entries
|
||||
var triggers = Object.keys(this.trigger) || [];
|
||||
if (triggers.length) {
|
||||
// calculate time from timestamp in ms on full minutes
|
||||
var time = new Date();
|
||||
time = (time.getHours() * 60 + time.getMinutes()) * 60 * 1000;
|
||||
// go through all triggers
|
||||
triggers.forEach(key => {
|
||||
// read entry to key
|
||||
var entry = this.trigger[key];
|
||||
// check if the event matches the entries requirements
|
||||
if (typeof entry.fn === "function" && (changed || !entry.onChange) &&
|
||||
(entry.from || 0) <= time && (entry.to || 24 * 60 * 60 * 1000) >= time)
|
||||
// and call afterwards with status data
|
||||
setTimeout(entry.fn, 100, {
|
||||
timestamp: new Date(data.timestamp),
|
||||
status: data.status,
|
||||
consecutive: data.consecutive,
|
||||
prevStatus: data.status === this.status ? undefined : this.status,
|
||||
prevConsecutive: data.consecutive === this.consecutive ? undefined : this.consecutive
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// cache change into a known consecutive state
|
||||
var changeIntoConsec = data.consecutive;
|
||||
|
||||
// check if the status has changed
|
||||
if (data.status !== this.status || data.consecutive !== this.consecutive) {
|
||||
// actions on a status change
|
||||
if (changed) {
|
||||
// append status
|
||||
this.appendStatus(data.timestamp, data.status, data.consecutive);
|
||||
|
||||
|
@ -319,7 +346,10 @@ if (sleeplog.conf.enabled) {
|
|||
}
|
||||
// return stats cache
|
||||
return this.statsCache;
|
||||
}
|
||||
},
|
||||
|
||||
// define trigger object
|
||||
trigger: {}
|
||||
}, sleeplog);
|
||||
|
||||
// initial starting
|
||||
|
|
|
@ -149,14 +149,6 @@ exports = {
|
|||
|
||||
// define move log function, move StorageFile content into files seperated by fortnights
|
||||
moveLog: function(force) {
|
||||
/** convert old logfile (< v0.10) if present **/
|
||||
if (require("Storage").list("sleeplog.log", {
|
||||
sf: false
|
||||
}).length) {
|
||||
convertOldLog();
|
||||
}
|
||||
/** may be removed in later versions **/
|
||||
|
||||
// first day of this fortnight period
|
||||
var thisFirstDay = this.fnToMs(this.msToFn(Date.now()));
|
||||
|
||||
|
@ -384,82 +376,5 @@ exports = {
|
|||
"unknown,not worn,awake,light sleep,deep sleep".split(",")[entry[1]].padEnd(12) +
|
||||
"for" + (duration + "min").padStart(8));
|
||||
});
|
||||
},
|
||||
|
||||
/** convert old (< v0.10) to new logfile data **/
|
||||
convertOldLog: function() {
|
||||
// read old logfile
|
||||
var oldLog = require("Storage").read("sleeplog.log") || "";
|
||||
// decode data if needed
|
||||
if (!oldLog.startsWith("[")) oldLog = atob(oldLog);
|
||||
// delete old logfile and return if it is empty or corrupted
|
||||
if (!oldLog.startsWith("[[") || !oldLog.endsWith("]]")) {
|
||||
require("Storage").erase("sleeplog.log");
|
||||
return;
|
||||
}
|
||||
|
||||
// transform into StorageFile and clear oldLog to have more free ram accessable
|
||||
require("Storage").open("sleeplog_old.log", "w").write(JSON.parse(oldLog).reverse().join("\n"));
|
||||
oldLog = undefined;
|
||||
|
||||
// calculate fortnight from now
|
||||
var fnOfNow = this.msToFn(Date.now());
|
||||
|
||||
// open StorageFile with old log data
|
||||
var file = require("Storage").open("sleeplog_old.log", "r");
|
||||
// define active fortnight and file cache
|
||||
var activeFn = true;
|
||||
var fileCache = [];
|
||||
// loop through StorageFile entries
|
||||
while (activeFn) {
|
||||
// define fortnight for this entry
|
||||
var thisFn = false;
|
||||
// cache new line
|
||||
var line = file.readLine();
|
||||
// check if line is filled
|
||||
if (line) {
|
||||
// parse line
|
||||
line = line.substr(0, 15).split(",").map(e => parseInt(e));
|
||||
// calculate fortnight for this entry
|
||||
thisFn = this.msToFn(line[0]);
|
||||
// convert timestamp into 10min steps
|
||||
line[0] = line[0] / 6E5 | 0;
|
||||
// set consecutive to unknown
|
||||
line.push(0);
|
||||
}
|
||||
// check if active fortnight and file cache is set, fortnight has changed and
|
||||
// active fortnight is not fortnight from now
|
||||
if (activeFn && fileCache.length && activeFn !== thisFn && activeFn !== fnOfNow) {
|
||||
// write file cache into new file according to fortnight
|
||||
require("Storage").writeJSON("sleeplog_" + activeFn + ".log", fileCache);
|
||||
// clear file cache
|
||||
fileCache = [];
|
||||
}
|
||||
// add line to file cache if it is filled
|
||||
if (line) fileCache.push(line);
|
||||
// set active fortnight
|
||||
activeFn = thisFn;
|
||||
}
|
||||
// check if entries are leftover
|
||||
if (fileCache.length) {
|
||||
// format fileCache entries into a string
|
||||
fileCache = fileCache.map(e => e.join(",")).join("\n");
|
||||
// read complete new log StorageFile as string
|
||||
file = require("Storage").open("sleeplog.log", "r");
|
||||
var newLogString = file.read(file.getLength());
|
||||
// add entries at the beginning of the new log string
|
||||
newLogString = fileCache + "\n" + newLogString;
|
||||
// rewrite new log StorageFile
|
||||
require("Storage").open("sleeplog.log", "w").write(newLogString);
|
||||
}
|
||||
|
||||
// free ram
|
||||
file = undefined;
|
||||
fileCache = undefined;
|
||||
|
||||
// clean up old files
|
||||
require("Storage").erase("sleeplog.log");
|
||||
require("Storage").open("sleeplog_old.log", "w").erase();
|
||||
}
|
||||
/** may be removed in later versions **/
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id":"sleeplog",
|
||||
"name":"Sleep Log",
|
||||
"shortName": "SleepLog",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"description": "Log and view your sleeping habits. This app is using the built in movement calculation.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
Loading…
Reference in New Issue