How to encode Floats in BLE advertisements ?

Posted on
  • Hey there!
    I'm using NRF.setAdvertising() to advertise temperature readings from Puck.js.
    The examples in docs sadly only mention byte arrays, integers and string fragments as supported data:

    // straight from official docs
    setInterval(function() {
      NRF.setAdvertising({
        0x1809 : [Math.round(E.getTemperature())]
      });
    }, 30000);
    

    What I'm trying to do is advertise the floating point temperature without rounding. Puck currently reports temperature with 0.25 accuracy, but I'm struggling with how to encode it in a way, that would make EspruinoHub understand it.

    The official BLE GATT spec for thermometer actually supports extra flags in the first byte and temp. reading (default is Celsius) as a Float32.

    The node on puck doesn't sadly provide Buffer so I tried encoding with TypedArrays like so:

    setInterval(function() {
      NRF.setAdvertising({
        0x1809 : temperatureAsArray()
      });
    }, 30000);
    
    function temperatureAsArray() {
      const a = new Float32Array(1);
      a[0] = E.getTemperature();
      const b = new Uint8Array(a.buffer);
      const c = new Uint8Array(5);
      c[0] = 0;    // the first byte set to 0 means a single celsius measurement without timestamp
     // I've also tried without this byte, didn't change anything
      c.set(b, 1);
      const result = [];
      
      for (let x = 0; x < c.length; x ++) {
        result[x] = c[x];
      }
    
      return result;
    }
    

    The resulting array is something like: [0, 0, 0, 236, 65] for temperature of around 21 deg, or [0, 0, 236, 65] if you don't include the "flags" byte and just have BE 32-bit float.

    However, when I receive it in EspruinoHUB, the debug shows me:

    d1:bf:fb:93:39:bf - Puck.js (RSSI -57)
      1809 => {"temp":0}
    

    It seems to struggle to decode those Float32 byte arrays.
    When I sent something like [236, 65] (without the GATT flags and trimmed to just 2 bytes) then it shows me:

    d1:bf:fb:93:39:bf - Puck.js (RSSI -57)
      1809 => {"temp":168.76}
    

    I have no idea how it got 168.75 from those bytes.

    Any idea what EspruinoHub/Noble/bleno expect as a format for floats?

    Have anyone succeeded in encoding floats for BLE Adv. ?

  • I think what you're doing is right (you can also use DataView - which could be a little easier).

    The issue is with the EspruinoHub's decoding. It's doing something pretty simple right now - 8 bit is interpreted as-is, and 16 bit is the temperature x100. I'm not sure if it's spec compliant but it is what the nRF Connect app seems to do.

    https://github.com/espruino/EspruinoHub/­blob/master/lib/attributes.js#L32

    I'm not 100% sure the docs you found are for the UUID 0x1809 that's being used though. They seem to be for 0x2A1C. Having said that there don't appear to be any docs for the 0x1809 UUID when used in advertising (in fact it's almost certainly non-standard).

    ... so I'd be tempted to add a decoder in EspruinoHub for the 0x2A1C UUID, and handle that correctly? Either that, or you could add to the 0x1809 decoder so that it interprets 4 bytes of data as a float?

  • I'm not 100% sure the docs you found are for the UUID 0x1809 that's being used though

    GATT docs list that as:

    Health Thermometer	org.bluetooth.service.health_thermometer­	0x1809
    

    It matches your example in docs. From what I can tell 0x2A1C is a code for "temperature measurement", while 0x1809 is the code for the "health thermometer" service as a whole. Not sure if the NRF library we're using even has a notion of those formats, or is it up for us to provide an implementation.

    The issue is with the EspruinoHub's decoding. It's doing something pretty simple right now - 8 bit is interpreted as-is, and 16 bit is the temperature x100

    Hmm... it seems that you're bitshifting the second value ((((a[1]<<8)+a[0])/100)), which makes it hard for me to reverse it.

    How would you encode a 16 bit float to this that would work on puck (i'm assuming its BE)?

    function encodeFloat(num) {
      const dec = Math.round(n);
      const fractional = Math.round(n % 1 * 100);
      return [ dec, fractional >> 8 ]; // ???
    }
    
  • The bit shift is just a way to turn the two 8 bit values into one 8 bit one.

    You're doing basically the right thing but n % 1 * 100 looks a bit weird to me... Something like this should work:

    function encodeFloat(num) {
      var d = Math.round(n*100);
      return [ d&255, d >> 8 ];
    }
    

    The &255 isn't required (it cuts of the top 8 bits) as we're writing an 8 bit array anyway, but it makes it easier to debug.

    The other option you have is DataView:

    var d = new DataView(new ArrayBuffer(2));
    d.setInt16(0,num*100,true)
    return new Uint8Array(d.buffer);
    
  • Thanks mate! Works like a charm.
    I'll update the docs, maybe it'll be useful for others as well.

  • Thanks!

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

How to encode Floats in BLE advertisements ?

Posted by Avatar for Thinkscape @Thinkscape

Actions