• Hi, I'm struggling with an issue and would really appreciate any help! I'm trying to implement a simple web application that connects to my Bangle.js 2 and visualises the received heart rate data. I would like to advertise heart rate service as as GATT service following all the specifications. I've tried many many versions of the code below (loading it via Espruino IDE for now) but nothing seems to work.. None of the services can be found when using nrFConnect and LightBlue. I've tried both NRF.setAdvertising like so (simplified version):

    function onInit() {
      Bangle.setHRMPower(1);
    
      Bangle.on('HRM', function(hrm) {
        var data = {
          hrmConfidence: hrm.confidence,
          hrmValue: hrm.bpm
        };
    
        NRF.setAdvertising({
          0x180D : [hrm.confidence, hrm.bpm]
        }, {
          discoverable: true,
          connectable: true,
          scannable: true,
          whenConnected: true,
          interval: 600
        });
      });
    }
    
    onInit();
    

    I've also tried a more precise way using NRF set and update services:

    function onInit() {
      Bangle.setHRMPower(1);
    
      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            broadcast: false,
            readable: true,
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      }, { advertise: ['180d'] });  
    
      Bangle.on('HRM', function(hrm) {
        NRF.updateServices({
          '180d': {
            '2a37': {
              value: [hrm.confidence, hrm.bpm],
              notify: true
            }
          }
        });
      });
    }
    
    onInit();
    

    However, when I send my heart rate data using UART (following this tutorial here: http://www.espruino.com/Bangle.js+Data+S­treaming#bonus-2-heart-rate-monitoring-g­raph), it works correctly and I'm able to receive this data on my web app:

    function onInit() {
      Bangle.setHRMPower(1);
    
      Bangle.on('HRM', function(hrm) {
        var data = 'H,' + hrm.bpm.toString() + ',' + hrm.confidence.toString();
        Bluetooth.println(data);
      });
    }
    
    onInit();
    

    I would really appreciate any help regarding this as I'm new to Espruino and Bluetooth in general and struggling to find a solution. Just a quick note: my web app is currently not scanning for the services but I would assume that if advertising works correctly, it should come up on nRFConnect and LightBlue.

    Thanks for your help in advance!

  • I just tried to run

    NRF.setAdvertising({
          0x180D : [100, 80]
        }, {
          discoverable: true,
          connectable: true,
          scannable: true,
          whenConnected: true,
          interval: 600
        });
    

    on 52840 dongle with recent 2.17 build and it simply works, I see
    Service Data 0x180d Data:0x6450
    in nrfconnect on android near the device name in scan list and also see values in the "RAW" popup.
    It even works when already connected to the device due to whenConnected: true, device is still advertising.

    As for the setServices, that worked too

      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            broadcast: false,
            readable: true,
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      });
    

    I just tried it without and also with the , { advertise: ['180d'] } part and both work for me.
    With the part in I see 0x180d both in service uuids and stil also as as direct servicee data as before.

    When connecting via nrfconnect I see "Heart Rate" service and can read the value of 0 bpm

  • And BTW the "more precise way" is something completely different, you can have one or the other or both but they are unrelated, it is not 'more precise' way.

  • Hey there I got your example working using following code:

      NRF.setServices({
      0x180D: { // heart_rate
        0x2A37: { // heart_rate_measurement
          notify: true,
          value : [0x06, 0],
        }
        }
      }, { advertise: [ '180D' ] });
      
      Bangle.setHRMPower(1);
      Bangle.on('HRM', function(hrm) {
        NRF.updateServices({
          '180d': {
            '2a37': {
              value: [0x06, hrm.bpm],
              notify: true
            }
          }
        });
      });
    

    With this code, I could see the heart rate inside the nRF connect app as well as in FitoTrack for Android. However, I got two problems to solve: First, setServices requires a BLE restart. Secondly, if the device is already connected to e.g. gadgetbridge it is not discoverable except when I trigger it within the bluetooth settings.

  • @user155613 Thank you for your feedback and signalling potential problems!

  • @fanoush Thank you for pointing this out!

  • Glad it's sorted! The BLE restart happens automatically if the Bangle is not connected, or as soon as the Bangle becomes disconnected - which I guess should happen in most cases anyway.

    On Bangle.js 2v18 firmware you can actually use Advertising and request it to keep advertising even while the Bangle is connected, so that could be an option - but Web Bluetooth's handling of scanning for Advertising isn't available on as many platforms as Web Bluetooth connections .

  • On Bangle.js 2v18 firmware you can actually use Advertising and request it to keep advertising even while the Bangle is connected, so that could be an option - but Web Bluetooth's handling of scanning for Advertising isn't available on as many platforms as Web Bluetooth connections .

    Does this mean that calling setAdvertising would suffice on 2v18?
    Can I advertise complex objects the same way i would using setServices?

  • You need to call http://www.espruino.com/Reference#l_NRF_­setAdvertising with whenConnected:true

    I'm not entirely sure what you mean about complex objects - you can advertise multiple services, but there is only so much space available in the packet (it's ~20 bytes).

  • Sorry for the confusion! I want to advertise this object in order to comply to the standard:

    0x180D: { // heart_rate
       0x2A37: { // heart_rate_measurement
          value : [0x06, 0],
       }
    }
    
  • I want to advertise this object in order to comply to the standard:

    which standard? GATT Heart Rate Service documented here
    https://www.bluetooth.com/specifications­/specs/heart-rate-profile-1-0/
    is different from just advertising the data. Just went briefly through HRP_V10.pdf and don't see that you should advertise it "to comply to the standard"

    You are mixing two relatively unrelated BLE features. Advertising is GAP. Services/characteristics is GATT. GATT works over connections, advertising does not.

  • Yes, unfortunately you can't do that and have it magically work with apps that are expecting the HRM Service on a connection. What you can do is advertise 0x2A37 : [0x06, 0] and it's possible that an app like NRF Connect might pick it up, but it's not the same as the HRM spec and it won't work with apps unless they are designed for it.

  • @AnotherStranger @Gordon Sorry to bother you again but the issue pointed out by AnotherStranger is actually not allowing me to properly develop an app. The following code, even though makes the heart rate service available, throws this error in the IDE:

    function onInit() {
      Bangle.setHRMPower(1);
      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            broadcast: false,
            readable: true,
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      }, { advertise: ['180d'] });  
      Bangle.on('HRM', function(hrm) {
        NRF.updateServices({
          '180d': {
            '2a37': {
              value: [hrm.confidence, hrm.bpm],
              notify: true
            }
          }
        });
      });
    }
    onInit();
    

    Trying to resolve the issue with the need for restart does not seem to resolve the problem. I have tried this:

    function onInit() {
      Bangle.setHRMPower(1);
    
      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      }, { advertise: ['180d'] });
    
      Bangle.on('HRM', function(hrm) {
        NRF.updateServices({
          '180d': {
            '2a37': {
              value: [hrm.confidence, hrm.bpm],
              notify: true
            }
          }
        });
      });
    }
    
    // Restart Bangle.js to apply the updated advertising
    Bangle.on('kill', function() {
      NRF.setAdvertising({}); // Stop advertising
      load(); // Restart Bangle.js
    });
    
    onInit();
    
    

    I have also tried this (introducing a timeout):

    function onInit() {
      Bangle.setHRMPower(1);
      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            broadcast: false,
            readable: true,
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      }, { advertise: ['180d'] });  
      Bangle.on('HRM', function(hrm) {
        setTimeout(function() {
          NRF.updateServices({
            '180d': {
              '2a37': {
                value: [hrm.confidence, hrm.bpm],
                notify: true
              }
            }
          });
        }, 500); // Delay in milliseconds before updating services
      });
    }
    
    onInit();
    
    

    This code still resulted in an error:
    in function called from system
    Uncaught Error: Can't update services until BLE restart
    at line 1 col 79
    ...idence,hrm.bpm],notify:true}}});

    Is there an established way to do this that I've missed perhaps? Thank you in advance!

  • Uncaught Error: Can't update services until BLE restart

    You need to disconnect everything including the IDE from the device to let the BLE stack restart automatically, then it will start working. You can wrap the update in try catch block if the exception breaks something.

  • Thanks everyone! The problem of the app not continuously advertising the data when deployed as an app and not via Espruino IDE was actually the fact that the program would quit before advertising anything. So I needed to implement a keep alive function. The app is functioning now. Thank you!

  • Hi, maybe just try:

      Bangle.setHRMPower(1);
      NRF.setServices({
        '180d': {  // Heart Rate Service UUID
          '2a37': {  // Heart Rate Measurement Characteristic UUID
            value: [0x00, 0x00],  // Initial value: HRM not available
            broadcast: false,
            readable: true,
            notify: true,
            description: "Heart Rate Measurement"
          }
        }
      }, { advertise: ['180d'] });  
      Bangle.on('HRM', function(hrm) {
        try {
          NRF.updateServices({
            '180d': {
              '2a37': {
                value: [hrm.confidence, hrm.bpm],
                notify: true
              }
            }
          });
        } catch (e) {
          NRF.disconnect();
        }
      });
    

    No need for the onInit stuff.

    You'll need to connect to the Bangle after you run the app. The NRF.disconnect() in the try...catch will force the Bangle to disconnect if you'd forgotten to disconnect before (if the services hadn't updated) and then you'll have to reconnect

  • @Gordon Thanks for pointing this out - moving the code outside the init() function does not require setInterval to keep the program alive, as it is done directly by HRM events. Thank you! Just wanted to as you one last thing - for now the app only runs when it is open - as soon as I get back to the main menu, the advertising stops. Is this an intended way to do it or is there a way to run it in the background? I've tried playing with

    g.clear();
    Bangle.setUI("clock");
    Bangle.loadWidgets();
    Bangle.drawWidgets();
    

    but it recreates the main screen in an arbitrary way. As soon as I want to interact with the menu (press the side button), the program stops.

  • Yes, that's the intended behaviour - basically because code is of varying quality, if we left all apps in charge of cleaning themselves up, many wouldn't and Bangle.js would be very unreliable after a while.

    So what we do is when loading a new app we tear everything down, and restart it. Some apps like some clock/launchers implement 'fast load' which lets them swap between themselves quickly without that full load but on the whole when launching apps everything gets removed.

    You can add 'boot code' (a file called myapp.boot.js) which always runs every time you switch apps, so you can put your code in there - I assume this is you actually? https://github.com/espruino/BangleApps/p­ull/2802

    So I probably already answered this when it was asked in the PR too :)

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

Resolved: advertising a GATT service (heart rate) does not work but UART does

Posted by Avatar for user155593 @user155593

Actions