Sending image file over Bluetooth 4.0 LE

tagabek picture tagabek · Aug 28, 2013 · Viewed 14.7k times · Source

I am trying to send an .png image file from one iOS Device to another over Bluetooth 4.0 LE.

I am able to simple pieces of data like strings, but unable to successfully send and utilize image files.

In the Peripheral device, I start out with this

pictureBeforeData = [UIImage imageNamed:@"myImage.png"];
NSData *myData = UIImagePNGRepresentation(pictureBeforeData);

Then I make myData a characteristic's value.

_myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:_myCharacteristicUUID
                                   properties:CBCharacteristicPropertyRead
                                        value:myData
                                  permissions:CBAttributePermissionsReadable];

In the Central Device, I have this when the characteristic's value is updated

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
  if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:_myCharacteristicUUID]]) {
    NSLog(@"PICTURE CHARACTERISTIC FOUND"); // This successfully gets logged

    NSData *dataFromCharacteristic = [[NSData alloc] initWithData:characteristic.value];

    UIImage *imageFromData = [[UIImage alloc]initWithData:dataFromCharacteristic];

    // This correctly logs the size of my image
    NSLog(@"SIZE: %f, %f", imageFromData.size.height, imageFromData.size.width);

    // This causes the imageView to turn completely black
    _sentPictureImageView.image = imageFromData;

    if (!([_myImagesArray containsObject:imageFromData]))
    {
      NSLog(@"DOES NOT CONTAIN"); // This is successfully logged
      [_myImagesArray addObject:imageFromData]; // This runs but is apparently not adding the image to the array
    }

    NSLog(@"COUNT: %u", _contactsImagesArray.count); // This always logs 0 - The array is empty }

EDIT 8-27-13: I am able to send over a single color 1by1 pixel .png file successfully that file is about 5,600 bytes large. I am unable to send a multi-color 20by20pixel .png file that is only about 2,000 bytes. I am able to send over the larger file that contains only one color and less pixels, but unable to send the smaller file that contains many colors and more pixels.

EDIT 8-30-13: I definitely have to parse the data into smaller chunks. In one of Apple's example projects, I found a method that does just that. I can't seem to understand exactly how to implement this in my own code, but I am currently attempting to figure out how. Here's the code:

- (void)sendData {
// First up, check if we're meant to be sending an EOM
static BOOL sendingEOM = NO;

if (sendingEOM) {

    // send it
    BOOL didSend = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];

    // Did it send?
    if (didSend) {

        // It did, so mark it as sent
        sendingEOM = NO;

        NSLog(@"Sent: EOM");
    }

    // It didn't send, so we'll exit and wait for peripheralManagerIsReadyToUpdateSubscribers to call sendData again
    return;
}

// We're not sending an EOM, so we're sending data

// Is there any left to send?

if (self.sendDataIndex >= self.dataToSend.length) {

    // No data left.  Do nothing
    return;
}

// There's data left, so send until the callback fails, or we're done.

BOOL didSend = YES;

while (didSend) {

    // Make the next chunk

    // Work out how big it should be
    NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex;

    // Can't be longer than 20 bytes
    if (amountToSend > NOTIFY_MTU) amountToSend = NOTIFY_MTU;

    // Copy out the data we want
    NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend];

    // Send it
    didSend = [self.peripheralManager updateValue:chunk forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];

    // If it didn't work, drop out and wait for the callback
    if (!didSend) {
        return;
    }

    NSString *stringFromData = [[NSString alloc] initWithData:chunk encoding:NSUTF8StringEncoding];
    NSLog(@"Sent: %@", stringFromData);

    // It did send, so update our index
    self.sendDataIndex += amountToSend;

    // Was it the last one?
    if (self.sendDataIndex >= self.dataToSend.length) {

        // It was - send an EOM

        // Set this so if the send fails, we'll send it next time
        sendingEOM = YES;

        // Send it
        BOOL eomSent = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];

        if (eomSent) {
            // It sent, we're all done
            sendingEOM = NO;

            NSLog(@"Sent: EOM");
        }

        return;
    }
}}

My imageView is the black square, which should be displaying the image I have sent over. enter image description here

Answer

allprog picture allprog · Aug 28, 2013

On iOS 6, BLE allows you to send around 20 byte chunks of data. For an example of how you can slice up the data to be sent and how to use notifications based transmission, download the BTLE Transfer Apple Example application.

To get a better understanding of what speeds you can achieve, I suggest you analyze this diagram http://www.scriptreactor.com/conn_interval-throughput.pdf It shows the achievable speeds as a function of connection interval. iOS does not give you such a fine grained control but this info is still valuable for you to calculate with.