Reputation: 11493
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
Upvotes: 5
Views: 8660
Reputation: 112857
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)
Upvotes: 2
Reputation: 2279
Looks like there are two questions.
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:
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)")
Upvotes: 6