bluetoothuint16

Why is documented Bluetooth Unit8 value being read as a Int16?


What have I tried?

I read through GATT_Specification_Supplement_v10.pdf and previous publicly available versions of the document mentioned.

I looked at stack overflow questions and 3rd party bluetooth libraries.


For 3.125 Indoor Bike Data characteristic in GATT_Specification_Supplement_v10.pdf resistance level is a UInt8 but in practice its a Int16. Meaning it take two octets or two array values of types UInt8 to represent the resistance level.

enter image description here

Additionally when browsing stack overflow I found this 3rd party documentation that documents the value as a SInt16.

enter image description here

https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/ibd-0000001051005923

I also looked at a 3rd party library that parses this value as a Int16. https://github.com/FitnessKit/BluetoothMessageProtocol

// File: CharacteristicIndoorBikeData.swift
// Line: 172
var resistanceLevel: Int16?
if flags.contains(.resistanceLevelPresent) {
     resistanceLevel = decoder.decodeInt16(data)
}

Do you think I'm misunderstanding something? Is it an issue with the documentation? Which I doubt would be the case.


Raw Data

Here is the raw data as requested.

// Raw Byte Array Data - represented as decimal integers
▿ 19 elements
  - 0 : 116
  - 1 : 3
  - 2 : 0
  - 3 : 0
  - 4 : 62
  - 5 : 0
  - 6 : 0
  - 7 : 0
  - 8 : 0
  - 9 : 16
  - 10 : 0
  - 11 : 0
  - 12 : 0
  - 13 : 0
  - 14 : 0
  - 15 : 0
  - 16 : 0
  - 17 : 0
  - 18 : 0
// Here is data I generated while actually riding the bike for a few seconds.
// resistance level in bike was updated during the ride, from 17 to 22.

[116, 3, 0, 0, 96, 0, 163, 0, 0, 19, 0, 0, 0, 2, 0, 0, 0, 0, 0]
[116, 3, 178, 12, 176, 0, 171, 0, 0, 17, 0, 162, 0, 3, 0, 0, 0, 0, 0]
[116, 3, 66, 14, 206, 0, 181, 0, 0, 17, 0, 219, 0, 3, 0, 0, 0, 0, 0]
[116, 3, 126, 14, 226, 0, 192, 0, 0, 16, 0, 229, 0, 3, 0, 0, 0, 0, 0]
[116, 3, 146, 14, 224, 0, 202, 0, 0, 16, 0, 233, 0, 3, 0, 0, 0, 0, 0]
[116, 3, 142, 13, 184, 0, 212, 0, 0, 16, 0, 192, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 78, 12, 154, 0, 221, 0, 0, 18, 0, 150, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 170, 10, 120, 0, 229, 0, 0, 18, 0, 103, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 126, 9, 100, 0, 236, 0, 0, 18, 0, 77, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 152, 8, 84, 0, 236, 0, 0, 19, 0, 57, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 98, 7, 60, 0, 242, 0, 0, 21, 0, 42, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 234, 6, 50, 0, 247, 0, 0, 21, 0, 31, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 114, 6, 40, 0, 252, 0, 0, 21, 0, 22, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 114, 6, 40, 0, 0, 1, 0, 21, 0, 22, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 100, 5, 28, 0, 4, 1, 0, 22, 0, 12, 0, 4, 0, 0, 0, 0, 0]
[116, 3, 100, 5, 28, 0, 8, 1, 0, 22, 0, 12, 0, 4, 0, 0, 0, 0, 0]
// For convenience here is the flags field represented as a 16bit binary number
1101110100

Solution

  • Have you tested actual devices? I would absolutely expect errors in the documentation. I've found several over the years. The more I dig into this characteristic, the more of a mess it seems to be.

    They list d=1, which means that resistance has a resolution of 10s of units which makes no sense (that would be represented values of 0, 10, 20, 30,... up to 2550). I'm sure they mean d=-1 (tenths of units). See the GATT Supplement section 2.3.2 "Scalar Values" for what I'm talking about.

    The test plan describes the "Target Resistance Level Changed" notification as having a payload of "New Target Value (UINT8, unitless with a resolution of 0.1) e.g., 5.0," which matches my d=-1 belief.

    But the test plan also says that the Supported Resistance Level Range should be 6 octets (there are 3 values there, so that would be int16). And the "Set Target Resistance Level Procedure" also takes an sint16.

    But if you read the FTMS spec, "Set Target Resistance Level" claims to take a "UINT8, unitless with a resolution of 0.1."

    But the old (no longer supported) xml docs list this as an unscaled sint16:

    <Field name="Resistance Level">
      <InformativeText>Unitless with a resolution of
      1</InformativeText>
      <Requirement>C6</Requirement>
      <Format>sint16</Format>
      <Unit>org.bluetooth.unit.unitless</Unit>
    </Field>
    

    It's a mess.

    IMO, uint8,M=1,d=-1,b=0 (i.e. scaled by 0.1) is the only scale that really makes sense for this value. sint16 is a waste of precious bytes (the spec discusses this problem in section 4.19) and doesn't even handle fractional levels, which are common. And signed? Are there negative resistances? I don't know why sint16 snuck in there. My guess is it was a cut-and-paste error at some point because several of the nearby values are sint16. But maybe it was on purpose at some point.

    That said, given the ambiguity between the spec and the test cases, my expectation is that real-world devices do both, and if your code needs to work with many devices, you will need to look at the length and decide what to do. I also bet some send uint8 with a resolution of 1 and others send uint8 with a resolution of 0.1, so you'll may have to look at the size of the value to decide what it probably means. Folks always forget the scaling factor....

    (On the other hand, the fact that Huawei's library only supports sint16 might mean that sint16 is pretty ubiquitous in the market. Or it might mean that Huawei's library doesn't work with all products. That's a thing.)

    Welcome to Bluetooth development. Do not assume the docs represent what all real products do, or even are 100% self-consistent. Honestly, the docs are not terrible, but there are definitely mistakes, and I think this part of this spec is a mistake. The only question is what products you need to work with have done about that.