Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract and convert NSData value from Bluetooth reading in Swift

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

like image 914
Bernd Avatar asked Aug 06 '14 13:08

Bernd


2 Answers

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)")
like image 145
Freddy Avatar answered Nov 15 '22 04:11

Freddy


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)
like image 39
zaph Avatar answered Nov 15 '22 03:11

zaph