2022-02-11 08:29:02 +00:00
// 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.
// https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en
2022-02-12 00:43:58 +00:00
// sleeplog.status values: undefined = service stopped, 0 = unknown, 1 = not worn, 2 = awake, 3 = sleeping
2022-02-11 08:29:02 +00:00
// load settings into global object
global . sleeplog = Object . assign ( {
enabled : true , // en-/disable completely
logfile : "sleeplog.log" , // logfile
2022-02-12 00:43:58 +00:00
powersaving : false , // disables ESS and uses build in movement detection
2022-02-11 08:29:02 +00:00
winwidth : 13 , // 13 values, read with 12.5Hz = every 1.04s
nomothresh : 0.012 , // values lower than 0.008 getting triggert by noise
sleepthresh : 577 , // 577 times no movement * 1.04s window width > 10min
2022-02-13 19:54:26 +00:00
maxmove : 100 , // movement threshold on power saving mode
2022-02-11 08:29:02 +00:00
tempthresh : 27 , // every temperature above ist registered as worn
} , require ( "Storage" ) . readJSON ( "sleeplog.json" , true ) || { } ) ;
// delete app settings
2022-02-12 00:43:58 +00:00
[ "breaktod" , "maxawake" , "minconsec" ] . forEach ( property => delete sleeplog [ property ] ) ;
2022-02-11 08:29:02 +00:00
// check if service enabled
2022-02-12 00:43:58 +00:00
if ( sleeplog . enabled ) {
2022-02-11 08:29:02 +00:00
2022-02-12 00:43:58 +00:00
// add always used values and functions to global object
sleeplog = Object . assign ( sleeplog , {
2022-02-11 08:29:02 +00:00
// set cached values
resting : undefined ,
2022-02-12 00:43:58 +00:00
status : undefined ,
2022-02-11 08:29:02 +00:00
// define stop function (logging will restart if enabled and boot file is executed)
stop : function ( ) {
2022-02-12 00:43:58 +00:00
// remove all listeners
Bangle . removeListener ( 'accel' , sleeplog . accel ) ;
Bangle . removeListener ( 'health' , sleeplog . health ) ;
2022-02-13 21:39:57 +00:00
E . removeListener ( 'kill' , ( ) => sleeplog . stop ( ) ) ;
2022-02-12 00:43:58 +00:00
// exit on missing global object
if ( ! global . sleeplog ) return ;
2022-02-11 08:29:02 +00:00
// write log with undefined sleeping status
2022-02-13 19:54:26 +00:00
require ( "sleeplog" ) . writeLog ( 0 , [ Math . floor ( Date . now ( ) ) , 0 ] ) ;
2022-02-12 00:43:58 +00:00
// reset always used cached values
sleeplog . resting = undefined ;
sleeplog . status = undefined ;
sleeplog . ess _values = [ ] ;
sleeplog . nomocount = 0 ;
sleeplog . firstnomodate = undefined ;
2022-02-11 08:29:02 +00:00
} ,
// define restart function (also use for initial starting)
start : function ( ) {
2022-02-12 00:43:58 +00:00
// exit on missing global object
if ( ! global . sleeplog ) return ;
// add health listener if defined and
if ( sleeplog . health ) Bangle . on ( 'health' , sleeplog . health ) ;
// add acceleration listener if defined and set status to unknown
if ( sleeplog . accel ) Bangle . on ( 'accel' , sleeplog . accel ) ;
2022-02-11 08:29:02 +00:00
// add kill listener
2022-02-13 21:39:57 +00:00
E . on ( 'kill' , ( ) => sleeplog . stop ( ) ) ;
2022-02-13 19:54:26 +00:00
// read log since 5min ago and restore status to last known state or unknown
sleeplog . status = ( require ( "sleeplog" ) . readLog ( 0 , Date . now ( ) - 3E5 ) [ 1 ] || [ 0 , 0 ] ) [ 1 ]
// update resting according to status
sleeplog . resting = sleeplog . status % 2 ;
// write restored status to log
require ( "sleeplog" ) . writeLog ( 0 , [ Math . floor ( Date . now ( ) ) , sleeplog . status ] ) ;
2022-02-12 00:43:58 +00:00
}
2022-02-11 08:29:02 +00:00
} ) ;
2022-02-12 00:43:58 +00:00
// check for power saving mode
if ( sleeplog . powersaving ) {
// power saving mode using build in movement detection
2022-02-13 19:54:26 +00:00
// delete unused settings
[ "winwidth" , "nomothresh" , "sleepthresh" ] . forEach ( property => delete sleeplog [ property ] ) ;
2022-02-12 00:43:58 +00:00
// add cached values and functions to global object
sleeplog = Object . assign ( sleeplog , {
// define health listener function
health : function ( data ) {
// set global object and check for existence
var gObj = global . sleeplog ;
if ( ! gObj ) return ;
2022-02-13 19:54:26 +00:00
// calculate timestamp for this measurement
var timestamp = Math . floor ( Date . now ( ) - 6E5 ) ;
2022-02-12 00:43:58 +00:00
// check for non-movement according to the threshold
if ( data . movement <= gObj . maxmove ) {
// check resting state
2022-02-14 07:18:16 +00:00
if ( gObj . resting !== true ) {
2022-02-12 00:43:58 +00:00
// change resting state
gObj . resting = true ;
// set status to sleeping or worn
gObj . status = E . getTemperature ( ) > gObj . tempthresh ? 3 : 1 ;
2022-02-13 19:54:26 +00:00
// write status to log,
2022-02-14 07:18:16 +00:00
require ( "sleeplog" ) . writeLog ( 0 , [ timestamp , gObj . status , E . getTemperature ( ) ] ) ;
2022-02-12 00:43:58 +00:00
}
} else {
// check resting state
2022-02-14 07:18:16 +00:00
if ( gObj . resting !== false ) {
2022-02-13 19:54:26 +00:00
// change resting state, set status and write status to log
2022-02-12 00:43:58 +00:00
gObj . resting = false ;
gObj . status = 2 ;
2022-02-14 07:18:16 +00:00
require ( "sleeplog" ) . writeLog ( 0 , [ timestamp , 2 ] ) ;
2022-02-12 00:43:58 +00:00
}
}
}
} ) ;
} else {
// full ESS calculation
// add cached values and functions to global object
sleeplog = Object . assign ( sleeplog , {
// set cached values
ess _values : [ ] ,
nomocount : 0 ,
firstnomodate : undefined ,
// define acceleration listener function
accel : function ( xyz ) {
// save acceleration magnitude and start calculation on enough saved data
if ( global . sleeplog && sleeplog . ess _values . push ( xyz . mag ) >= sleeplog . winwidth ) sleeplog . calc ( ) ;
} ,
// define calculator function
calc : function ( ) {
// exit on wrong this
if ( this . enabled === undefined ) return ;
// calculate standard deviation over
var mean = this . ess _values . reduce ( ( prev , cur ) => cur + prev ) / this . winwidth ;
var stddev = Math . sqrt ( this . ess _values . map ( val => Math . pow ( val - mean , 2 ) ) . reduce ( ( prev , cur ) => prev + cur ) / this . winwidth ) ;
// reset saved acceleration data
this . ess _values = [ ] ;
// check for non-movement according to the threshold
if ( stddev < this . nomothresh ) {
// increment non-movement sections count, set date of first non-movement
if ( ++ this . nomocount == 1 ) this . firstnomodate = Math . floor ( Date . now ( ) ) ;
// check resting state and non-movement count against threshold
if ( this . resting !== true && this . nomocount >= this . sleepthresh ) {
// change resting state
this . resting = true ;
// set status to sleeping or worn
this . status = E . getTemperature ( ) > this . tempthresh ? 3 : 1 ;
2022-02-13 19:54:26 +00:00
// write status to log, with first no movement timestamp
require ( "sleeplog" ) . writeLog ( 0 , [ this . firstnomodate , this . status , E . getTemperature ( ) ] ) ;
2022-02-12 00:43:58 +00:00
}
} else {
// reset non-movement sections count
this . nomocount = 0 ;
// check resting state
if ( this . resting !== false ) {
2022-02-13 19:54:26 +00:00
// change resting state and set status
2022-02-12 00:43:58 +00:00
this . resting = false ;
this . status = 2 ;
2022-02-13 19:54:26 +00:00
// write status to log
require ( "sleeplog" ) . writeLog ( 0 , [ Math . floor ( Date . now ( ) ) , 2 ] ) ;
2022-02-12 00:43:58 +00:00
}
}
}
} ) ;
}
2022-02-11 08:29:02 +00:00
// initial starting
global . sleeplog . start ( ) ;
}