Teddy13
Teddy13

Reputation: 3854

Convert NSData to a NSString returns random characters

I am working on a bluetooth iOS project and have managed to get some data from the bluetooth device. However, I am struggling to convert this data into something useful, such as an NSString. Whenever I try to NSLog the NSString that was converted from the NSData received, it is a bunch of gibberish. The output is:

ēဥ၆䄀

The bluetooth device is a heart monitor from a manufacturer in Asia and they have provided the protocol reference on how to make calls to the device. This one thing they mention in the protocol reference:

The PC send 16-byte packets to the device, then the device sent back the 16-byte packets. Except for some special commands, all others can use this communication mode.

Can anyone tell me what I am doing wrong? I have tried everything I know, including every single encoding in the apple docs as well as both initWithData and initWithBytes. Thanks!

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
         error:(NSError *)error {
if (error)
{
    NSLog(@"erorr in read is %@", error.description);
    return;
}
NSData *data= characteristic.value;
 NSString *myString = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF16StringEncoding];
NSLog(@"Value from device is %@", myString); //OUTPUT IS ēဥ၆䄀

}

Upvotes: 1

Views: 936

Answers (1)

Rok Jarc
Rok Jarc

Reputation: 18875

What you have here is a string of raw data that can't be directly converted into a human readable string - unless you consider hex-representation to be human readable :)

To make sense of this data you need to either have a protocol specification at hand or prepare for hours (sometimes) days of reverse-engineering.

This byte-sequence can be composed of multiple values formatted in standard (float IEEE 754, uint8_t, uint16_t...) or even proprietary formats.

One important thing to consider when communicating with the outside world is also endianness (ie: does the 'biggest' byte in multi-byte format come first or last).

There are many ways to manipulate this data. To get the raw array of bytes you could do:

NSData *rxData = ...
uint8_t *bytes = (uint8_t *)[rxData bytes];

And then if (for example) first byte tells you what type of payload the string holds you can switch like:

switch (bytes[0])
{
    case 0x00:
        //first byte 0x00: do the parsing
        break;

    case 0x01:
        //first byte 0x01: do the parsing
        break;

    // ...

    default:
        break;
}

Here would be an example of parsing data that consists of:

byte 0: byte holding some bit-coded flags bytes 1,2,3,4: 32-bit float bytes 5,6: uint16_t

bool bitFlag0;
bool bitFlag1;
bool bitFlag2;
bool bitFlag3;

uint8_t firstByte;
float theFloat;
uint16_t theInteger;

NSData *rxData = ...
uint8_t *bytes = (uint8_t *)[rxData bytes];

// getting the flags
firstByte = bytes[0];

bitFlag0 = firstByte & 0x01;
bitFlag1 = firstByte & 0x02;
bitFlag2 = firstByte & 0x04;
bitFlag3 = firstByte & 0x08;

//getting the float

[[rxData subdataWithRange:NSMakeRange(1, 4)] getBytes:&theFloat length:sizeof(float)];
NSLog (@"the float is &.2f",theFloat);

//getting the unsigned integer

[[data subdataWithRange:NSMakeRange(6, 2)] getBytes:&theInteger length:sizeof(uint16_t)];
NSLog (@"the integer is %u",theInteger);

One note: depending on the endianness you might need to reverse the 4-float or the 2-uint16_t bytes before converting them. Converting this byte arrays can also be done with unions.

union bytesToFloat
{
    uint8_t b[4];
    float f;
};

and then:

bytesToFloat conv;

//float would be written on bytes b1b2b3b4 in protocol
conv.b[0] = bytes[1]; //or bytes[4] .. endianness!
conv.b[1] = bytes[2]; //or bytes[3] .. endianness!
conv.b[2] = bytes[3]; //or bytes[2] .. endianness!
conv.b[3] = bytes[4]; //or bytes[1] .. endianness!

theFloat = conv.f, 

If for example you know that byte6 and byte7 represent an uint16_t value you can calculate it from raw bytes:

value = uint16_t((bytes[6]<<8)+bytes[7]);

or (again - endianness):

value = uint16_t((bytes[7]<<8)+bytes[6]);

One more note: using simply sizeof(float) is a bit risky since float can be 32-bit on one platform and 64-bit on another.

Upvotes: 3

Related Questions