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.
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.
BTLE is significantly limited in terms of the amount of data that can be sent both in the characteristics and in any data packet. You should make the connection between the devices and then split the image data up and send multiple small packets. The 2013 WWDC videos will provide you a good overview and code samples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With