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
Looks like there are two questions.
How to extract the binary data from NSData into a swift data type
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:
Your binary string is LSB
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)")
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)
Related
Hi I am trying to use a UITextbox and restrict the number of characters input by the user to 10.
I have looked at using the below link,
Max length UITextField
My Questions,
1.Its not working as characters are depreciated in Swift 4 so the string.characters.count is throwing an error so what would be a workaround in Swift 4?
2.After the user enters his x number of characters, I want to make the reminders that is (limitlength - x) into empty spaces (ascii for space = 32 in decimal) so that reminder of the byte array is equal to dec 32.
I have tried doing this,
if let receivedData = rxCharacteristic?.value
let myByteArray = Array(receivedData) {
let b0 = myByteArray[0]
let b1 = myByteArray[1]
let b2 = myByteArray[2]
let b3 = myByteArray[3]
//Now reading data from textbox input
var userdata = textbox.text
let userdataarray: [UInt8] = Array(userdata!.utf8)
//I tried putting values into myByteArray as below
userdataarray[0] = myByteArray[0]
userdataarray[1] = myByteArray[1]
userdataarray[2] = myByteArray[2]
//The last value in myByteArray will remain unchanged so I'm not overwriting it
So from the question when I try to input textbox data less than its length its throwing an index out of range exception. But I went a little extreme to try the below code.
if(userdataarray[0] != 0 && userdataarray[0] != nil)
{
userdataarray[0] = myByteArray[0]
}
else
{
userdataarray[0] = 32 //Which is space in ascii
userdataarray[0] = myByteArray[0]
}
I don't think it worked but wanted to check on how its properly done?
If I understand your question correctly, then your trials are very much overenginering. In Swift you can just add characters to a String (as long as it is declared as var that is). This just boils down to
let orig = "Hello World"
var copy = orig
while copy.count < 15 {
copy.append(" ")
}
let dta = copy.data(using:.isoLatin1)!
let arr = Array(dta)
Since Swift is using some Unicode-encoding internally it is probably crucial to convert your String to data using a specific encoding if you plan to "directly" transfer it to some device that is limited to a certain character set. Still a lot less code than what you provided.
var dataFile: NSData = NSMutableData.init(data: wav.subdataWithRange(NSRange.init(location: currentByte, length: wavDataSize)))
How to me convert this code to using Data with Swift 3? Or how to parseNSRange to Range
Separating Data into smaller Data instances
Assumptions
This answer is the Swift 3 & 4 equivalent of the code in the question. It will produce the same result, dataFile, given the same input values: wav, currentByte and wavDataSize assuming none of the surrounding code changes.
I did not make assumptions about what the variables: wav, dataFile, currentByte or wavDataSize mean. To avoid the variable names implying things not stated in the question, I will use the following names instead: sourceData, subdata, rangeStartByte and subdataLength. I assume the code (not shown in the question) surrounding this would assure that rangeStartByte and subdataLength were in a valid range to avoid an error.
Converting NSRange to Range<Int>
The Swift 2 implementation from the question uses an NSRange defined by a start point and a length like this:
NSRange.init(location: rangeStartByte, length: subdataLength)
The Swift 3 & 4 implementation I propose creates an equivalent Range<Int> defined by a start point and end point like this:
rangeStartByte ..< (rangeStartByte + subdataLength)
I converted an app from Swift 2.2 to 3 which used similar code to upload a photo in smaller chunks. During conversion we missed this nuance and used the Swift 2 implementation's length in place of the Swift 3 & 4 implementation's end point. This caused a defect that was tricky to resolve. The first iteration succeeded but subsequent iterations failed.
Another answer implements the issue I just described as the solution. It uses subdataLength from the length of the Swift 2 range as the end point of the Swift 3 & 4 range. This will produce the same result in the special case where currentByte is 0 and subdataLength is <= the length of the NSData instance (which is why the first iteration succeeded in the issue I described). That assumption was not explicitly stated in the question and yields a less flexible solution for others.
Swift 3 & 4 equivalent
var subdata = sourceData.subdata(in: rangeStartByte ..< (rangeStartByte + subdataLength))
Swift 2.2
(code from question with updated variable names)
var subdata: NSData = NSMutableData.init(data: sourceData.subdataWithRange(NSRange.init(location: rangeStartByte, length: subdataLength)))
Runnable sample code
I've included sample code you can run in a playground demonstrating how this line of code could be used to separate a Data instance into smaller Data instances. The source Data instance is created from a string "ABCDEFGHIJKL". This instance is separated into smaller Data instances of length 5.
Swift 3 & 4 with context
import UIKit
var sourceString = "ABCDEFGHIJKL"
let sourceData = sourceString.data(using: String.Encoding.utf8)! // sourceData is equivalent to "wav" from question
var rangeStartByte = 0 // rangeStartByte is equivalent to "currentByte" from question
let maxSubdataLength = 5
let dataLength = sourceString.lengthOfBytes(using: String.Encoding.utf8)
precondition(maxSubdataLength <= dataLength, "maxSubdataLength must be <= to dataLength")
while rangeStartByte < dataLength {
// subdataLength is equivalent to "wavDataSize" from question
let subdataLength = min(maxSubdataLength, dataLength - rangeStartByte)
// subdata is equivalent to "dataFile" from question
let subdata = Data(sourceData.subdata(in: rangeStartByte ..< (rangeStartByte + subdataLength)))
let subdataString = String(data: subdata, encoding: String.Encoding.utf8) ?? ""
print("'\(subdataString)'")
rangeStartByte += subdataLength
}
The result is:
'ABCDE'
'FGHIJ'
'KL'
Swift 2.2 with context
import UIKit
var sourceString = "ABCDEFGHIJKL"
let sourceData = sourceString.dataUsingEncoding(NSUTF8StringEncoding)! // sourceData is equivalent to "wav" from question
var rangeStartByte = 0 // rangeStartByte is equivalent to "currentByte" from question
let maxSubdataLength = 5
let dataLength = sourceString.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
precondition(maxSubdataLength <= dataLength, "maxSubdataLength must be <= to dataLength")
while rangeStartByte < dataLength {
// subdataLength is equivalent to "wavDataSize" from question
let subdataLength = min(maxSubdataLength, dataLength - rangeStartByte)
// subdata is equivalent to "dataFile" from question
let subdata: NSData = NSMutableData.init(data: sourceData.subdataWithRange(NSRange.init(location: rangeStartByte, length: subdataLength)))
let subdataString = String(data: subdata, encoding: NSUTF8StringEncoding) ?? ""
print("'\(subdataString)'")
rangeStartByte += subdataLength
}
The result is:
'ABCDE'
'FGHIJ'
'KL'
Swift 3 & 4 using NSRange
pedrouan's answer uses NSRange like this:
var subdata: Data = Data(sourceData.subdata(with: NSRange(location: rangeStartByte, length: subdataLength)))
I could not get this to compile initially so I disregarded it. Now I realize that it works if sourceData is declared or cast at NSData and not Data
If you want to run this approach within the "Swift 3 & 4 with context" sample code above, replace the corresponding code in that sample with this:
// subdata is equivalent to "dataFile" from question
let sourceNSData = sourceData as NSData
let subdata = sourceNSData.subdata(with: NSRange(location: rangeStartByte, length: subdataLength))
I'm trying not to use "NS" classes like NSRange where possible so I favored the solution using a Swift Range.
Some 'little' changes in Swift 3.0
var dataFile: Data = Data(wav.subdata(with: NSRange(location: currentByte, length: wavDataSize)))
In Swift 3 your code will be like this one:
var dataFile = sourceData.subdata(in: currentByte..<currentByte+wavDataSize)
i'm currently trying to get the data(HEX) from UITextfield, and i would want to store the data in UInt8 i'm currently doing this.
let incomingdata = UInt8(textfield.text!)
by doing this it returns nil. The purpose i'm doing this because after i gets the data from UITextField, i would send out the data in UInt8 format via bluetooth. Can someone suggest me how can i do that?Thank you
I update my question, in short i input 72AE in UITextField, i get the text in string format, but in the end i wan to convert it to UInt8 and it is in 0x72, 0xAE
In short, i'm converting HexString to UInt8
You can convert a hex value to an Int with this code.
let hex2int = String(format:"%2X", hex)
Quite easy.
I might be misunderstanding your question, but if you would like to covert string into array of Int, this is how you might go about it.
let stringFromTextField = "anything"
// Convert stringFromTextField to NSString and then then convert it to NSData
// in encoding of your choosing
if let data = NSString(string: stringFromTextField).dataUsingEncoding(NSUTF8StringEncoding) where data.length > 1 {
// Create buffer
var buffer: Int = 0
let bufferSize = 1
let adjustedDataLenght = data.length / bufferSize
var yourInt = [Int]()
// Loop over data and get bytes
for i in 0..<adjustedDataLenght {
data.getBytes(&buffer, range: NSRange(location: i, length: bufferSize))
yourInt.append(buffer)
}
// Here are your ints
print(yourInt)
}
Works in playground. Hope it helped.
I'm a swift/iOS newbie and I have a problem to solve.
I'm trying to get data from Texas Instrument SensorTag 2. To activate a sensor, following the instructions, I have to write a binary string in the configuration bank of my sensor.
I have this snippet of code:
if SensorTag.validConfigCharacteristic(thisCharacteristic) {
// Enable Sensor
let enableByte = SensorTag.getEnableByteFor(thisCharacteristic)
self.sensorTagPeripheral.writeValue(enableByte, forCharacteristic: thisCharacteristic, type: CBCharacteristicWriteType.WithResponse)
}
and I write the function to get the value to write. enableByte type is NSData.
class func getEnableByteFor(thisCharacteristic: CBCharacteristic) -> NSData {
print(thisCharacteristic.UUID)
var enableValue = 0
if thisCharacteristic.UUID == MovementConfigUUID {
enableValue = ...
} else { // any other activation
enableValue = 1
}
return NSData(bytes: &enableValue, length: sizeof(UInt8))
}
For every sensor I have to write a 1 if I want to enable the sensor and 0 if I want to disable it, but with the movement sensor I have to write according to this guide 16 bits (2 byte). For my config I have to write a binary value of 0000000001111111, 0x007F. How can I initialize a NSData object with value 0x007F?
Try this:
let bytes : [CChar] = [0x0, 0x7F]
let data = NSData(bytes: bytes, length: 2)
NSData(bytes:length:) creates an NSData object from a byte stream. In Objective-C, this byte stream is of type char *. The Swift equivalent is [CChar]. The question (and another answer) use an Int to represent this byte stream. This is wrong and dangerous.
var enableValue = 0 // enableValue is a 64-bit integer
NSData(bytes: &enableValue, length: sizeof(UInt8)) // this trims it to the first 8 bits
It works because x86 uses Little Endian encoding, which puts the least significant byte first. It will fail on PowerPC, which uses Big Endian. ARM uses switchable endianness so it may or may not fail there. When the situation call for exact bit layout, you should not rely on the architecture's endianness:
class func getEnableByteFor(thisCharacteristic: CBCharacteristic) -> NSData {
print(thisCharacteristic.UUID)
let enableValue : [CChar]
if thisCharacteristic.UUID == MovementConfigUUID {
enableValue = [0x0, 0x7F]
} else { // any other activation
enableValue = [0x1]
}
return NSData(bytes: enableValue, length: enableValue.count)
}
Much shorter solution taking in account byte order:
NSData(bytes: [UInt16(0x007F).bigEndian], length: 2)
Now there is nothing wrong with using [UInt16] as byte stream because UInt16 has bigEndian property that returns the big-endian representation of the integer changing byte order if necessary.
I'm on beta 3. Consider the following Objective-C line:
const uint8_t *reportData = [data bytes];
where data is a NSData object.
How would this line be re-written in Swift?
data.bytes is of type ConstUnsafePointer<()>, and while there's plenty of documentation on how to create a pointer type in Swift, there isn't much info on how to work with them.
edit:
To add some context, I'm trying to port Apple's HeartRateMonitor sample code to Swift. This code interacts with BLE heart rate monitors. This code I'm working on translates the data received by the Bluetooth system into an int for use in the UI. The data received from BT is expected to be an array of uints, element 0 is used to check for a flag and element 1 contains the value.
Here's the same Objective-C line in context:
const uint8_t *reportData = [data bytes];
uint16_t bpm = 0;
if ((reportData[0] & 0x01) == 0)
{
/* uint8 bpm */
bpm = reportData[1];
}
What you were looking for was how to convert NSData to an array of UInt8. Here's how.
import Foundation
let path = "/etc/csh.cshrc" // something existent
let data = NSData(contentsOfFile: path)
var aofb = [UInt8](count:data.length, repeatedValue:0)
data.getBytes(&aofb, length:data.length)
for c in aofb {
let s = UnicodeScalar(Int(c)).escape(asASCII:true)
println("\(c):\(s)")
}
Just built following code (Note code below works on Beta 3, ConstUnsafePointer<()> needs to be changed to COpaquePointer in order to work on Beta 2, please see edit history for more information)
var dataPath = NSBundle.mainBundle().pathForResource("TestData", ofType: "") // What I have in TestData is "GREETINGS WORLD"
var originalData = NSData(contentsOfFile: dataPath)
var dataLength = originalData.length
println("original data: \(originalData)") // Output original data
// Data to bytes
var reportBytes: ConstUnsafePointer<()> = originalData.bytes
var bytesToString = NSString(bytes: reportBytes, length: dataLength, encoding: NSUTF8StringEncoding)
println("string from bytes: \(bytesToString)")
// Bytes to data
var bytesToData = NSData(bytes: reportBytes, length: dataLength)
println("data from bytes: \(bytesToData)")
Console log
original data: <47524545 54494e47 5320574f 524c44>
string from bytes: GREETINGS WORLD
data from bytes: <47524545 54494e47 5320574f 524c44>
Also found this may help
ConstUnsafePointer<T>
/// This type stores a pointer to an object of type T. It provides no
/// automated memory management, and therefore the user must take care
/// to allocate and free memory appropriately.
Hope this shed light.
Looking at handling bluetooth heart rate monitors in Swift now I found the simplest way to get the NSData byte values to UInt8 format:
let bytes = UnsafePointer<UInt8>(data.bytes)
if bytes[0] & 0x01 == 0 {
NSLog("BPM \(bytes[1]")
}