Closed myxor closed 11 months ago
This shouldn't be a big deal.
For the first two values I can simply add sleeplog.awakeSince
as a timestamp from the last status change to awake (status == 2) and reset this value to undefined
on the first detection of a sleeping state (status > 2).
Date(sleeplog.awakeSince)
Date.now() - sleeplog.awakeSince
To determine the other three values it is necessary to do little calculations over the logged data.
I assume you want to use this data on a watch face and therefore it could be the best approach to add these values as properties of sleeplog, too.
There are a few ways at wich point to calculate the values:
My preferred way would be the second one: On every new log entry. It's easy to implement and the fact that the logfile needs to be loaded already makes it less power consuming.
The more tricky question is how the last three values should be calculated. The total values are heavily depending on the used time span. I would suggest to use the same preferences as used for the app, wich would result in the following values for your requested values:
Or do you want to have the sum of all logged awake / sleeping periods?
I'm actually working on a more detailed sleep detection with a light and deep sleep phase and some code improvements. Therefore I need to make a lot of changes to the code and it will be no problem to implement these ideas.
Thanks for your suggestions, those are some great ideas.
Thanks for answering.
- when a new value is added to the logfile I would prefer this way as well.
I would suggest to use the same preferences as used for the app, wich would result in the following values for your requested values
Fine for me. I guess it would be the best if the values via the "API" match the ones being shown in the app.
Therefore I need to make a lot of changes to the code and it will be no problem to implement these ideas.
if you could integrate some parts of this would be great :)
@myxor I hope you don't mind my piggybacking here, but I am excited to hear that @storm64 is working on the detailed sleep detection; it's something I was hoping for. I can open a separate issue if that would be better.
@storm64, while you're working on that do you think you could add a debug option to log more details, rather than just the state changes? I was thinking that could be helpful for when a user is trying to dial in their personal thresholds for more accurate classification of the states. I found that I had to connect to my watch with the IDE and dump the sleeplog
state to set the nomothreshold. It might also be possible to capture that data overnight in some kind of learning mode and then modify the values for the user before going back to non-debug mode automatically.
@GrandVizierOlaf sure that is fine.
@storm64 i am really looking forward to your rework of the sleeplog app. This night I had another idea: it would be great if we somehow could make a connection between the Quiet Mode and the current sleeping state. I would like the notification mode be toggled to "silent" or "alarm" while sleep is detected. Just as an idea :)
I'm happy to here that you are having fun with the app.
@GrandVizierOlaf: I stepped on the same problem while trying to figure out why no sleeping is recognized on @juanjgit's watch.
To tell you more about what I am working on, here a list of the major changes:
debug_{hours since 1970}.log
07:55:00.180 on ESS > status: 3, value: 0.1408, temp: 32.5 °C , times exceeded: 3
07:55:00.180
timestampon ESS/PWM >
power saving or ESS modestatus: 3,
0 = unknown, 1 = not worn, 2 = awake, 3 = light sleep, 4 = deep sleepvalue: 0.1408,
maximal movement value on PSM or stddev on ESS inside the intervaltemp: 32.5 °C,
value of E.getTemperature() at the timestamptimes exceeded: 3
only for ESS, maximal count a exceeded threshold is ignored (see 2.)require("qmsched").setMode(sleeping ? 1 : 2)
from Quiet Mode Schedule and Widget could be enough?At last I want to share a thought related to the future of ESS mode:
For comparison I installed the sleeplog app twice, each app using one mode (ESS/PSM). (I installed the second instance by replacing "sleeplog" with "sleeptest".) In my opinion the ESS calculation brings no real benefit, obviously except for a faster feedback on a status change to light sleep or awake but is extremely more power consuming and harder to set up correct. On the next version I would enable power saving by default. I could imagine to use the ESS calculation only when in deep sleep to detect a status change faster. What are your thoughts on this?
@storm64 that all sounds fantastic. Let me know if you need any help testing it.
When you mention "the last day" with regards to sleeplog.stats
, is that the current day, the previous day, the last 24 hours, or something else?
Reading through it again reminds me of an issue I ran into yesterday and a potential solution; the ambient temperature was 81°F (27 C) and my watch was unplugged and charging, but was detected as worn. I bumped the temperature threshold, but it gets above body temperature here in the summer and I worry that it will mess with the detection then. One potential shortcut is to check if the watch is charging and, if it is, know that it is not worn. You could also check the HRM stats every so often and see if that gives more accurate worn/not worn than the temperature.
Toggle Quiet Mode: This could be a really nice idea. @myxor do you already have in mind how to realize this? Maybe calling require("qmsched").setMode(sleeping ? 1 : 2) from Quiet Mode Schedule and Widget could be enough?
Yes indeed it looks like this could be enough to toggle the quiet mode! I will try this out in the next days.
When you mention "the last day" with regards to
sleeplog.stats
, is that the current day, the previous day, the last 24 hours, or something else?
I would stay with the same day periods as in the app. The duration is fixed to 24 hours. Start and end time is set by the "Break ToD" value in settings. As a result "the last day" is the period from second last to last time of day equal to "Break ToD".
@GrandVizierOlaf, I was unsatisfied with the "not wearing" check from the beginning. Checking for the charging status is a good idea and for checking the status if not charging I might have found a solution with minimal HRM up time:
function onWearingCheck(isWearing) {
print("Wearing status:", isWearing);
}
function wearingCheck() {
// define function to read wearing status
var hrmListener = hrm => global.checkIsWearing = hrm.isWearing;
// enable HRM
Bangle.setHRMPower(true, "sleeplog");
// wait for initialisation
setTimeout(() => {
// add HRM listener
Bangle.on('HRM-raw', hrmListener);
// set default wearing value
global.checkIsWearing = false;
// wait for two cycles (HRM working on 60Hz)
setTimeout(() => {
// disable HRM and remove listener
Bangle.setHRMPower(false, "sleeplog");
Bangle.removeListener('HRM-raw', hrmListener);
// execute follow-up function with the result
onWearingCheck(checkIsWearing);
// clear cached status
delete global.checkIsWearing;
}, 34);
}, 2500);
}
wearingCheck();
@storm64 any news on the sleeplog rework? :)
I'm on 80% for "public" testing. For now the last part to rewrite is the app.js for displaying the logged data.
I am proud to present the new sleeplog app: version 0.10 🎉 ✨ 🎊 https://storm64.github.io/BangleApps/?id=sleeplog
Sorry that it took so long but hopefully most of the early bugs are sorted out and the app should be ready to be use and get tested!
I would love to hear about your impressions and like to know your choice of thresholds, to set the default values as optimized as possible.
The last piece of work is to rewrite the README.md to show how to use it and show the restrictions and possibilities. But here are some explanations how to use the app and settings:
On the app screen:
Night to Fri 20/05/2022
) to enter a day selection promptInside the settings:
lockTimeout
and backlightTimeout
only for the sleeplog appEnable
and write File
should be self explainingDuration
specifies how long data should be written into the .csv fileTimestamps and files:
global.sleeplog
) are formatted as Bangle timestamps:sleeplog.log (StorageFile)
) is within the highest available resolution:Bangle / (10 * 60 * 1000)
)sleeplog_123456.csv
) has a hourly resolution:
hours since 1970-01-01 00:00 UTC (Bangle / (60 * 60 * 1000)
)Bangle / (24 * 60 * 60 * 1000) + 25569
)sleeplog.log (StorageFile)
is reduced and old entries are moved into separat files for each fortnight (sleeplog_1234.log
) but still accessible though the app:require("sleeplog").msToFn(Bangle)
and require("sleeplog").fnToMs(fortnight)
)Logfiles from before 0.10:
timestamps and sleeping status of old logfiles are automatically converted on your first consecutive sleep or manually by require("sleeplog").convertOldLog()
View logged data:
if you'd like to view your logged data in the IDE, you can access it with require("sleeplog").printLog(since, until)
or require("sleeplog").readLog(since, until)
to view the raw data
since & until in Bangle timestamp, e.g. require("sleeplog").printLog(Date()-24*60*60*1000, Date())
for the last 24h
Thank you @storm64, i will try it out the next days (nights).
I would like to collect a list of possible further statistics data which the sleeplog app could deliver in future:
The solutions with the new app:
Date(sleeplog.awakeSince)
Date() - sleeplog.awakeSince
~Last Sleep Duration~ Last Stats [object]:
// get stats of the last night (period as displayed inside the app)
// as this might be the mostly used function the data is cached inside the global object
sleeplog.getStats();
// get stats of the last 24h
require("sleeplog").getStats(0, 24*60*60*1000);
// same as
require("sleeplog").getStats(Date.now(), 24*60*60*1000);
// output as object, timestamps as UNIX timestamp, durations in minutes
={ calculatedAt: 1653123553810, deepSleep: 250, lightSleep: 150, awakeSleep: 10,
consecSleep: 320, awakeTime: 1030, notWornTime: 0, unknownTime: 0, logDuration: 1440,
firstDate: 1653036600000, lastDate: 1653111600000 }
// to get the start of a period defined by "Break TOD" of any date
var startOfBreak = require("sleeplog").getLastBreak();
// same as
var startOfBreak = require("sleeplog").getLastBreak(Date.now());
// output as date
=Date: Sat May 21 2022 12:00:00 GMT+0200
// get stats of this period as displayed inside the app
require("sleeplog").getStats(require("sleeplog").getLastBreak(), 24*60*60*1000);
// or any other day
require("sleeplog").getStats(require("sleeplog").getLastBreak(Date(2022,4,10)), 24*60*60*1000);
// use with caution, may take a long time !
require("sleeplog").getStats(0, 0, require("sleeplog").readLog());
I would like the notification mode be toggled to "silent" or "alarm" while sleep is detected.
For now I forgot about this but would like to implement it.
Toggle Quiet Mode: This could be a really nice idea. @myxor do you already have in mind how to realize this? Maybe calling require("qmsched").setMode(sleeping ? 1 : 2) from Quiet Mode Schedule and Widget could be enough?
Yes indeed it looks like this could be enough to toggle the quiet mode! I will try this out in the next days.
Have you tried if require("qmsched").setMode(sleeping ? 1 : 2)
is the correct approach?
And how should it be triggered:
In my opinion I would prefer a option in the settings (showing if qmsched is installed) like
never -> off
set Quiet on first -> 1.i.
consec. -> 1.ii.
@gfwilliams I'm working on sending the sleep state to gadgetbridge and found the following two java files that might be interesting for this:
.../model/ActivityKind.java defining the different kinds of activities.
A list of the interesting kinds for sleeplog:
and
.../service/devices/banglejs/BangleJSDeviceSupport.java handling the communication with Bangle.js.
Where the interesting part for activities could be altered just a bit:
372 case "act": {
373 BangleJSActivitySample sample = new BangleJSActivitySample();
- 374 sample.setTimestamp((int) (GregorianCalendar.getInstance().getTimeInMillis() / 1000L));
+ int ts = GregorianCalendar.getInstance().getTimeInMillis() / 1000L;
375 int hrm = 0;
376 int steps = 0;
+ int activity = BangleJSSampleProvider.TYPE_NOT_MEASURED;
+ if (json.has("ts")) ts = json.getLong("ts") / 1000L;
377 if (json.has("hrm")) hrm = json.getInt("hrm");
378 if (json.has("stp")) steps = json.getInt("stp");
- 379 int activity = BangleJSSampleProvider.TYPE_ACTIVITY;
- 380 /*if (json.has("act")) {
+ if (json.has("act")) {
381 String actName = "TYPE_" + json.getString("act").toUpperCase();
382 try {
383 Field f = ActivityKind.class.getField(actName);
384 try {
385 activity = f.getInt(null);
386 } catch (IllegalAccessException e) {
387 LOG.info("JSON activity '"+actName+"' not readable");
388 }
389 } catch (NoSuchFieldException e) {
390 LOG.info("JSON activity '"+actName+"' not found");
391 }
- 392 }*/
+ }
+ sample.setTimestamp(ts);
- 393 sample.setRawKind(activity);
394 sample.setHeartRate(hrm);
395 sample.setSteps(steps);
+ sample.setRawKind(activity);
396 try (DBHandler dbHandler = GBApplication.acquireDB()) {
397 Long userId = getUser(dbHandler.getDaoSession()).getId();
398 Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
399 BangleJSSampleProvider provider = new BangleJSSampleProvider(getDevice(), dbHandler.getDaoSession());
400 sample.setDeviceId(deviceId);
401 sample.setUserId(userId);
402 provider.addGBActivitySample(sample);
403 } catch (Exception ex) {
404 LOG.warn("Error saving activity: " + ex.getLocalizedMessage());
405 }
406 // push realtime data
407 if (realtimeHRM || realtimeStep) {
408 Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES)
409 .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample);
410 LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
411 }
412 } break;
The according code snippet to trigger a status change inside sleeplog would be as following:
// send status to gadgetbridge
var gb_kind = "unknown,not_worn,activity,light_sleep,deep_sleep";
Bluetooth.println(JSON.stringify({
t: "act",
act: gb_kind.split(",")[data.status],
ts: data.timestamp // as UNIX timestamp in ms
}));
I am not sure if this works as easy es I might think/hope, especially the behavior from gadgetbridge receiving two separate actions (one with steps+hrm and one with the activity), but this could be altered within Bangle itself if neccessary.
I would really like to here your thoughts and am excited to take the sleeplog app a step further.
Yes, this looks promising - and the code is already there, just disabled? However is the timestamp thing really required? Can't we just send the update when the activity type actually changes?
Due to the fact that i need to evaluate the movement collected over the last 10 minutes, a status change had occurred 10 minutes ago. This is taken into account in the sleeplog app and to use the same base of data, I would recommend to hand over the corrected timestamp.
Ok, thanks. I guess it also has the benefit that maybe the Bangle can 'catch up' if it's been disconnected from Gadgetbridge for a while
@storm64 just wanted to let you know that i saw this exception happening today a few times:
Uncaught Error: Module "sleeplog" not found
at line 105 col 4013 in .boot0
...les.removeCached("sleeplog");}
^
in function "setStatus" called from line 105 col 2258 in .boot0
...;}else{sleeplog.setStatus(data);}
Sry, that this took me so long, but the module not found error should be fixed with the new beta04.
Hi @myxor,
just wanted to remind you of my question:
Toggle Quiet Mode: This could be a really nice idea. @myxor do you already have in mind how to realize this? Maybe calling require("qmsched").setMode(sleeping ? 1 : 2) from Quiet Mode Schedule and Widget could be enough?
Yes indeed it looks like this could be enough to toggle the quiet mode! I will try this out in the next days.
Have you tried if require("qmsched").setMode(sleeping ? 1 : 2)
is the correct approach?
And how should it be triggered:
In my opinion I would prefer a option in the settings (showing if qmsched is installed) like
never -> off
set Quiet on first -> 1.i.
consec. -> 1.ii.
In my opinion I would prefer a option in the settings (showing if qmsched is installed) like
never -> off set Quiet on first -> 1.i. consec. -> 1.ii.
This sounds to me as the best approach as well.
Have you tried if require("qmsched").setMode(sleeping ? 1 : 2) is the correct approach?
I did not yet try it but from my view on the qmsched code this looks correct.
I think we can close this issue now, if there's nothing else outstanding?
Looks good - we can always reopen if there is something specific
I would like to collect a list of possible further statistics data which the sleeplog app could deliver in future:
This could be done via the already existing
global.sleeplog
object.Pinging @storm64 as creator of sleeplog.
Open for discussion and ideas.