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:
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?characteristic.read()
to get the value of the Flags
?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).data
for the callback in onReadRequest()
. Is what I have above correct?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.
An attempt to answer your question:
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
});