How do I publish multiple pieces of BLE characteristic data using bleno

1.1k Views Asked by At

I'm trying to learn how to implement a BLE peripheral device using bleno. I would like to discover and read from the peripheral using noble. For example sake, I want to know how I would implement a simple smart scale that reports back weight, BMI etc following the Weight Measurement GATT spec.

What I can't figure out is if reading multiple pieces of information from a characteristic is possible. The Weight Measurement GATT spec makes it seem like in a single noble characteristic.read() you can simultaneously retrieve Weight, BMI, Height etc.

For example, this simple bleno characteristic:

'use strict';

const bleno = require('bleno');

const flags = {
  IMPERIAL_WEIGHT: 1 << 0,
  USER_ID_PRESENT: 1 << 2,
  BMI_AND_HEIGHT_PRESENT: 1 << 3
};

module.exports.flags = flags;

module.exports.WeightMeasureCharacteristic = class WeightMeasureCharacteristic extends bleno.Characteristic {
  constructor(scale) {
    super({
      uuid: '2A9D',
      properties: ['read'],
      descriptors: []
    });
    this._scale = scale;
  }

  onReadRequest(offset, callback) {
    //Not sure what `offset` means here or how it gets populated...Help!

    let data = new Buffer.alloc(8); //1(flags)+2(weightImp)+1(userId)+2(BMI)+2(heightImp)

    //Write supported value fields as bit flags
    data.writeUInt8(flags.IMPERIAL_WEIGHT | flags.USER_ID_PRESENT | flags.BMI_AND_HEIGHT_PRESENT), 0);

    //Write out weight (lbs) - offset 1 byte
    data.writeUInt16LE(100.01, 1);

    //Write out user id - offset 12 bytes (flags+Imperial, no need to include offset for SI or Timestamp since the flags indicated those are not supported)
    data.writeUInt8(69, 3);

    //Write out BMI - offset 13 bytes (after UserId)
    data.writeUInt16LE(18.6, 4);

    //Write out Height Imperial - offset 17 bytes (after Height SI)
    data.writeUInt16LE(72.2, 6);

    callback(this.RESULT_SUCCESS, data);
  }
}

If someone was able to implement/pseudocode onReadRequest() above I think it would help things click in my head.

Questions:

  1. Does the C<number> value in the "Field Requirement" column of the spec indicate the offset value passed into onReadRequest()? If a consumer wanted to get "Weight - SI"(C1) they would somehow construct a noble characteristic.read() that triggers an onReadRequest(1,function())? If so, how is the characteristic.read() constructed?
  2. How do I construct a noble characteristic.read() to get the value of the Flags?
  3. How do I construct a noble characteristic.read() that will return me multiple (or all) properties in one read? Ex: Give me all values this peripheral supports (Weight - SI, BMI etc).
  4. If my peripheral supports imperial weight, user id, bmi, and height how do I populate the data for the callback in onReadRequest(). Is what I have above correct?
  5. How is offset populated & what does it mean in onReadRequest(offset,callback)?

Or, am I doing this all wrong? Should I have a characteristic for each value? Ex: a single characteristic for weight - SI, and another characteristic for BMI? I would like to avoid this, would prefer to save round trips and get multiple values in one call.

1

There are 1 best solutions below

4
On BEST ANSWER

An attempt to answer your question:

  1. I am not sure what the C<number> means, but I believe that each field (Weight, BMI, Height, etc.) is represented as a group of one or more octets. Notice how at the bottom of the spec it says

Note: The fields in the above table are in the order of LSO to MSO. Where LSO = Least Significant Octet and MSO = Most Significant Octet.

Thus, I believe that in order to get the "Weight - SI" field, you would do something like:

    characteristic.read((err, data) => {
      let char_flags = data.readUint8(0); // read first bit
      if (!(char_flags & flags.IMPERIAL_WEIGHT)) // if SI weight
        let weightSI = data.readUint16LE(1) // read SI weight starting at second bit
    });
  1. Answered above
  2. Answered above, just check whether the property exists in the flags and then read the value from the appropriate offset. Also this might help.
  3. Answered above.