0

This is a follow on question from this SO (Extract 4 bits of Bluetooth HEX Data) which an answer has been accepted. I wanna understand more why the difference between what I was using; example below; (which works) when applied to the SO (Extract 4 bits of Bluetooth HEX Data) does not.

To decode Cycling Power Data, the first 2 bits are the flags and it's used to determine what capabilities the power meter provides.

guard let characteristicData = characteristic.value else { return -1 }
var byteArray = [UInt8](characteristicData)
// This is the output from the Sensor (In Decimal and Hex)
// DEC [35,  0, 25,  0, 96, 44, 0, 33, 229] Hex:{length = 9, bytes = 0x23001900602c0021e5} FirstByte:100011    

/// First 2 Bits is Flags
let flags = byteArray[1]<<8 + byteArray[0]

This results in the flags bit being concatenate from the first 2 bits. After which I used the flags bit and masked it to get the relevant bit position.

eg: to get power balance, I do (flags & 0x01 > 0) enter image description here

This method works and I'm a happy camper.

However, Why is it that when I used this same method on SO Extract 4 bits of Bluetooth HEX Data it does not work? This is decoding Bluetooth FTMS Data (different from above)

guard let characteristicData = characteristic.value else { return -1 }
let byteArray = [UInt8](characteristicData)
let nsdataStr = NSData.init(data: (characteristic.value)!)

print("pwrFTMS 2ACC Feature  Array:[\(byteArray.count)]\(byteArray) Hex:\(nsdataStr)")

PwrFTMS 2ACC Feature Array:[8][2, 64, 0, 0, 8, 32, 0, 0] Hex:{length = 8, bytes = 0x0240000008200000}

Based on the specs, the returned data has 2 characteristics, each of them 4 octet long.

enter image description here

doing

byteArray[3]<<24 + byteArray[2]<<16 + byteArray[1]<<8 + byteArray[0]

to join the first 4bytes results in an wrong output to start the decoding.

edit: Added clarification

app4g
  • 670
  • 4
  • 24
  • I don't know much about the bluetooth code in question, but the table you show says that flags is a struct that is 2 *octets* in length. An *octet* is equivalent to a *byte* on modern computers, so the flags is 16 bits. The comment says that is 2 bits, but it's 2 bytes according to the docs. – Chip Jarred Jul 06 '21 at 07:06
  • 1
    To clarify for any reading this who might not know, a *byte* is not defined to be 8 bits. It is defined to be the smallest addressable unit of memory. On some early computers it was 7 or even 6 bits. An *octet*, on the other hand, is defined to be 8-bits. On modern computers a byte is an octet. – Chip Jarred Jul 06 '21 at 07:09
  • OK, so to address the actual question, according to the table shown, byte 4 (the 5th byte, representing Pedal Power Balance) is only present if bit 0 of `flags` is 1. Flags for the first characteristic is either 0x0240 or 0x4002 depending on endianness. Either way bit 0 is 0, so the 5th byte isn't present. Bytes 4 and 5 are therefore `flags` for the second characteristic. They are 0x0820 or 0x2008, again either way bit 0 is 0, so no Pedal Power Balance is present. What is the expected result? – Chip Jarred Jul 06 '21 at 07:32
  • @ChipJarred yes. No power balance is present and **is** expected output. The question is actually more towards **why** this method does not work for the other SO I listed. I admit I'm not well versed in the octet vs byte vs bit differences and that may be the reason why doing the bit shifting method does not work for the other SO. Thanks – app4g Jul 06 '21 at 08:43
  • My first comment was just to let you know that the code comment stating the flags is the first 2 *bits* was in error. The code itself seems to use the first two bytes. Explaining byte vs octet was just general knowledge stuff in case the meaning of octet was throwing you off. All modern computers are designed so that a byte coincides with an octet. I'll address the bit shifting in another comment so I don't run out of room. – Chip Jarred Jul 06 '21 at 09:24
  • So the bit-shifting you do composes the first 4 bytes into a 32-bit word (you don't show what you're assigning it to, but I assume it's at least 32-bits). However, my reading of the table you provide its that each "characteristic" begins with 2 bytes of flags. So the flags for the first characteristic consists of bytes at offsets 0 and 1. If the bit 0 of those flags is 0, then the flags of the second characteristic are in bytes at offsets 4 and 5, but if the bit 0 of the first characteristic is 1, then the flags for the second are at byte offsets 5 and 6. – Chip Jarred Jul 06 '21 at 09:28
  • @ChipJarred Ah.. the code comment. I missed that. YOu're right, it's supposed to be the 1st 2 bytes in the byteArray. As for the other comment, My apologies for not putting the specs for the 32-bit word into this SO (it was linked in the other SO). Is this the reason why I can't use the same method for both (this SO question and the other linked SO). 2 byte (16bit) vs 4 byte(32 bit)? – app4g Jul 06 '21 at 10:30
  • If you're interpreting the first 4 bytes as flags for two characteristics, then yes, that is a problem. All of the bytes from the first characteristic are together then followed by the bytes of the second characteristic. That means flags for the first are in `byteArray[0...1]`, but the location of flags for the 2nd are *not* in `byteArray[2...3]`. I posted an answer that might clarify. – Chip Jarred Jul 06 '21 at 10:48

1 Answers1

1

There is a problem with this code that you say works... but it seems to work "accidentally":

let flags = byteArray[1]<<8 + byteArray[0]

This results in a UInt8, but the flags field in the first table is 16 bits. Note that byteArray[1] << 8 always evaluates to 0, because you are shifting all of the bits of the byte out of the byte. It appeared to work because the only bit you were interested in was in byteArray[0].

So you need it convert it to 16-bit (or larger) first and then shift it:

let flags = (UInt16(byteArray[1]) << 8) + UInt16(byteArray[0])

Now flags is UInt16

Similarly when you do 4 bytes, you need them to be 32-bit values, before you shift. So

let flags = UInt32(byteArray[3]) << 24 
    + UInt32(byteArray[2]) << 16
    + UInt32(byteArray[1]) << 8
    + UInt32(byteArray[0])

but since that's just reading a 32-bit value from a sequence of bytes that are in little endian byte order, and all current Apple devices (and the vast majority of all other modern computers) are little endian machines, here is an easier way:

let flags = byteArray.withUnsafeBytes {
    $0.bindMemory(to: UInt32.self)[0]
}

In summary, in both cases, you had been only preserving byte 0 in your shift-add, because the other shifts all evaluated to 0 due to shifting the bits completely out of the byte. It just so happened that in the first case byte[0] contained the information you needed. In general, it's necessary to first promote the value to the size you need for the result, and then shift it.

Chip Jarred
  • 2,600
  • 7
  • 12
  • Firstly I apologise, but I think I have led you astray with my poor question. There are 2 parts to this question. In the first part, I am decoding Bluetooth Cycling Power data and the first part (the one w/ the pedal power balance spec pic). That works w/o issues. The 2nd part is decoding Bluetooth FTMS data (that's the 2nd pic w/ the 32bit word). I tried to use the same method (the bit shifting) to combine the 4 bytes into one 32bit word but the resultant output is incorrect. The answer from SO https://stackoverflow.com/q/68258711/14414215 works. Hence my question is basically - why – app4g Jul 06 '21 at 10:45
  • Ah ok... the second pic either wasn't there or wasn't displaying earlier, but it's there now. I will read and re-think – Chip Jarred Jul 06 '21 at 10:53
  • Tell me if this is correct. I'm getting the impression that there are different kinds of "characteristics" structures, and that the first table describes data that is common to them all, and that the second table describes the data that is specific to a particular characteristic structure. – Chip Jarred Jul 06 '21 at 10:59
  • [this blog](https://jjmtaylor.com/post/fitness-machine-service-ftms/) makes it look as though the `flags` field (which that blog seems to call `properties`) actually specifies a number of bits to indicate whether certain characteristics are or are not present in the bytes that follow. If that's right, it means you have to parse the bits of `flags` to compute the byte offset for the characteristic you're interested in. – Chip Jarred Jul 06 '21 at 11:12
  • I downloaded the actual spec from [bluetooth.com](https://www.bluetooth.com/specifications/specs/). – Chip Jarred Jul 06 '21 at 11:37
  • Yes. you're absolutely right and the blog you linked is also one of the sources of info I'm looking at. The characteristics structures are indeed different but the concept is supposedly the same, they use these flags to determine what services are being offered. (key difference between these 2 specs is the 2 byte vs the 4 byte) and I'm confused why I can't use the same method of bit shifting. – app4g Jul 06 '21 at 11:50
  • It seems the Fitness Machine Feature characteristic contains two 4-byte fields, which is why you get 8 bytes, and it appears correct that you are reading it into a 32-bit value, although according to the section that follows your image in the spec, upper-16 bits are reserved for future use. So my question what are you expecting the value of that 32-bit field to be? Put another way, how do you know you're getting incorrect results? – Chip Jarred Jul 06 '21 at 11:53
  • 1
    BTW - I'm going to change my answer, because this information makes what I wrote irrelevant, but I do see another potential problem. – Chip Jarred Jul 06 '21 at 11:56
  • let me answer this in 2 parts. First for the CyclePower bit shifting "accidentally". I think I now understand more of the specs w/ the number of bits in spec. The spec says First 2 bytes (8bit + 8 bit = total 16bits) so in order to get a full 16bits from bit shifting, I need to convert it to 16Bits first. And for the Fitness Machine Feature Characteristics, which is 4 Bytes (8bit + 8bit + 8bit + 8bit = 32bit), I need to do the same. Now I really understand It more. **thanks v much for the guidance** – app4g Jul 06 '21 at 12:41
  • You're welcome. Please comment after you try it. I'd like to know if that was the problem. – Chip Jarred Jul 06 '21 at 12:43
  • 1
    For the 2nd part. The 1st 32bit are for Section ```4.3.1.1 Fitness Machine Features Field``` in ```Table 4.3``` specifically. The other 32bits are for section ```4.3.1.2 Target Setting Features Field``` in ```Table 4.4```. The FRU is for bit17-31. Now w/ your guidance on the 32bit bit shifting, I think it will work now and the output would be correct. – app4g Jul 06 '21 at 12:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234570/discussion-between-myjunk-and-chip-jarred). – app4g Jul 06 '21 at 13:17