Bangle.js 2 Cutting Edge - new heart rate algorithm

Posted on
of 6
First Prev
/ 6
  • What I can tell others from my experience:
    hrmSportMode setting should be best left at -1.

    This is because hrmSportMode: 0 is real heart rate approximation for values between 50-90. Values above this are mostly impossible to predict with an algorithm considering the values this device produces.

    hrmSportMode: 1 is a boosted heart rate from acceleration heuristics, but if you leave it on when you are not moving much it creates false positives. That explains moments of hr persisting above 100 when we are at 60. This mode NEEDS movement for it to make any sense. ( perhaps if it didn't trigger so easily from just being stationary it could be improved, it has hard-coded ppg delta change and the ppg moves a lot for no apparent reason on macro scale ).

    Using hrmSportMode: -1 gives us best of both worlds, if we move a lot within a 20 second window period, we become 1, and then 20 seconds later, we are back to 0.

    So ye, the 0 mode is really good for normal relaxed heart rate values. I tried to create an algorithm that is as good as it and it took me A LOT of effort just to get close. The difference was small and so I don't think I will even bother to release it. I will however collect some more data over the coming days.

    When testing, remember to use Bangle.dbg() to notice the sportsMode changing because of the auto -1 in Bangle.getOptions().hrmSportMode

    I believe the algorithm relies on something similar to :­keisan.htm after looking at the decompilation. So its a frequency-domain based solution? I guess.

    The method I have been testing recently was a threshold based peak detection on the time-domain.

    I'm personally grateful we have something that works so well, considering how the signal looks, its kind of a miracle.

  • Yes, I think as you say the binary blob is frequency based - it works on blocks of data, rather than instantaneously (as the 'open' algorithm does). I think the benefit there is it's relatively straightforward to discount readings where there's high acceleration.

  • Hey all. Thanks for all the hard work tinkering and testing the HRM settings. I've read the 6 pages of back and forth and just want to confirm a couple of things. As a non-technical end user and based on the current firmware, overall how accurate is the HRM? What are the key things to presently be aware of when considering HRM data (ex: certain activities are more prone to bad data VS resting HRM, etc.)? Thanks in advance!

  • I think someone else needs to jump in here and answer as I haven't done a great deal of comparison testing, but my experience has been that with the new algorithm, HRM works well enough for everyday use.

    However it's not going to compete with a device that uses an actual ECG - it's a different technology that is much less susceptible to movement.

  • Thanks for the reply Gordon! I didn't realize that we weren't really comparing apples-to-apples. It sounds like it'll be most useful to gather a really good understanding of my resting heart rate and maybe my sleep heart rate, but that I should take it with a grain of salt when moving.

    I'd be curious if someone else can comment on if light movement walking HRM tends to be accurate. Also, does it find its "groove" over the course of exercise (I.e. maybe the first few minutes of a 30-60 min walk might be a bit funky, but perhaps it settles into a rhythm once movement is detected)?

  • Very late response, but I don't know how useful it is anyway:

    Compared to a Fitbit Versa 3, I usually get within 5-10bpm consistently. They both use the photosomethingorother sensor, but in case there was some secret algorithm Fitbit uses, here's a roughly what I've found:

    Fitbit likes to draw their graphs as "landscape" as possible, with a thick line that hides how spikey it gets, but usually it has the exact same peaks and valleys as Bangle.

    Attached are screenshots of the graphs of both while shoveling and using a snowblower on and off for about an hour and a half.

    Peaks in the Fitbit graph are 117bpm, then 132, 132, 132.
    Corresponding peaks in Gadgetbridge are 90bpm, then 127, 130, 137.

    2 Attachments

    • Fitbit Versa 3 - 20240325.png
    • Screenshot_20240325_155050_Banglejs Gadgetbridge.jpg
  • Appreciate the insights :)

  • @Gordon I am working with 2 students on the algorithms for HR and steps. We would like to integrate with your work in the most efficient way so that, in case they come up with something promising, you can integrate their code easily into the firmware.

    What we are going to do:

    • collect raw data with reference, both step count and heart rate
    • test out a few alternative algos and compare them in terms of accuracy / resources utilization

    I am aware of these two repos that you have created:­MTestHarness

    We can simply push code to those 2, or, maybe more efficiently, we could merge them into one?
    Some guidance would be greatly appreciated.


  • collect raw data with reference, both step count and heart rate

    Out of interest, what do you use for capturing the reference data?

  • what do you use for capturing the reference data?

    HR: Polar HR sensor with BLE (based on ECG)
    Steps: we put an IMU on a shoe. With some simple thresholding, the step count is quite reliable.

  • That's great! It's up to you - I guess I'd be inclined just to keep the two repos separate - I would imagine the test data in each case might not always have that much in common (no need to have HRM data if you only want to test out step counting and so on) although it sounds like in your case you'll be gathering both at the same time.

    ... also the way you'll want to compare algorithms for step counts and HRM is probably different too.

    We can't easily run the binary algorithm 'offline' so it might be worth logging the Bangle's reported HRM as well so you can test. One thing to note is the binary algo 'windows' the data and runs a filter over it, so it can't report instantaneous beat data - which would be really nice for heart rate variability values.

    Realistically the important thing for integration is that the algorithms themselves are normal, self-contained C files like­b/master/libs/misc/heartrate.c that implement this API:­b/master/libs/misc/heartrate.h

    So hrm_new/etc

    Same for step counting and­b/master/libs/misc/stepcount.c

    I'm sure you know this already, but getting good algorithms is really all about getting good test data. For step counting for a while we were using data gathered from just one person and we did a great job of step counting for them - it just sucked for most other people :)

  • I should also add, on the data front it's important to have a bunch of non-walking data too. Working at a computer, cooking, and driving especially.

  • hello again, some news about the new algorithms benchmark.

    Data: we have prepared a data collection protocol, we aim at 20 people, doing different activities, both on treadmill and outdoor walking. The protocol will get an ethical approval from Politecnico di Milano. Right now we don't have the permission to publish the data, but we are planning to obtain it. I understand that the data is what you would mostly appreciate, so I am keen to publish those files, but it may take some time. We are planning to acquire the data at its default sampling frequency for both acceleration and PPG. Would that be OK for you, or would it make more sense to acquire at higher frequencies, in case you want to test out different frequencies?

    Tests: the repository for the tests that you have may need some adaptation though. We are starting with the step counter and we can already see some changes to make. Concretely:

    1. the main.c needs to be adapted to be able to run different algorithms, not just 2 (espruino and "original"), we are going to change it and make it more general. I hope that it's OK?
    2. in terms of output, I would suggest to print out just the totals, comma separated, then that could be copied into a spreadsheet where all sorts of statistics can be done. Or we add stats to main.c, but maybe after the totals.
    3. algorithms would go into a dedicated folder, that includes moving the "original" algorithm into its own .h/.c files instead of being mixed within main.c. We can keep the espruino one linked from the Espruino code so it's always upated to the last version.

    If you are OK with these, we will be sending pull requests, as we advance with the task. You can of course review those and suggest changes if there's anything you don't like.

    Another issue: from what I understand the current API for the step counter (stepcount_new(int accMagSquared)) return a 1 if a step is detected or a 0 otherwise (and in some cases it tries to make up for lost steps too? haven't studied this well yet). I would suggest to change the signature to something that is more generalisable for other algorithms, for example instead of returning the "new" steps, it would return the total step count, since the algorithm was inited (or reset) and instead of accepting the magnitude as argument, it could accept the raw x, y and z. I know that this has repercussions in many places within the Esrpuino code, so it's a lot to ask, but it would make the signature compatible with algorithms that are based on different approaches. Let me know how doable this would be. We can of course decide later, if and when we have some good alternative candidate algorithm.


  • would it make more sense to acquire at higher frequencies, in case you want to test out different frequencies?

    That's a tricky one. A higher sample rate means the HRM sensor will adjust quicker, so even if you take samples out it may not give an accurate comparison. I think maybe just stick with the default rate (for that and the step counting).

    It's worth noting that right now we don't calibrate the frequency so it can depend slightly on the watch. In the OEM firmwares they fine-tune the sampling frequency to exactly 10Hz.

    In terms of changes to the test harness - yes, that all sounds good.

    I would appreciate it if you could try and ensure the existing step count data can run with the step counter too so we can have a clear idea of how new algorithms perform with that too. I don't care about HRM as we don't have much data there.

    I would suggest to change the signature to something that is more generalisable for other algorithms, for example instead of returning the "new" steps, it would return the total step count

    I'd prefer not to handle 'total step count' because we're juggling a few different step totals inside Espruino already and I don't want one more. It feels like a trivial for an algorithm to just keep an internal counter and output the difference between current and last? We can always ensure we handle negative step differences if that needs to happen.

    But on the x/y/z front, yes, that makes complete sense to change the signature. Ideally pass accMagSquared too so existing algorithms aren't having to calculate it out twice.

    ... but before we mess with the Espruino repo, let's get some data and algorithms - you can always ensure your repo uses the new call signature, and then you can just have a stub:

    int stepcount_new_ext(int x, int y, int z, int accMagSquared) {
      return stepcount_new(accMagSquared);

    I'm not sure if this is a good extra thing to have, but some trackers have an 'activity' value for what it thinks you're doing - sleeping, walking, running, etc. Something that could detect that would be a really neat addition (but obviously your data would need marking up somehow)

  • thanks for the availability and the suggestions! You will be receiving pull requests soon 😃. I'll keep the signature as it is for now, and only change it if it is required.

    One little note about the code, I see that you mostly use generic types like short, int, float etc. We will try to keep that style too, although I wonder if it wouldn't make more sense to be more specific and indicate also the n. of bits? What's the philosophy here?

    Re. activity detection: it is not planned at the moment, but it's something I'd like to work on at some point.

  • Great. I'm very happy to use uint8_t/etc types. In Espruino it's generally assumed that int is 32 bits, long is 64 and char is 8, but where I feel it really matters I do tend to use int32_t/etc - but being more explicit going forward can't really hurt

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

Bangle.js 2 Cutting Edge - new heart rate algorithm

Posted by Avatar for Gordon @Gordon