Bangle.js 2 Cutting Edge - new heart rate algorithm

Posted on
of 6
  • So you're seeing this issue with the offset HRM value as well?

    I have not looked too closely for that. Can do that if you want!

    I just wanted to add comparison data for the open algorithm now that I have a chest strap.

  • Ok, great! I think you should still be able to create useful data even with a 'normal' build using the binary blob. While the recorded HRM will be from the binary blob, the raw sensor data will be off the sensor, so when the test harness runs it'll still be able to return data from the open source algorithm.

    Well, now you have the chest strap if you've got time I'd be interested to see if you find that the data from the binary blob is off by 10% as well - I'm not sure if it's something to do with the older VC31 sensor in the original Bangles, or if it applies to the VC31B as well

  • HRM Accelerometer Event Recorder fails with

    Uncaught TypeError: Puck.connect is not a function
        at connect (custom.html:253:8)

    So maybe a change in one of the app loader core updates?

  • Ahh, ok - thanks for looking into it. It's because normally a custom file would communicate with the watch via the functions provided by lib/customize.js but you're including the Puck.js library directly and actually creating a new Bluetooth connection.

    I added Puck.eval and .write to lib/customize.js a while back (and it's const Puck) so that must have messed it up. I'll see if there's a nice way to make to work or if we need to bodge it to use the Puck.js library again

  • Ok, if you try the development app loader then I think it should all work!

  • Thanks!

    It works for the most part. Enabling "Store on bangle" and downloading it from the bangle works. The default mode will start the program on the watch, but not start the count in the app loader and not save any logging.

    Attached is the log downloaded from the bangle and the image generated by running ./

    Captured on custom built firmware (based on 2v19.36 I think) switched over to open source algorithm (last commit before switch to open source algo: 442c0c78892f0756f74ceb3522a6d70e2c885763­).

    2 Attachments

  • That's odd - it might be a GitHub caching issue though that means you hadn't got the latest of all files - it's definitely something I tried here with success before pushing

  • Hello all,

    very interesting developments here!
    I have been trying the new algorithms myself too. I had the Heart Rate Monitor app on the Bangls JS (version 2 bought from Kickstarter campaign, FW 2v19), and another smartwatch plus a PPG from the samrtphone on the other arm. I have noticed some differences, especially at lower HRs, but couldn't do this very systematically. I am planning to do some more tests with a bluetooth Polar ECG belt in the coming days and will report back.

    I have read in this thread that the manufacturer's library also supports SPO2 and Blood Pressure. It would be extremely interesting to receive these 2 as well. I do not trust BP at all, but SPO2 is doable and has been proven reliable on other devices (for example Withings). Is there any plan to support these? And /or to allow collecting PPG from the 2 LEDs (my understanding is that there is a green LED and an Infrared LED)?


  • I believe the IR value is available via vcEnv although to get these readings at the same time as the green readings you need to reconfigure the HRM using I2C. There's an example of doing that at­d=flashcount though (although it may not work as-is on the KickStarter bangles with the VC31 sensor).

    However, just to be clear on this:

    • I do have an SPO2 algorithm binary blob for the VC31B HRM sensor that is in watches that are sold now and (mostly) since the KickStarter, but it requires the sensor to be reconfigured when sensing SPO2 and there are a lot of questions about exactly what's needed (­b/master/libs/misc/hrm_vc31.c#L767-L776)
    • I don't have SPO2 for the VC31 in the KickStarter Bangles - if we can force them to take an IR reading as fast as a green reading then we might be ok but I'm not sure exactly how we'd do that.
    • I don't have a Blood Pressure algorithm at all, for either sensor

    I don't have any immediate plans to implement it but if someone wanted to take a look I could send the binary blob and what info I have. Effectively all you need to do is call the function with IR and green readings at the right speed and it gives you a value

  • I don't have any immediate plans to implement it but if someone wanted to take a look I could send the binary blob and what info I have. Effectively all you need to do is call the function with IR and green readings at the right speed and it gives you a value

    I am willing to take a shot at implementing this

  • That's great, thanks!

    Actually it seems I couldn't email you the zip so I'll attach it here. It's mostly in Chinese so it can be a bit tricky to dig through, but:

    One of the important bits is probably:

    			regConfig[0] = 0x47;
    			regConfig[1] = 0x2F;
    			regConfig[6] = 0;
    			regConfig[7] = 0x80;
    			regConfig[13] = 0x96;
    			regConfig[16] = 0x04; // OverSample

    Which you need to get put in­b/master/libs/misc/hrm_vc31.c#L767-L776 when SPO2 mode is to be enabled.

    Right now we don't auto-calibrate the HRM speed which their code does - I'm not sure if that's going to end up causing problems or not.

    1 Attachment

  • Does the "HRM Accelerometer event recorder" work on bangleJS2 without an attached BT HRM? It opens a screen on the watch and I can see the events count going up on the watch, but the browser page just shows '0', and if I press the 'stop' and 'save' buttons it saves an empty file.
    Could a running widget that acts as a GATT server be interfering (I am running an updated version of the openseizure widget).

    I am thinking of re-visiting the open source HR calculation algorithm because I am finding that the current version is too sensitive to movement - I get very high readings if I am not staying very, very still - I will look at trying an algorithm like this one:­/articles/10.5334/jors.241


  • Sounds very similar to what I experienced above.

    I could choose to save the data on the watch and later download it to my laptop with the Espruino Web IDE.

  • Thank you - I had missed that post - will try the save to watch option and see if that works.


  • I have been looking at the BangleJS2 HRM without much success for the last week or so (I was hoping to just add some filtering and error checking...) - so I thought I'd share where I have got to in case someone else has already tried my ideas and know it is not the problem.
    I am using the latest BangleJS2 firmware (2v19, commit cfbc4040d).
    I used a slightly modified version of the HRM data exporter app which records the sample time as well as the ppg reading in case the sample frequency was not what I was expecting.

    I was surprised that I could not see a heart rate signal from a simple visual examination of the data, so I did some processing to try to get rid of the baseline drift on the signal, but still can not really see anything that looks like my heart rate. The calculated heart rate from the bangleJS is nowhere near correct - it shows something like 90 bpm where my heart rate is probably more like 50 bpm when I am sitting still.
    I have done a little comparison to the signal from a PineTime HRM and the heart rate signal is obvious from that - you can see some graphs etc. on my bug tracker issue here.
    So I am really doubting whether the BangleJS2 HRM is actually working - one thing that is really noticeable is the LED brightness - the pine time is much, much brighter than BangleJS2 (Pinetime on the left, BangleJS on the right)

    I think that if I want to increase the LED brightness, I need to build my own firmware, so I think I'll look at that next, but thought I'd share where I have got to in case anyone else has a better idea?

  • Bangle.setOptions({
      hrmGreenAdjust: false

    Adjust 15, to change intensity.

    Regarding the data, I agree and have same results. I assume the old way of read pin directly with analogRead produced better results for raw ppg manipulation. You would have to uncomment:

    # Standard open-source heart rate algorithm:
    # 'SOURCES += libs/misc/heartrate.c',

    Line 74 of boards/, and recomment the current one.

    Then it doesn't read from Register 0x80 via I2C (HEARTRATE_PIN_SDA) (hrm_vc31.c @vc31b_readfifo) , but instead HEARTRATE_PIN_ANALOG 29 (hrm_analog.c @hrm_sensor_on).

    That is as much as I know atm. However the actual readings of BPM are considered quite good even though its routed through the binary blob, but not sure why it forces the raw values to be bad.

    EDIT: HEARTRATE_PIN_ANALOG 29 is for Bangle.js 1 only!!

  • Thanks - I hadn't realised I could alter the LED intensity without rebuilding the firmware - I'll try that first, then have a look at using the analogue output as you suggest.

  • Interesting, at one point I ran some motion artifact algorithms from papers like WPFY but they always failed because of the noisy data. Maybe I should try again with the changed LED intensity.

    it shows something like 90 bpm where my heart rate is probably more like 50 bpm when I am sitting still.

    ok this is unexpected. Just to make sure, you did remove the plastic cover from the sensor? Because from my experience the readings only become bad if movement is involved.

  • This code can be used to view the data, might be useful. It is using analogRead from old method of reading pin directly instead of talk through i2c. Could be interesting to compare outputs. Pass hrm.raw into instead of val to c.onHRM to see difference. (You have to adjust graph constraints too, hrm.raw are huge numbers between 0 - 10000 ).
    EDIT: It is using wrong pin (29), its not hrm data

    var c = E.compiledC(`
      // void onHRM(int,int,int)
      const unsigned char windowSize = 25;
      typedef struct inputs {
        short * vals;
      } inputs_t;
      void onHRM(unsigned int &tick,int val,inputs_t *argss){
        argss->vals[tick % windowSize] = val;
    let newUint32FlatArray = len => {
      let fs = E.toFlatString((new Uint32Array(len)).buffer);
      if ( !fs ) throw new Error("Required to be a flatstring");
      let obj = new Uint32Array(E.toArrayBuffer(fs));
      return {"obj" : obj, "ref" : E.getAddressOf(obj,true) };
    let newInt16FlatArray = len => {
      let fs = E.toFlatString((new Int16Array(len)).buffer);
      if ( !fs ) throw new Error("Required to be a flatstring");
      let obj = new Int16Array(E.toArrayBuffer(fs));
      return {"obj" :  obj, "ref" : E.getAddressOf(obj,true) };
      hrmGreenAdjust: false
    let windowSize = 25;
    var vals = newInt16FlatArray(windowSize);
    var inputs = newUint32FlatArray(6);
    inputs.obj[0] = vals.ref;
    let tickFlat = newUint32FlatArray(1);
    tickFlat.obj[0] = 0;
    let tickCountOld = 0;
    //This is called by idleLoop. Not an interrupt.
    Bangle.on('HRM-raw', function(hrm) {
      let val = Math.round(analogRead(29)* 16383);
    //25Hz, Bangle.js 2
    //50Hz, Bangle.js 1
    setInterval(function hmm(){
        let tick = tickFlat.obj[0];
        tickCountOld = tick;
        let nonCircVals = [];
        //circular to non-circular.
        for ( let i = 0; i < windowSize; i++ ) {
          nonCircVals.push(vals.obj[(tick + 1 + i) % windowSize]);
        require("graph").drawLine(g, nonCircVals,{axes:false,miny:-50,maxy:50­,gridy:25});
  • To set constraints of graph with min/max...

    let nonCircVals = [];
        let min = 40000;
        let max = -40000;
        //circular to non-circular.
        for ( let i = 0; i < windowSize; i++ ) {
          let v = vals.obj[(tick + 1 + i) % windowSize];
          if ( v < min ) min = v;
          if ( v > max ) max = v;
        print(`min is ${min}, max is ${max}`);
        require("graph").drawLine(g, nonCircVals,{axes:false,miny:min,maxy:ma­x,gridy:(Math.abs(max-min))/8});

    The heartrate.c implementation also applies a band pass filter, which greatly helped before.

  • @user140377 If there is a plastic film cover over my sensor, it is very well attached - I can't detect it.
    I thought from reading this thread that most people were finding that the Bangle bpm was lower than manual measurements, but I am seeing higher - it is consistent with my watch as I had someone else wear it and got a similar answer. I'll try the LED intensity and see if it makes any difference.

  • The watch is usually shipped with the cover, so unless you removed it or it accidently fall off I would check again.

    I thought from reading this thread that most people were finding that the Bangle bpm was lower than manual measurements

    Thats because of noise due to motion artifacts, e.g. when walking with 60 BPM with heart rate = 100 BPM the sensor will give you the 60 BPM walking (or a mix of both). This does not apply when sitting still.

  • This has a rolling average windows of size 8. And includes the hrm band pass filter from heartrate.c­fe03c949449bfae9a50ae5

  • The output from hrm.raw stops working for me when Bangle.hrmWr(0x17,X), X >8. X==7 is the sweet spot.
    Yet the analogRead(), does not seem affected by the LED current change.
    EDIT:This is mostly just my code doing fancy things and not related to LED

  • Thank you very much for this example, d3nd3-o0. I have never seen inline C code in javascript before, so this is a bit of an education for me!
    I will have a go with your example tomorrow because my attempt to implement the PineTime HRM graph app in javascript is not being very successful - I was assuming that the 'on hrm-raw' functions etc. were interrupt driven, but I was taking too long to draw the graph and blocking it, so need to be more careful.

    I see from the source code that there is another register that is supposed to adjust the LED current - but I find that if I try to set this, then the HRM stops updating after about 50 samples. I can see the LED intensity changing with register 0x17 as you suggested though.
    But so far, I have not seen a stunning effect on the measured signal - will do some more digging tomorrow - thank you all for your suggestions!

  • 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