Extract and Convert NSData from BLE to Float in Swift - ios

I'm developing an IOS App that handles a BlueTooth SensorTag.
That SensorTag is based on the TI BLE SensorTag, but we had some of the Sensors removed.
In the sourcecode of the original IOS App from TI the XYZ-Values are calculated like follows
with KXTJ9_RANGE defined as 1.0 in my implementation and KXTJ9 is the Accelerometer built on the SensorTag
+(float) calcXValue:(NSData *)data {
char scratchVal[data.length];
[data getBytes:&scratchVal length:3];
return ((scratchVal[0] * 1.0) / (64 / KXTJ9_RANGE));
}
The data comes as hexadecimal like "fe850d" and by the method will be cut in 3 parts.
Now i'm trying to convert this method to swift, but i get the wrong numbers back
e.g. fe should return something around 0.02 what the objective C Code does
My Swift Code so far
class Sensor: NSObject {
let Range: Float = 1.0
var data: NSData
var bytes: [Byte] = [0x00, 0x00, 0x00]
init(data: NSData) {
self.data = data
data.getBytes(&bytes, length: data.length)
}
func calcXValue()->Float {
return ((Float(bytes[0]) * 1.0) / (64.0 / Range))
}
...
}
I believe problem must lie around my Float(bytes[0]) because that makes 254 out of fe whereas scratchVal[0] in ObjectiveC is around 64
But my main problem is, i was all new with IOS programming when i had to begin with this project, so i chose to use Swift to learn and code the app with it.
Right now i use the original Objective C Code from TI to use with our SensorTag, but i would prefer using Swift for every part in the App.

On all current iOS and OS X platforms, char is a signed quantity,
so that the input fe is treated as a negative number.
Byte on the other hand is an alias for UInt8 which is unsigned.
Use [Int8] array instead to get the same behaviour as in your Objective-C code.

it depends on the BLE device Endianness, in relation to your device Endianness.
wiki on Endianness
To keep it simple you need to check which does two method
NSData *data4 = [completeData subdataWithRange:NSMakeRange(0, 4)];
int value = CFSwapInt32BigToHost(*(int*)([data4 bytes]));
or
NSData *data4 = [completeData subdataWithRange:NSMakeRange(0, 4)];
int value = CFSwapInt32LittleToHost(*(int*)([data4 bytes]));
And check which one make more sense when you parse the data.

Related

iOS Bluetooth Performing Write Long

I'm working on a project with an iPhone connecting to an ESP32 using BLE. I'm trying to write a 528 byte long blob to a characteristic. Since the blob is longer than the max 512 allowed per write I'm trying to do a Write Long.
I'ved tried a couple things
1 - If I just try to write the blob I see the first chunk go through with Prepare Write set but there are no subsequent writes.
Why is it stopping after the first chunk?
2 - If I try to chuck it manually based on the size returned from maximumWriteValueLengthForType I see all the data is sent correctly but Prepare Write is not set so they aren't handled correctly.
How do I specify Prepare Write / Execute Write requests?
Here's a code snippet covering the implementation #2
NSData *blob = [request value];
NSUInteger localLength = 0;
NSUInteger totalLength = [blob length];
NSUInteger chunkSize = [peripheral maximumWriteValueLengthForType:type];
uint8_t localBytes[chunkSize];
NSData *localData;
do
{
if(totalLength > chunkSize) {
NSLog(#"BIGGER THAN CHUNK!!!!!!!!!!!!!!!!!!!!!");
NSLog(#"%tu", chunkSize);
for ( int i = 0; i < chunkSize; i++) {
localBytes[i] = ((uint8_t *)blob.bytes)[localLength + i];
}
localData = [NSMutableData dataWithBytes:localBytes length:chunkSize];
totalLength -= chunkSize;
localLength += chunkSize;
}
else {
NSLog(#"Smaller than chunk");
uint8_t lastBytes[totalLength];
for (int i = 0 ; i < totalLength; i++) {
lastBytes[i] = ((uint8_t *)blob.bytes)[localLength + i];
}
localData = [NSMutableData dataWithBytes:lastBytes length:totalLength];
totalLength = 0;
}
// Write to characteristic
[peripheral writeValue: localData forCharacteristic:characteristic type:type];
} while( totalLength > 0);
Long writes are affected by the same limit of 512 bytes maximum for the characteristic value. Long writes are only useful when MTU is too short to write the full value in one packet. Maybe you're trying to write out of this allowed range or something.
Newer iOS versions communicating with BLE 5 devices use a large enough MTU to fit a characteristic value of 512 in one packet (if the remote device also supports such a big MTU).
If you want to write bigger values than 512 bytes, you will need to split it up into multiple writes, so that the second write "overwrites" the first value sent, rather than appending to it. You can also use L2CAP CoC instead which eliminates this arbitrary 512 byte limit.
You have the right general approach but you can't just send the chunks sequentially. There is a limited buffer for sending Bluetooth data and your loop will write data into that buffer more quickly than the Bluetooth hardware can send it.
The exact approach you need to take depends on whether your characteristic supports write with response or write without response.
If your characteristic uses write with response, you should send a chunk and then wait until you get a call to the didWriteValueFor delegate method. You can then write the next chunk. The advantage of this approach is essentially guaranteed delivery of the data. The disadvantage is it is relatively slow.
If your characteristic uses write without response then you call write repeatedly until you get a call to didWriteValueFor with an error. At this point you have to wait until you get a call to peripheralIsReady. At this point you can start writing again, beginning with the last failed write.
With this approach there is the potential for lost data, but it is faster.
If you have to move large amounts of data, an L2Cap stream might be better, but you need to handle data framing.

How to convert AVAudioPCMBuffer to NSData in Objective-C?

As the title suggests.
There is an old solution in Swift here. But I have hard time converting to Objective-C. Seems there is no Objective-C equivalent of UnsafeBufferPointer
Objective-C has unsafe pointers built right into the language, so the conversion simply becomes:
- (NSData *)bufferToNSData:(AVAudioPCMBuffer *)buffer {
return [[NSData alloc] initWithBytes:buffer.floatChannelData[0] length:buffer.frameLength * 4];
}
N.B. this assumes mono 32 bit float data in the buffer. More work needs to be done to serialize all supported AVAudioPCMBuffer formats to NSData.

Converting AVAudioPCMBuffer to NSData

I'm currently trying to convert the audio samples from AVAudioPCMBuffer to NSData - I had taken a look at the accepted answer on this SO Post and this code from GitHub but it appears some of the AVFAudio API's have changed...below is the extension I have for AVAudioPCMBuffer:
private extension AVAudioPCMBuffer {
func toNSData() -> NSData {
let channels = UnsafeBufferPointer(start: int16ChannelData, count: 1)
let ch0Data = NSData(bytes: channels[0], length:Int(frameCapacity * format.streamDescription.inTotalBitsPerChannel))
return ch0Data
}
}
I'm seeing an error of Value of type 'UnsafePointer<AudioStreamBasicDescription>' has no member 'inTotalBitsPerChannel'. So far, I've not been able to find out any other way to find out the inTotalBitsPerChannel value...any help appreciated!
I don't see any method named inTotalBitsPerChannel in either of the code samples you linked to; instead, they both seem to use mBytesPerFrame. You will also need .pointee to dereference the pointer. Finally, in modern Swift, you should generally prefer to use Data over NSData. So, basically, I think your extension should work if you rewrite the last line to:
let ch0Data = Data(bytes: channels[0], count: Int(frameCapacity * format.streamDescription.pointee.mBytesPerFrame))

Swift: best way to send large arrays of numbers ([Double]) over HTTP

I thought I'd ask after hours of inconclusive research and tests:
Introduction
I'm trying to send very large arrays of Doubles from an app to a server, naturally, I want to compress this as much as possible.
Specifically, these array contain CMDeviceMotion components (acceleration, x, y, z, gyroscope, etc...), but this question should apply to any large array of numbers (over 100K or a million values)
What I've tried and found by researching options
Say I have a large array of Double (There are many others) :
var CMX = CM.map({$0.userAcceleration.x})
here, CMX is of type [Double] and CM is [CMDeviceMotion]
I've tried making POST requests to my server by sending CMX in different ways, then calculating the total size after I receive it on the server :
First, as a single comma separated string :
{"AX":"-0.0441827848553658,-0.103976868093014,-0.117475733160973,-0.206566318869591,-0.266509801149368,-0.282151937484741,-0.260240525007248,-0.266505032777786,-0.315020948648453,-0.305839896202087,0.0255246963351965,0.0783950537443161,0.0749507397413254,0.0760494321584702,-0.0101579604670405,0.106710642576218,0.131824940443039,0.0630970001220703,0.21177926659584,0.27022996544838,0.222621202468872,0.234281644225121,0.288497060537338,0.176655143499374,0.193904414772987,0.169417425990105,0.150193274021149,0.00871349219232798,-0.0270088445395231,-0.0 ....
Size 153 Kb.
It makes sense that this is larger than sending as binary data, since a single number here is 64 bits (8 bytes), and becomes 17 bytes long (one byte per character) +1 = 18 (added a character for the comma).
With this reasoning, sending the array as binary data should be smaller.
Base 64 encoding
Here, I convert the array to a Data object using NSKeyedArchiver and base 64 encode the data before sending it :
["AX":NSKeyedArchiver.archivedData(withRootObject:CM.map({$0.userAcceleration.x})).base64EncodedString()]
This made the file size 206 Kb
Sending the data as a JSON array
By just sending :
["AX": CM.map({$0.userAcceleration.x})]
It turned out that this array of numbers was practically converted to a comma separated string, the size ended up being the same as in trial 1 (160Kb)
Sending as Data without base 64 encoding
Doing this:
["AX":NSKeyedArchiver.archivedData(withRootObject:CM.map({$0.userAcceleration.x}))
made the application crash at runtime, so I can't send a Data object as a value in a JSON
Question
How can I send these array in a more condensed way in a JSON object ?
Note that I already have downsampling in mind, and using 32 bit floats to reduce the size.
Simple way would be to do this:
let data: Data = CMX.withUnsafeBufferPointer { pointer in
return Data(buffer: pointer)
}
And you have binary buffer with all your Doubles/Floats combined.
But because HTTP is text-based protocol you will have to convert this data to base64 string:
let base64String = data.base64EncodedString()
And this base64String should be passed for AX parameter of your POST(?) HTTP request.
EDIT:
To convert it back you may use code like this:
extension Array {
init?(data: Data) {
// This check should be more complex, but here we just check if total byte count divides to one element size in bytes
guard data.count % MemoryLayout<Element>.size == 0 else { return nil }
let elementCount = data.count / MemoryLayout<Element>.size
let buffer = UnsafeMutableBufferPointer<Element>.allocate(capacity: elementCount)
data.copyBytes(to: buffer)
self = buffer.map({$0})
buffer.deallocate()
}
// Wrapped here code above
var data: Data {
return self.withUnsafeBufferPointer { pointer in
return Data(buffer: pointer)
}
}
}
let converted: [Double]? = Array(data: CMX.data) // converted now should be equal to CMX

-[NSString dataUsingEncoding:] gives garbage at end of string in iOS 9, not iOS 8

The following code runs fine on iOS 8 but when run on iOS 9.0.2 I get some odd results:
NSString * input = #"Hi there";
NSData * data = [input dataUsingEncoding:NSASCIIStringEncoding];
Byte *byteData = (Byte*)malloc(data.length);
memcpy(byteData, [data bytes], data.length);
NSString * result = [NSString stringWithCString:(const char*)byteData encoding:NSASCIIStringEncoding];
NSLog(#"Result: %#", result);
iOS 8.4 (iPhone 6 Plus) byteData is Hi there
iOS 9.0.2 (iPhone 6S) byteData is Hi there\xb6<M\x13
On iOS 9 I end up with a load of garbage at the end of the string.
This feels like a 32 bit vs 64 bit issue as it looks like on iOS 9 the byteData length is twice as long?
Apple have their table of 32 to 64 bit changes here:
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaTouch64BitGuide/Major64-BitChanges/Major64-BitChanges.html
data.length is an unsigned long long. Could this be returning different lengths when malloc is called? The code above returns 8 for data.length when run on each version of iOS.
This just feels quite odd and I've run out of angles to attack it from. Hopefully someone out there might be able to shed some light on this one.
Thanks!
Update
I can fix it using
NSString * result = [[NSString alloc] initWithBytes:byteData length:data.length encoding:NSASCIIStringEncoding];
but I'd still like to know why I get a different result on the two iOS versions with
NSString * result = [NSString stringWithCString:(const char*)byteData encoding:NSASCIIStringEncoding];
A “C string” ends with a NUL byte. Since you created data using dataUsingEncoding:, data does not contain a C string.
Since stringWithCString:encoding: is reading outside of valid memory (looking for the NUL terminator), the behavior is undefined and thus allowed to changed at any time.
Use cStringUsingEncoding: to create data and you'll get the NUL terminator you need.

Resources