Create a Bangle.isWorn() method

Posted on
  • I recently made a widget that buzzes following the 20-20-20 rule… But it should only run when the user is actually wearing the watch. Is there already some detection for if someone is wearing the watch? Or do all widgets run constantly in the background?

    If all widgets are always running, my proposal is an optional boolean in the manifest that can have a widget only run while the watch is being worn. I could see other widgets using this, but mine definitely could and it’d increase the battery life as well!


    I’m thinking a call like this:

    Call type:

    `Bangle.isWorn()`
    

    Return

    Is the device being worn or not?
    

    Description

    Determines if the device is being worn from various sensors’ activities.
    
    **Note:** This is only available in Bangle.js smartwatches
    
  • This thread has some tips regarding this.

    The 20-20-20 app is a great idea btw! Gonna' check it out. Right now I use 'Activity Reminder' for nudging me to move around.

  • It's a nice idea only running widgets if the watch is worn, but the issue is we can't 'unload' widgets so I'm not sure it'd work that well.

    For 'isWorn' there are a few options - maybe someone could make a simple library when they come up with a solution that works nicely and then everyone could use it?

  • Thanks a bunch for the link! I like the temperature solution since it could be low-power.

    Potentially checking if E.getTemperature() is above some value, if Bangle.getAccel() is above a low magnitude, not Bangle.isCharging().

  • Would it be easier to suspend watch features (like buzzing) for opt-in widgets when not worn? I was thinking about adding a check right before I buzz the watch about whether it’s being worn.

    And I’ll make a PR for the library and share it on the other thread so everyone can improve it :)

  • That sounds great! Temperature may be an idea but it will take a long time to respond - motion is probably enough, and potentially won't use too much extra power since the accelerometer is always running anyway.

    In terms of buzzing - one option is just to overwrite Bangle.buzz with your own function that calls the original if the watch is worn, or ignores if not.

    There is also a 'quiet mode' and you could just enable that when the Bangle is not worn. That may be more sensible.

    However some people may still wish for buzzing if the Bangle is on a desk (eg alarms) so I guess it should be an option.

  • I collected some data yesterday (I attached the CSV) that recorded the temperature and acceleration... I'll try to fit a decision tree to it today for a basic if-else predictor :) Hopefully acceleration is enough!

    I like the quiet mode the most, but would that also quiet alarms like you mentioned? In that case the more widget-by-widget solution would be overriding.


    1 Attachment

  • I put together a quick decision tree and here's the output:

    tree

    Here is the proposed JS that achieved an accuracy of 99% on the dataset:

    function isWorn() {
        if (Bangle.isCharging())
            return false;
        if (E.getTemperature() > 24.625)
            return true;
        if (Bangle.getAccel().mag > 1.045)
            return true;
        return false;
    }
    

    1 Attachment

  • The temperature one will probably vary a lot with ambient temperature. At least during the hot summer days I needed to have the threshold at ca. 31 degrees.

    Edit: Maybe an approximation of ambient temperature could be put in a variable that is updated by a function when the watch is not worn and not charging. This variable could then be used instead of the fixed value 24.625.

  • Can't you just query the PPG sensor?

  • Temperature feels really tricky, I can easily imagine it getting higher from e.g. leaving the watch in the sun than wearing it.

    Quiet mode has 3 levels:
    0 = not quiet
    1 = only alarms
    2 = totally quiet
    Maybe including this in the Quiet Mode Schedule app would be a good idea? (it looks like it could do with an update to the new sched library anyway...)

    Instead of a isWorn() function, could it maybe work with events and a global var?
    Basically add a boot app that keeps track of movement/charging/etc, and does something like

    (function(){
          function CheckWearing(event) {
            if (/* watch is being worn */) {
                Bangle.isWorn = true;
                Bangle.emit('worn', true);
            } else {
                Bangle.isWorn = false;
                Bangle.emit('worn', false);
            }
          }
          Bangle.on('hrm', checkWearing);
          Bangle.on('accell', checkWearing);
    })()
    

    That way other apps can check Bangle.isWorn !== false and/or do Bangle.on('worn', wearing => {});, without needing to include e.g. a library in every widget.

  • Comments on the PR at https://github.com/espruino/Espruino/pul­l/2264 but I just added the code mentioned above as a library: https://github.com/espruino/BangleApps/c­ommit/ff9a5c4c20bc95fd0d628bd7041f71753a­1edf6c

    I made it so it returns a promise, because I think there's a good chance that the code will need changing and may need to do some things (like waiting for an HRM reading) that don't return immediately.

    I think there is a potential for the library to emit a 'worn' event as well (which probably makes a lot of sense) but I imagine @splch is thinking that in cases like alarms they might want to just check instantaneously at the point they were thinking of making a noise.

    In terms of the checks, I think a few things would need improving:

    • as @Ganblejs says, temperature may end up being too low
    • Checking Bangle.getAccel().mag once is unlikely to be a good way to check movement, since it checks movement only over a 1/12.5 sec period. You probably want Bangle.getHealthStatus().movement (see http://www.espruino.com/Reference#l_Bang­le_getHealthStatus) but again, this works in 10 minute blocks, so some work probably needs to be done (maybe checking Bangle.getHealthStatus('day').movement once every 10 seconds and comparing?) to ensure that you check the full amount of movement over a period of a few seconds
  • @Ganblejs I like the idea of recording temperature relative to the mean… But if a person is always wearing their watch, the man will always be high. Gordon might be right about acceleration being the best way to go.

    @user140377 We definitely can, and I think that it’d be great to check if the sensors have power before using them. But I’d like a low-power solution that works if the user isn’t powering their HRM or GPS.

    @rigrig I agree, temperature alone isn’t enough. I can’t think of a use of temperature that isn’t able to be fooled 😂 I like the worn event! It’d make sense to me to have certain widgets start on the worn event. I was only thinking about either a global variable or function…

    @Gordon I’ll update the function to check if certain sensors are powered on (like HRM) before using that data. I also need to collect some more data with the healthStatus movement! That function might be enough to detect if you’re wearing the watch :)

  • Another idea: just look at the accelerometer with a timeout?

    (function(){
            let timeout;
            function setWorn(worn) {
                 worn = !!worn; // undefined -> false
                 if (timeout) clearTimeout(timeout);
                 if (Bangle.isWorn === worn) return; // no change
                 Bangle.isWorn = worn;
                 Bangle.emit('worn', worn);
            }
            Bangle.on('accel', xyz => {
                if (Bangle.isCharging()) {
                    setWorn(false);
               }
                else if (xyz.diff) {  // or Math.abs(xyz.diff) > 0.01?
                     setWorn(true);
                     // no movement for 1 minute
                     timeout = setTimeout(setWorn, 60000);
                }
            });
            Bangle.on('charging', charging => {
                // assume charging ends = watch picked up
                setWorn(!charging);
            });
    })()
    

    But I guess constantly running code for accell events might not be great for battery life :-(

  • In the case when the user mostly keeps the bangle on them, maybe it could be assumed the ambient temperature is a couple degrees lower than the 'wearing temperature'?

    So maybe there would be three temperature measurements:

    • 'Ambient temperature' measured when the bangle is probably not worn and not charging.
    • 'Wearing temperature' measured when the bangle is worn.
    • 'Assumed ambient temperature' which would be a set number of degrees lower than 'wearing temperature'.

    When the last 'ambient temperature' reading became too old then 'assumed ambient temperature' would take it's place.

    But I also suspect that Gordon might be right :p.

  • I just gave it a shot yesterday with the Bangle.getHealthStatus().movement method works great! Temperature adds some inference but I included the data as well.

    Without temperature, the model has a 99.2% accuracy :)

    The decision tree function is:

    function isWorn() {
        if (Bangle.isCharging())
            return false;
        if (Bangle.getHealthStatus().movement > 146)
            return true;
        return false;
    }
    

    2 Attachments

  • Hi,

    I was using https://banglejs.com/apps/?id=health for a day until I realized that it doesn't stop HRM when I stop wearing it at night.

    see edit below
    Regarding Bangle.getHealthStatus().movement > 146: from the docs,
    movement is the sum of diffs, so you can't just use a constant
    threshold. And as Gordon pointed out, it only resets every 10min.

    Anyways, Bangle.js already detects movement for powerSave option and for me this would be sufficient. The following proof-of-concept works well enough for me:

    function is_worn() {
        return new Promise(function(resolve, reject) {
            var i = 0;
            function on_accel(a) { i++; }
            Bangle.on('accel', on_accel);
            setTimeout(function() {
                Bangle.removeListener('accel', on_accel);
                resolve(1 < i);
            }, 3*80);
        });
    }
    

    I didn't find a way to get the current power state/poll interval/"hasn't moved". Is there?

    edit: I was wrong, acceleration diffs are unsigned, sry

  • I see it's you, but just a note that this just went in: https://github.com/espruino/Espruino/com­mit/05791f374d1a5cca68c56c04df8386d928f4­a18c

    So Bangle.js 2v17 will have the ability to query if the device has been moving or not

  • https://pastebin.com/BahN25YL
    This was my solution, but I only just read the above post by Gordon, so might not be needing it. My code is comparing orientation in space 5 minutes apart, if they are equal it calls softOff. So not completely fool-proof, uses magnetometer and accelerometer dot product, magnetometer only stays on for the 1 sec it's needed.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Create a Bangle.isWorn() method

Posted by Avatar for splch @splch

Actions