5

I am trying to read values from a Bluetooth 4.0 LE scale on iOS. How do I convert the Bluetooth Characteristics measurements received as NSData to dedicated Swift objects?

As a specification I know that …

Bit 0 to Bit 12 → weight (0 to 5000 g)

Bit 15 → positive/negative weight value

func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {

    let value:NSData = characteristic.value

    let weight:Double = … ?
    let negative:Bool = … ?

one more piece of information – looking at

value.length

it looks like I am always getting 1 or 2 bytes (?) of data from the device. However I am still not sure how to extract the data/values I was looking for. I'd appreciate any advice.

Here's what works a little bit so far …

    var bin16:UInt16 = 0
    characteristic.value.getBytes(&bin16, range: NSRange(location: 0,length: 1))

    println("Bytes \(characteristic.value.bytes) Length \(characteristic.value.length)")
    println("Value \(bin16)")

– Doing this I manage to get some weight reading. It seems to work unless the value is bigger than 255 or negative. Here are some examples:

75 grammes

Bytes 0x1655e458 Length 1 Value 75

367 grammes

Bytes 0x1765cbc8 Length 2 Value 161

-6 grammes

Bytes 0x17670f18 Length 2 Value 32

Also this gets transmitted more often in between – it doesn't stand for 160 gramme in this case. Maybe some sort of error code?!

Bytes 0x146528b8 Length 2 Value 160

Bernd
  • 11,133
  • 11
  • 65
  • 98
  • I just added some more examples. It looks like I only get more than 1 byte *when needed*, meaning when the weight is above 255g or negative. However in those 2 byte cases my rudimentary value extraction stops working. – Bernd Aug 15 '14 at 13:30
  • Oh, I thought characteristic.value.length gives me used amount of bytes, isn't it? Output in my examples 1 or 2. – Bernd Aug 15 '14 at 13:53
  • Thanks, for the clarification. To get the hex value I just use "characteristic.value.bytes" as written in my code example – Bernd Aug 15 '14 at 14:30
  • any sample project is available so i can connect my weight scale ?? – Varsha Vijayvargiya Jan 06 '20 at 08:03

2 Answers2

6

Looks like there are two questions.

  1. How to extract the binary data from NSData into a swift data type
  2. How to extract the useful data from the binary string

Extracting Swift data types from NSData

Looking at the question above your data is in a 16-bit binary string. Therefore, our first task is to extract the 16-bit binary string into a datatype. I think UInt16 is best for this.

func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {

    var bin16:UInt16 = 0
    var wrapin: NSNumber = NSNumber(unsignedShort: 0)
    characteristic.value.getBytes(&wrapin, range: NSRange(location: 0,length: 2))
    bin16 = wrapin.unsignedShortValue

Extracting data from the binary string

At this point bin16 has a 16-bit binary string. Based on your description the weight data is stored in bites 0-12 and the sign bit is the 16th bit. Below is an example of how you would extract this data using bitwise operators & and >>. Check out the swift language guid for more on binary operators in Swift.

// The actual data
let value  : UInt16 = 0b0010_1010_0000_0000             // 2560g & positive sign
let weight : uint16 = value & 0b0001_1111_1111_1111     // Extract weight
let sign : UInt16 = value >> 15                         // Posative or negative

Please note that I made the following assumptions:

  1. Your binary string is LSB
  2. Your binary string is only 16-bits long. If this is not the case then you should maybe use an & operator instead of the >> to extract the sign.

Update - Include playground content

// Important Note: Reading & Writing to NSData using the native UInt16 does
// not work correctly. Something happens and it mangles the data. To get 
// around this in xcode beta 5 you must wrap your data in an NSNumber.

import UIKit

// Build an NSData type
// Bit 0 through 12 --> Weight in g
// Bit 13 & 14      --> not read or used in example (discarded)
// Bit 15           --> Sign-bit
// The value below:
//      Weight: (bit 0-12) : 2560G
//        Sign: Positive
let rawvalue : UInt16 = 0b0010_1010_0000_0000


// Build NSData payload
var wrapout : NSNumber = NSNumber(unsignedShort: rawvalue)
var payload : NSData = NSData(bytes: &wrapout, length: 2)


// Extracting data
var finalWeight = 0

if payload.length >= 2 {
    var wrapin   : NSNumber = NSNumber(unsignedShort: 0)
    var bstring  : UInt16 = 0
    payload.getBytes(&wrapin, range: NSRange(location: 0, length: 2))
    bstring = wrapin.unsignedShortValue

    let weight : UInt16 = bstring & 0b0001_1111_1111_1111
    let valsign: UInt16 = (bstring & 0b1000_0000_0000_0000) >> 15

    if valsign == 0 {
        finalWeight = Int(weight)
    } else {
        finalWeight = -1 * Int(weight)
    }
}
println("\(finalWeight)")
Freddy
  • 2,249
  • 1
  • 22
  • 31
  • That looks good to me. Thanks a lot. However when I run this I get an exception after "characteristic.value.getBytes(&bin16, range: NSRange(location: 0,length: 16))" → "… Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[_NSInlineData getBytes:range:]: range {0, 16} exceeds data length 1'" – Bernd Aug 08 '14 at 22:26
  • I changed it "characteristic.value.getBytes(&bin16, range: NSRange(location: 0,length: characteristic.value.length))" … but still something seems to be wrong. For weight and sign I get "2560" and "0" for every reading. – Bernd Aug 08 '14 at 22:35
  • Did you change `value` to `bin16` in the weight and sign calculations? i.e. `let weight : uint16 = bin16 & 0b0001_1111_1111_1111` and `let sign : UInt16 = bin16 >> 15`? – Mike S Aug 09 '14 at 01:09
  • @MikeS Sorry for the confusion. I was trying to tie together the example from two test playgrounds I had open. I used different names in both. I have included the full playground I used. – Freddy Aug 09 '14 at 01:17
  • @BerndPlontsch I apologize. My first response was confusing in how I tied the two examples together. To help clarify I have updated my response with the actual playground file used in compiling my answer. Could you post your most recent code? – Freddy Aug 09 '14 at 01:20
  • @BerndPlontsch Regarding your comment about Range{0,16} crashing your code. That was an error on my end. I was passing bits instead of bytes. I have updated my example. – Freddy Aug 09 '14 at 01:39
  • @Freddy Wow, that's great. I think I am getting really close now. Also your example helps me to develop a better general understanding for this operation. I appreciate it a lot. Unfortunately, when connecting to the device I still get an exception. I think it's because I only get 1 byte, not 2 bytes of data. I thought I can accommodate for that by updating "payload.getBytes(&wrapin, range: NSRange(location: 0, length: 1))" but it doesn't seem to be that easy. Any other idea about how to work with a byte length of **1**? – Bernd Aug 09 '14 at 15:54
  • @BerndPlontsch Strange that you're only getting one byte of data at a time. Can you share the link of the API that is sending you this data? Maybe you need to accumulate your bytes until you have two. You can append bytes to a NSData in swift (must be var, not let). Please let me know if you have any documentation you can share. – Freddy Aug 11 '14 at 01:32
  • Unfortunately I don't have a full spec/API, just the specification of bits I mentioned. And when I turn this first bit into an int as mentioned I am in deed able to read a value until 255 or so. Just nothing further. I need to dig in more into understanding the bit reading. Logging the actual 1s and 0s of transmitted packet bits might help. Still looking into how to output/print this in Swift (any idea?). – Bernd Aug 15 '14 at 13:12
2

More information is needed about the received data. Assuming that the two bytes are b0 and b1 and b0 the lead byte (first byte received). 1. Which byte has the sign bit. 2. Is the sign bit the left most bit (0x80) of the most right bit (0x01).

Here is a potential solution based on assumed byte, bit numbering order and endian-ness:

// let value:NSData = characteristic.value
// println("value: \(value)")

let value16 = UnsafePointer<UInt16>(value.bytes)[0]

//value16 = value16.bigEndian // if value is bigendian uncomment, change let to var

println("value16: 0x\(String(value16, radix:16))")

let negative:Bool = (value16 & 0x0001) == 0 // Possibly the sign is a different bit
let weightInt = (value16 >> 4) & 0x0fff     // Possibly a different shift is needed
let weight:Double = Double(weightInt)
zaph
  • 111,848
  • 21
  • 189
  • 228
  • Thanks a lot for this. I tried it but I don't seem to get weight values. Examples: 83g → (value16: 0x53 negative: false weight int: 5 weight: 5.0), 510g → (value16: 0xfe negative: true weight int: 15 weight: 15.0) – Bernd Aug 15 '14 at 13:46