Skip to content

Instantly share code, notes, and snippets.

@yonran
Created December 19, 2013 00:35
Show Gist options
  • Save yonran/8032363 to your computer and use it in GitHub Desktop.
Save yonran/8032363 to your computer and use it in GitHub Desktop.
Bluetooth LE Peripheral Role hello world based on [Performing Common Peripheral Role Tasks](https://developer.apple.com/library/mac/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/PerformingCommonPeripheralRoleTasks/PerformingCommonPeripheralRoleTasks.html). When I run this program on a Mac Mini, the peripheral is advertisi…
#import <Foundation/Foundation.h>
#import <IOBluetooth/IOBluetooth.h>
enum HandlerState {
HandlerStateStarting,
HandlerStateWaitingForServiceToAdd,
HandlerStateWaitingForAdvertisingToStart,
HandlerStateAdvertising,
HandlerStateError,
};
@interface Handler: NSObject <CBPeripheralManagerDelegate>
@property (nonatomic) CBPeripheralManager *peripheralManager;
@property (nonatomic) enum HandlerState state;
@property (nonatomic) NSTimer *incrementTimer;
@property (nonatomic) uint16_t count;
@property (nonatomic) CBMutableCharacteristic *countCharacteristic;
@end
static NSString *MyServiceUuid = @"5C128D26-741E-4BE4-95E5-9DEE6F97414B";
static NSString *CounterCharacteristicUuid = @"5C128D26-741E-4BE4-95E5-9DEE6F97414C";
@implementation Handler
+ (NSString*)stringFromCBPeripheralManagerState:(CBPeripheralManagerState)state {
switch (state) {
case CBPeripheralManagerStatePoweredOff: return @"PoweredOff";
case CBPeripheralManagerStatePoweredOn: return @"PoweredOn";
case CBPeripheralManagerStateResetting: return @"Resetting";
case CBPeripheralManagerStateUnauthorized: return @"Unauthorized";
case CBPeripheralManagerStateUnknown: return @"Unknown";
case CBPeripheralManagerStateUnsupported: return @"Unsupported";
}
}
+ (NSString*)stringFromHandlerState:(enum HandlerState)state {
switch (state) {
case HandlerStateStarting: return @"Starting";
case HandlerStateWaitingForServiceToAdd: return @"WaitingForServiceToAdd";
case HandlerStateAdvertising: return @"Advertising";
case HandlerStateWaitingForAdvertisingToStart: return @"WaitingForAdvertisingToStart";
case HandlerStateError: return @"Error";
}
}
- (void)raiseBadStateTransition:(enum HandlerState) newState {
[NSException raise:@"Invalid state transition" format:@"Cannot transition from %@ to %@", [Handler stringFromHandlerState:self.state], [Handler stringFromHandlerState:newState]];
}
- (void)transitionState:(enum HandlerState)newState {
switch (self.state) {
case HandlerStateStarting:
switch (newState) {
case HandlerStateStarting: break;
case HandlerStateWaitingForServiceToAdd: {
CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:MyServiceUuid] primary:YES];
NSMutableArray *characteristics = [[NSMutableArray alloc] init];
CBMutableCharacteristic *characteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:CounterCharacteristicUuid] properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyIndicate value:nil permissions:CBAttributePermissionsReadable];
[characteristics addObject:characteristic];
self.countCharacteristic = characteristic;
[self.peripheralManager addService:service];
break;
}
case HandlerStateWaitingForAdvertisingToStart:
case HandlerStateAdvertising:
[self raiseBadStateTransition:newState];
case HandlerStateError: break;
}
break;
case HandlerStateWaitingForServiceToAdd:
switch (newState) {
case HandlerStateStarting: break;
case HandlerStateWaitingForServiceToAdd: break;
case HandlerStateWaitingForAdvertisingToStart:
[self.peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey: @"Hello World Peripheral", CBAdvertisementDataServiceUUIDsKey: @[[CBUUID UUIDWithString:MyServiceUuid]]}];
break;
case HandlerStateAdvertising:
[self raiseBadStateTransition:newState];
break;
case HandlerStateError: break;
}
break;
case HandlerStateWaitingForAdvertisingToStart:
switch (newState) {
case HandlerStateStarting:
[self.peripheralManager stopAdvertising];
break;
case HandlerStateWaitingForServiceToAdd:
[self raiseBadStateTransition:newState];
break;
case HandlerStateWaitingForAdvertisingToStart: break;
case HandlerStateAdvertising:
self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(incrementCount:) userInfo:nil repeats:YES];
break;
case HandlerStateError: break;
}
break;
case HandlerStateAdvertising:
switch (newState) {
case HandlerStateStarting:
[self.peripheralManager stopAdvertising];
[self.incrementTimer invalidate];
self.incrementTimer = nil;
break;
case HandlerStateWaitingForServiceToAdd:
case HandlerStateWaitingForAdvertisingToStart:
[self.incrementTimer invalidate];
self.incrementTimer = nil;
[self raiseBadStateTransition:newState];
break;
case HandlerStateAdvertising:
break;
case HandlerStateError:
[self.incrementTimer invalidate];
self.incrementTimer = nil;
break;
}
break;
case HandlerStateError: break;
}
NSLog(@"Transitioning state from %@ to %@", [Handler stringFromHandlerState:self.state], [Handler stringFromHandlerState:newState]);
self.state = newState;
}
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
NSLog(@"CBPeripheralManager entered state %@", [Handler stringFromCBPeripheralManagerState:peripheral.state]);
if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
[self transitionState:HandlerStateWaitingForServiceToAdd];
} else {
[self transitionState:HandlerStateStarting];
}
}
- (void)incrementCount:(NSTimer*)timer {
self.count++;
uint16_t bigEndianCount = ntohs(self.count);
NSData *countData = [NSData dataWithBytes:&bigEndianCount length:sizeof(bigEndianCount)];
if (self.countCharacteristic.subscribedCentrals.count) {
if ([self.peripheralManager updateValue:countData forCharacteristic:self.countCharacteristic onSubscribedCentrals:nil]) {
NSLog(@"Incremented count to %u. Successfully notified %lu subscribers", self.count, self.countCharacteristic.subscribedCentrals.count);
} else {
NSLog(@"Incremented count to %u. Queue full; waiting to notify %lu subscribers", self.count, self.countCharacteristic.subscribedCentrals.count);
}
} else {
NSLog(@"Incremented timer to %u, but no one is subscribed.", self.count);
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict {
NSLog(@"peripheralManager:willRestoreState: %@", dict);
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error {
NSLog(@"peripheralManagerDidStartAdvertising (error:%@)", error);
if (error == nil) {
[self transitionState:HandlerStateAdvertising];
} else {
[self transitionState:HandlerStateError];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if (error == nil) {
[self transitionState:HandlerStateWaitingForAdvertisingToStart];
} else {
[self transitionState:HandlerStateError];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"Central:%@ didSubscribeToCharacteristic:%@", central, characteristic);
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"Central:%@ didUnsubscribeFromCharacteristic:%@", central, characteristic);
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
NSLog(@"DidReceiveReadRequest:%@", request);
if ([request.characteristic.UUID isEqual:[CBUUID UUIDWithString:CounterCharacteristicUuid]]) {
uint16_t bigEndianCount = ntohs(self.count);
request.value = [NSData dataWithBytes:&bigEndianCount length:sizeof(bigEndianCount)];
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
} else {
[self.peripheralManager respondToRequest:request withResult:CBATTErrorAttributeNotFound];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
for (CBATTRequest *request in requests) {
[self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral {
uint16_t bigEndianCount = ntohs(self.count);
NSData *countData = [NSData dataWithBytes:&bigEndianCount length:sizeof(bigEndianCount)];
if ([self.peripheralManager updateValue:countData forCharacteristic:self.countCharacteristic onSubscribedCentrals:nil]) {
NSLog(@"peripheralManagerIsReadyToUpdateSubscribers count to %us. Successfully sent to %lu subscribers", self.count, self.countCharacteristic.subscribedCentrals.count);
} else {
NSLog(@"peripheralManagerIsReadyToUpdateSubscribers count to %us. Queue full; waiting again to notify %lu subscribers", self.count, self.countCharacteristic.subscribedCentrals.count);
}
}
@end
int main(int argc, const char *argv[])
{
@autoreleasepool {
Handler *handler = [[Handler alloc] init];
CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:handler queue:nil];
handler.peripheralManager = peripheralManager;
peripheralManager.delegate = handler;
NSLog(@"Hello, World!");
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment