BTLE Transfer/BTLEPeripheralViewController.m
/*  | 
File: LEPeripheralViewController.m  | 
Abstract: Interface to allow the user to enter data that will be  | 
transferred to a version of the app in Central Mode, when it is brought  | 
close enough.  | 
Version: 1.0  | 
Disclaimer: IMPORTANT: This Apple software is supplied to you by  | 
 Apple Inc. ("Apple") in consideration of your agreement to the | 
following terms, and your use, installation, modification or  | 
redistribution of this Apple software constitutes acceptance of these  | 
terms. If you do not agree with these terms, please do not use,  | 
install, modify or redistribute this Apple software.  | 
In consideration of your agreement to abide by the following terms, and  | 
subject to these terms, Apple grants you a personal, non-exclusive  | 
license, under Apple's copyrights in this original Apple software (the  | 
"Apple Software"), to use, reproduce, modify and redistribute the Apple  | 
Software, with or without modifications, in source and/or binary forms;  | 
provided that if you redistribute the Apple Software in its entirety and  | 
without modifications, you must retain this notice and the following  | 
text and disclaimers in all such redistributions of the Apple Software.  | 
Neither the name, trademarks, service marks or logos of Apple Inc.  | 
may be used to endorse or promote products derived from the Apple  | 
Software without specific prior written permission from Apple. Except  | 
as expressly stated in this notice, no other rights or licenses, express  | 
or implied, are granted by Apple herein, including but not limited to  | 
any patent rights that may be infringed by your derivative works or by  | 
other works in which the Apple Software may be incorporated.  | 
The Apple Software is provided by Apple on an "AS IS" basis. APPLE  | 
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION  | 
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS  | 
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND  | 
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.  | 
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL  | 
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF  | 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  | 
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,  | 
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED  | 
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),  | 
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE  | 
POSSIBILITY OF SUCH DAMAGE.  | 
Copyright (C) 2012 Apple Inc. All Rights Reserved.  | 
*/  | 
#import "BTLEPeripheralViewController.h"  | 
#import <CoreBluetooth/CoreBluetooth.h>  | 
#import "TransferService.h"  | 
@interface BTLEPeripheralViewController () <CBPeripheralManagerDelegate, UITextViewDelegate>  | 
@property (strong, nonatomic) IBOutlet UITextView *textView;  | 
@property (strong, nonatomic) IBOutlet UISwitch *advertisingSwitch;  | 
@property (strong, nonatomic) CBPeripheralManager *peripheralManager;  | 
@property (strong, nonatomic) CBMutableCharacteristic *transferCharacteristic;  | 
@property (strong, nonatomic) NSData *dataToSend;  | 
@property (nonatomic, readwrite) NSInteger sendDataIndex;  | 
@end  | 
#define NOTIFY_MTU 20  | 
@implementation BTLEPeripheralViewController  | 
#pragma mark - View Lifecycle  | 
- (void)viewDidLoad  | 
{ | 
[super viewDidLoad];  | 
// Start up the CBPeripheralManager  | 
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];  | 
}  | 
- (void)viewWillDisappear:(BOOL)animated  | 
{ | 
// Don't keep it going while we're not showing.  | 
[self.peripheralManager stopAdvertising];  | 
[super viewWillDisappear:animated];  | 
}  | 
#pragma mark - Peripheral Methods  | 
/** Required protocol method. A full app should take care of all the possible states,  | 
* but we're just waiting for to know when the CBPeripheralManager is ready  | 
*/  | 
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral  | 
{ | 
// Opt out from any other state  | 
    if (peripheral.state != CBPeripheralManagerStatePoweredOn) { | 
return;  | 
}  | 
// We're in CBPeripheralManagerStatePoweredOn state...  | 
NSLog(@"self.peripheralManager powered on.");  | 
// ... so build our service.  | 
// Start with the CBMutableCharacteristic  | 
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]  | 
properties:CBCharacteristicPropertyNotify  | 
value:nil  | 
permissions:CBAttributePermissionsReadable];  | 
// Then the service  | 
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]  | 
primary:YES];  | 
// Add the characteristic to the service  | 
transferService.characteristics = @[self.transferCharacteristic];  | 
// And add it to the peripheral manager  | 
[self.peripheralManager addService:transferService];  | 
}  | 
/** Catch when someone subscribes to our characteristic, then start sending them data  | 
*/  | 
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic  | 
{ | 
NSLog(@"Central subscribed to characteristic");  | 
// Get the data  | 
self.dataToSend = [self.textView.text dataUsingEncoding:NSUTF8StringEncoding];  | 
// Reset the index  | 
self.sendDataIndex = 0;  | 
// Start sending  | 
[self sendData];  | 
}  | 
/** Recognise when the central unsubscribes  | 
*/  | 
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic  | 
{ | 
NSLog(@"Central unsubscribed from characteristic");  | 
}  | 
/** Sends the next amount of data to the connected central  | 
*/  | 
- (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;  | 
}  | 
}  | 
}  | 
/** This callback comes in when the PeripheralManager is ready to send the next chunk of data.  | 
* This is to ensure that packets will arrive in the order they are sent  | 
*/  | 
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral  | 
{ | 
// Start sending again  | 
[self sendData];  | 
}  | 
#pragma mark - TextView Methods  | 
/** This is called when a change happens, so we know to stop advertising  | 
*/  | 
- (void)textViewDidChange:(UITextView *)textView  | 
{ | 
// If we're already advertising, stop  | 
    if (self.advertisingSwitch.on) { | 
[self.advertisingSwitch setOn:NO];  | 
[self.peripheralManager stopAdvertising];  | 
}  | 
}  | 
/** Adds the 'Done' button to the title bar  | 
*/  | 
- (void)textViewDidBeginEditing:(UITextView *)textView  | 
{ | 
// We need to add this manually so we have a way to dismiss the keyboard  | 
UIBarButtonItem *rightButton = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonSystemItemDone target:self action:@selector(dismissKeyboard)];  | 
self.navigationItem.rightBarButtonItem = rightButton;  | 
}  | 
/** Finishes the editing */  | 
- (void)dismissKeyboard  | 
{ | 
[self.textView resignFirstResponder];  | 
self.navigationItem.rightBarButtonItem = nil;  | 
}  | 
#pragma mark - Switch Methods  | 
/** Start advertising  | 
*/  | 
- (IBAction)switchChanged:(id)sender  | 
{ | 
    if (self.advertisingSwitch.on) { | 
// All we advertise is our service's UUID  | 
        [self.peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }]; | 
}  | 
    else { | 
[self.peripheralManager stopAdvertising];  | 
}  | 
}  | 
@end  | 
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-11-15