Bangle.js 2 Cutting Edge - new heart rate algorithm

Posted on
of 6
  • I have just tested a garmin heartrate strap compared to the Heart Rate Monitor app of Bangle2. The readings are Way off, so far that the strap was reading 120 and the app 60. This was whilst being on a gym bike. So I don't know what to call the current HR reliability, its almost non-existent I would imagine.

    It has also shown 120, whilst the strap shows 80. Considering the watch is fed accel data, would the faulty readings(spikes noticed with onTap) affect it?

    Part of the reason the first test was super bad for me was because riding a stationary gym bike doesnt' trigger accel changes, so automatic sportsMode didnt' kick in. After using Bangle.setOptions({hrmSportMode: 1}) , it improved, but sometimes in a bad way, like above.

    I will try hrmSportMode 2, for bike. Ok results: I was actually surprised how well it keeps up with the hrm in sportsMode 2, seriously. It seems to struggle going above 100, if eg. the strap says 130, it can remain at 100.
    But its accuracy within 70-100 is really good. So its doing something right!

    But something is also wrong with it :D, like it gets stuck often. Or wanders into weird hrm territory for no reason.

    Btw its fair to assume hrmSportMode 1==2, as gordon pointed out previously, haven't noticed a difference between the two yet.

  • I can confirm the indoor bike issue, had the same in the last weeks, it measured the cadence of ~80 BPM.

    So I don't know what to call the current HR reliability, its almost non-existent I would imagine.

    I would not go that far, when walking or cycling outdoor it is not that off.

  • ... I wonder if we should just make a small app that forces one of the sport modes:­b/master/libs/misc/vc31_binary/algo.h#L1­0

  • I think hrmSportsMode is the algorithm that becomes dependent on Accel data, because in the example source it takes x,y,z input. hrmSportsMode 0 also worked well for me too at times. I think its either slow and has to take a while to follow true heartrate or the signal is sometimes too noisy for the current alg.

    Because at times, I am very impressed, but its short-lived. Does anyone know if its best to measure time between 2 consecutive beats, or to count peaks over time? I'm starting to think the time-measuring is the way to go.

  • Algorithms like Tapir do count the time between peaks after some filtering.

  • ROFL, I was measuring wrong pin entire team I think haha.!!! :D I used the pin from Bangle 1!!

  • Did a small test on indoor bike (not very representative):
    sports mode=-1 (auto): Bangle internal: 111, BT belt: 135
    sports mode=0 (normal): Bangle internal: 86, BT belt: 136
    sports mode=1 (running): Bangle internal: 103, BT belt: 139
    sports mode=2 (ride bike): Bangle internal: 89, BT belt: 134
    and just for fun:
    sports mode=0 (normal), hrmmar=fftelim: Bangle internal: 131, BT belt: 138

    so far "running" was best, so lets try another sample:
    sports mode=1 (running): Bangle internal: 124, BT belt: 127

  • sports mode=0 (normal), hrmmar=fftelim: Bangle internal: 131, BT belt: 138

    What is this line representing? Ah, the motion removal app!

  • I have made a little test app that will record raw HRM data to a file, and allow me to vary the LED power by swiping the screen up or down.
    The things I have found are:

    1. The photodiode reading varies from 8190 counts (13 bits?) to zero
    2. Increasing the LED current decreases the reading
    3. The default current register value of 15 (decimal) is pretty good and gives a mid range photodiode reading.

    As long as I don't try to plot the graph on the watch screen, I can log at about 40 ms intervals, and can pull it into a speadsheet to look at it:


    From a visual inspection of the first 5 seconds, I can see about 5 peaks, which is consistent with my actual heart rate of around 60 bpm, which is encouraging. Unfortunately the watch is reporting over 100 bpm, which is not so impressive. It sounds like this is the opposite to what other users are reporting, as others are reporting low indicated heart rate compared to actual.

    So I'm still not sure what to make of this, so I'm going to try to do the same on Pinetime and see how it compares, as I am still suspecting that the HRM sensor in my watch is not really working very well. If anyone else could try to collect a similar set of data on a different watch, I'd be interested in seeing the comparison of different BangleJs watches.


  • But something is also wrong with it :D, like it gets stuck often. Or wanders into weird hrm territory for no reason.

    I found that I was getting occasional I2C errors when reading registers from the HRM device. In the end I caught these errors and re-set the HRM device when it occurred - that seems to have stopped my problem with logging freezing (­angleSD/blob/master/apps/hrmtest/app.js#­L240)

  • Something like this?

    1 Attachment

    • bpm0.png
  • Thank you for the graph. The peaks in your data do look easier to see than mine (but that might just be the graph scale - I'll have a proper look later) It looks like just over 60 bpm. Is that right? Did the watch report something similar?


  • ~80 BPM was reported by the Bangle

  • Pin 29 was not true. Its only for bangle 1, please delete that information, sorry for that. I am in the progress of testing your app, I'll keep you posted.

  • @Graham_Jones My sensor only begins to work at LedCurrent : 7 , I used your app and have to swipe down until its 7, else I get readings of 0. Strange you don't have that? unless its lighting related? Even 7 is sometimes too high, requirement seems to vary.

    Also your app, I can't see a graph, its empty below it.

    It seems that the photodiode reading increases the closer to 0 I go with LED current.
    LedCurrent: 0 , Average: 5800
    LedCurrent: 1 , Average: 5000
    LedCurrent: 2, Average: 3200
    LedCurrent: 3, Average: 1600
    LedCurrent: 4, Average: 800

    Some kind of effect like that. Its interesting that, I didn't notice this before, thanks to your app :)

    I think the real max value is 8190, despite it being stored in a 16 bit short?
    Probably why auto adjust ON is the best. Hopefully its keeping it within good bounds? Like sometimes my ledCurrent: 0 creates too high values ,so it only reads 8190 cos of some clamp somewhere.

    Based on this idea, perhaps the target photodiode value should be half way between 0 and 8190 , 8190/2 == 4095

    I will try in different lighting condition. Even in dark room, the conditions are same for me. The 0x80 last bit of the LedCurrent value is a PpgGain btw, so at values between 129->136, I get some numbers , but it seems gain is bad because the swings are higher so you need to adjust led more often.

    Would be nice to see if @user140377 has anything similar to this ( the low LED current requirement ), we could compare to figure if any of our bangles are different/defective.

    What I like others to test:

    To be clear:

    if my LEDcurrent(reg 0x17) are above some very low number, eg. 5,
    The signal of the photodiode drops to 0.
    And there is an inverse scaling, the closer LEDcurrent is to 0, the Higher the photodiode values

    Also, confirm your output of : Bangle.hrmRd(0) is 33 , confirming you have the vc31B device and not the vc31.

    @Graham_Jones There are hrm->csv recorder apps already on the app store (namely the "recorder" app by gordon (seems beats only), or "HRM Accelerometer event recorder" by halemmerich, your app store is a bit out of date. Just in case you did not know.­ree/master/apps/hrmaccevents­MTestHarness

  • Pin 29 was not true. Its only for bangle 1, please delete that information, sorry for that.

    No problem - that accounts for why I was measuring noise!

  • @d3nd3-o0 - thanks for this detail - I will go through it and reply properly, but it might be tomorrow evening or the one after. A couple of quick thoughts:

    I will check the ID from the sensor - I was starting to wonder if maybe I have a different sensor (I only purchased the Bangle2 recently)

    For me, a LED current value of 15 seemed about right - the automatic adjuster varied it from about 13 to 22, so our watches do seem to be behaving differently if you had to use a much lower current (unless our skin colours are very different maybe - that might have an effect, so could be another reason for needing automatic current control for different users.).

    My copy of the app store is very minimalist on purpose - I am thinking of pointing users of my seizure detector app to it if I can get the heart rate measurement working reliably, and I only want them to install apps that I now will not interfere with the seizure detector (some users like to fiddle.....).

    Alas, I had to disable the graph drawing part of my little HRM test app because drawing to the screen seemed to take such a long time (over 1 second), and it blocked the HRM measurement while it was happening - it seems that the HRM measurements are not interrupt driven so if I want to achieve 40ms sample period I have to be very careful about not doing too much.... My little app is based on one of the others in the app store - I modified it to try to make it easier to try out different settings (and the idea was to have a graph at the same time to see the effect in real time). I don't get on very well with javascript so had a bit of a sense-of-humour failure at it and de-scoped my intentions for the test app!



  • { "vcPPG": 1419, "vcPPGoffs": 0, "isWearing": true, "adjusted": false,
      "vcPre": [ 4, 0 ],
      "vcPS": 15,
      "vcEnv": [ 0, 0, 0 ],
      "vcIRQ": 4,
      "vcRaw": new Uint8Array([0, 4, 130, 4, 0, 15, 5, 128, 224, 87, 55, 103]).buffer,
      "raw": 2838, "bpm": 60, "confidence": 80, "filt": -26624, "avg": 2929 }

    Look at the object from hrm-raw event. I noticed in halemmerich's code that he logs also e.vcPPG. I wonder how this value differs from raw.

    I conclude that hrm.raw is the sum of the e.vcPPG and e.vcPPGoffs, then multiplied by 2.


    int v = vcInfo.ppgValue + vcInfo.ppgOffset;
      if (vcType == VC31B_DEVICE)
        v <<= 1; // on VC31B the PPG doesn't vary as much with pulse so try and bulk it up here a bit
  • gnuplot -e "load 'plotscript.gnu'"

    # 5 = beats
    # 6 = confidence
    # 7 = hrm.raw=(ppg+offset)*2
    # 9 = ppg
    # 10 = offset

    set datafile separator ","
    filename = system("echo 'hrm1.csv'")
    first_timestamp = system("awk -F',' 'NR==2 {print $1}' hrm1.csv")
    set xlabel 'Time (seconds)'
    set ytics nomirror
    set ylabel 'ppg'
    plot filename using (($1 - first_timestamp) / 1000.0):7 with p title 'raw' axes x1y1
    replot filename u (($1 - first_timestamp) / 1000.0):10 with l t 'offset' axes x1y1
    set y2tics nomirror
    set y2label 'beats'
    replot filename using (($1 - first_timestamp) / 1000.0):5 with p title 'beats' axes x1y2
    # Pause to keep the plot window open
    pause -1
  • Created using :­ree/master/apps/hrmaccevents and gnuplot file above.

    Longer recording. ( Did some bike activity at end, real HR went way above 100 ). Shows ppgOffset in green, which is when the ledCurrent is adjusted. I now just have to get some chest strap comparison data.

    1 Attachment

    • plot_with_beats.png
  • Acceleration data plotted against ppg

    1 Attachment

    • plot_with_mag.png
  • Whats interesting is that the amplitude of the ppg seems to exist in the beat count, I wonder if that is an error or a feature.

  • @d3nd3-o0 you're trying to use analogRead on Bangle.js 2? If so that's not going to work - it doesn't have an analog sensor.

    With the LED brightness, the heart rate monitor code attempts to adjust the LED brightness to keep the reading from the HRM within range. So if the HRM isn't pointing at anything the LED brightness isn't going to be the 'real' brightness you see.

    If anything it's surprising you can see it at all, as Bangle.js should turn off the green LED after a few seconds of the HRM sensor not being near anything.

  • So if the HRM isn't pointing at anything the LED brightness isn't going to be the 'real' brightness you see.

    Not sure which part this is response to. Keep in mind these tests were often done with hrmGreenAdjust false, and hrmWearDetect false, although, you only have to lift the watch a tiny bit to peek at the led without triggering wear detect.

    @Gordon Welcome Back, hope you had a merry holiday period.

  • 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