Skip to content
Guido Marucci Blas edited this page Nov 18, 2014 · 7 revisions

WLXBluetoothDevice Wiki

This is documentation page of the library, here you can find a brief explanation of the library's architecture and how to perform the most important operations. Although full knowledge of the CoreBluetooth framework is not required, it is recommend to a least have an idea of the key components of the framework. Remind that this library is a wrapper of CoreBluetooth but some of its classes are widely used and expose in the library's API.

If this is the first time you are using Bluetooth we recommend you to read the CoreBluetooth programming guide and the framework reference. Also this WWDC 2013 video is pretty good to get an introduction to CoreBluetooth.

Architecture

One of the main goals of this library is to separate the responsibilities of the connection process in different classes in order to have a more maintainable and easy to read code. We think that by providing small classes with concrete responsibilities, the application's code will be easier and simpler to write. The following is a list of the most important classes and protocols in this library:

  • WLXBluetoothDeviceManager: The root class of the library that provides access to all the other classes.
  • WLXDeviceDiscoverer: A protocol that exposes an API to discover devices with Bluetooth 4.0 capabilities.
  • WLXConnectionManager: A protocol that exposes an API to manage the connection life cycle with a Bluetooth device that was previously discovered.
  • WLXServicesManager: A class that provides a way to discover services and create a manager for each discovered service.
  • WLXServiceManager: A class that manages a Bluetooth service providing an API to interact with it. This is the class that allows clients to read and write values to a characteristic.

WLXBluetoothDeviceManager

The root class that provides access to all the other classes is WLXBluetoothDeviceManager. You should never create an instance of another class of this library except from WLXBluetoothDeviceManager. Any other object instance of this library should be obtained from an instance of WLXBluetoothDeviceManager.

Although is not necessary, is recommended to only have one instance of this class in the application's life cycle. There are several way to get an instance but the easies one is to call the class method deviceManager. This method will create a new instance of WLXBluetoothDeviceManager using a default dispatch_queue_t. This means that all the bluetooth related operation will be executed on dedicated serial dispatch queue. You can also create an instance of WLXBluetoothDeviceManager with your own custom dispatch queue by calling the class method deviceManagerWithQueye:. Keep in mind that the use of serial dispatch queues is recommended because they assure that the messages will be processed in the same order they arrived and one at a time. This might be important for your application's logic and is a safe default.

Discovery

The discovery process is handled by an object that conforms to the WLXDiscoverer protocol. You can get this object by accessing the discoverer property of the WLXBluetoothDeviceManager. This property will return an object that implements the WLXDiscoverer protocol.

The WLXDiscoverer protocol exposes a simple API to discover devices. The only thing you should do to start discovering is invoke the discoverDevicesNamed:withServices:andTimeout: method. You can filter devices by their name, the services they expose or both. A discovery timeout (in milliseconds) is mandatory and must be a positive number. After the timeout expires the discovery process will be stopped. If you want to filter devices by the services they expose you should provide an NSArray of CBUUID objects with the required services UUID.

You can also configure some properties of the discovery process by setting the scanOptions property of the WLXDiscoverer object. This property expect an NSDictionary with some specific values. The default value is @{CBCentralManagerScanOptionAllowDuplicatesKey: @NO};

Every time a new devices gets discovered the deviceDiscoverer:discoveredDevice: method of the WLXDeviceDiscovererDelegate. You should implement this protocol and set the delegate using the delegate property of the WLXDiscoverer object. When a device is discovered a WLXDiscoveryData object is provided. This object contains information about the device and its discovery data, for example it contains the CBPeripheral object.

The WLXDeviceDiscovererDelegate will also be notified every time the discovery process is started or stopped.

To stop the discovery process you can invoke the stopDiscoveringDevices method.

Notifications

The WLXDiscoverer also publishes discovery related notifications using NSNotificationCenter. The following is a list of the notifications:

  • WLXBluetoothDeviceStartDiscovering: When the discovery process is started.
  • WLXBluetoothDeviceStoptDiscovering: Every time a new devices is discovered. You can get the WLXDiscoveryData object from the user info dictionary of the NSNotification object using the key WLXBluetoothDeviceDiscoveryData.
  • WLXBluetoothDeviceDeviceDiscovered: When the discovery process is stopped.

Examples

To discover all the devices that expose a service with UUID 68753A44-4D6F-1226-9C60-0050E4C00066 with a timeout of 30 seconds, you should call the discover method with the following parameters:

WLXBluetoothDeviceManager * deviceManager = [WLXBluetoothDeviceManager deviceManager];
id<WLXDiscoverer> discoverer = deviceManager.discoverer;
discoverer.delegate = self;
CBUUID * serviceUUID = [CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00066"];
[discoverer discoverDevicesNamed:nil withServices:@[serviceUUID] andTimeout:30000];

If you want to list only the devices whose name starts with Tracker, you can pass a regular expression to the name parameter:

[discoverer discoverDevicesNamed:@"Tracker.*" withServices:nil andTimeout:30000];

This will match device with the name Tracker, Tracker1, Tracker A, etc. You can also combine the name filter with the service filter.

Here is an example of UITableViewController that lists all the discovered devices in a UITableView.

@import CoreBluetooth;

@interface WLXDiscoverViewController ()<WLXDeviceDiscovererDelegate>

@property (nonatomic) id<WLXDeviceDiscoverer> discoverer;

@end

static NSUInteger DISCOVERY_TIMEOUT = 30000; //ms

@implementation WLXDiscoverViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)viewWillAppear:(BOOL)animated {
    self.discoverer.delegate = self;
    [self discover];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self.discoverer stopDiscoveringDevices];
}

#pragma mark - WLXDeviceDiscovererDelegate methods

- (void)deviceDiscoverer:(id<WLXDeviceDiscoverer>)discoverer startDiscoveringDevicesWithTimeout:(NSUInteger)timeout {

}

- (void)deviceDiscoverer:(id<WLXDeviceDiscoverer>)discoverer discoveredDevice:(WLXDeviceDiscoveryData *)discoveryData {
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
}

- (void)deviceDiscovererStopDiscoveringDevices:(id<WLXDeviceDiscoverer>)discoverer {

}

#pragma mark - UITableViewDelegate methods

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    CBPeripheral * peripheral = [self peripheralForIndexPath:indexPath];
    [self.delegate discoverViewController:self didSelectPeripheral:peripheral];
}

#pragma mark - UITableViewDataSource methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.discoverer.discoveredDevices count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell * cell = [self.tableView dequeueReusableCellWithIdentifier:@"CellIdentifier" forIndexPath:indexPath];
    CBPeripheral * peripheral = [self peripheralForIndexPath:indexPath];
    cell.textLabel.text = peripheral.name;
    cell.detailTextLabel.text = peripheral.identifier.UUIDString;
    return cell;
}

#pragma mark - Private methods

- (void)discover {
    self.title = @"Discovering ...";
    [self.discoverer discoverDevicesNamed:nil withServices:nil andTimeout:DISCOVERY_TIMEOUT];
}

- (CBPeripheral *)peripheralForIndexPath:(NSIndexPath *)indexPath {
    WLXDeviceDiscoveryData * discoveryData = self.discoverer.discoveredDevices[indexPath.row];
    return discoveryData.peripheral;
}

@end

Connection management

Once you discover the devices, is time to establish a connection with one of them. To do this you need a WLXConnectionManager. The WLXConnectionManager is a protocol that exposes an API to manage the connection with a device. As with the WLXDiscoverer, you ask for an object that implements the WLXConnectionManager to the WLXBluetoothDeviceManager through the method connectionManagerForPeripheral:usingReconnectionStrategy:.

To obtain a WLXConnectionManager you need a CBPeripheral and WLXReconnectionStrategy. You can get the CBPeripheral from the WLXDiscoveryData. As for the reconnection strategy you can create an instance of one the classes provided by the library or implement your own.

Once you get a WLXConnectionManager you can start a connection using one of the following methods: connectWithTimeout: or connectWithTimeout:usingBlock:. Both of this methods require a timeout (in milliseconds). After the timeout expires, if the connection cannot be established, an error will be raised. If you use the method that requires a block, that block will get called with an error or nil if the connection was successfully established.

If you want to be notified about the connection status, you can implement the WLXConnectionManagerDelegate that provides methods for every possible scenario:

  • Connection established
  • Fail to connect
  • Connection lost
  • Connection terminated
  • Did reconnect
  • Will attempt to reconnect

Reconnection strategies

The library provides a protocol to encapsulate the reconnection logic, WLXReconnectionStrategy. It also provides two concrete implementations: WLXLinearReconnectionStrategy and WLXNullReconnectionStrategy.

The WLXNullReconnectionStrategy, as its name suggests, does nothing. If the connection is lost a connection lost error will immediately be raised.

The WLXLinearReconnectionStrategy performs a linear back-off between each reconnection attempt. Every time a connection is lost this strategy will check if there are any remaining connection attempts. In such case, after waiting for a certain amount of time, it will try to reconnect. If there are no remaining reconnection attempts a connection lost error will be raised. To create an instance of WLXLinearReconnectionStrategy you must provide the following parameters:

  • a wait time, that is the amount of time (in milliseconds) to wait between each reconnection attempt.
  • the maximum amount of reconnection attempts.
  • a connection timeout, you can use the same one you use for the first connection
  • a dispatch queue, we recommend to provide the same one the device manager is using. You can getting by doing deviceManager.queue

Here is an example of how to create a linear reconnection strategy:

WLXBluetoothDeviceManager * deviceManager = ...;
id<WLXReconnectionStrategy> reconnectionStrategy =
  [[WLXLinearReconnectionStrategy alloc] initWithWaitTime:2000
                                  maxReconnectionAttempts:3
                                        connectionTimeout:3000
                                                    queue:deviceManager.queue];

Notifications

The WLXConnectionManager also publishes connection related notifications using NSNotificationCenter. The following is a list of the notifications:

  • WLXBluetoothDeviceConnectionEstablished: Published every time a connection is established. You can get the peripheral from the user info using the key WLXBluetoothDevicePeripheral.
  • WLXBluetoothDeviceReconnectionEstablished: Published every time a reconnection is established. You can get the peripheral from the user info using the key WLXBluetoothDevicePeripheral.
  • WLXBluetoothDeviceFailToConnect: Published when a connection with a peripheral could not be established. You can get the error from the NSNotification user info using the key WLXBluetoothDeviceError.
  • WLXBluetoothDeviceConnectionLost: Published when a established connection with a peripheral is lost. You can get the error and the peripheral from the user info dictionary using the keys WLXBluetoothDeviceError and WLXBluetoothDevicePeripheral.
  • WLXBluetoothDeviceConnectionTerminated: Published every time a the WLXConnecitonManager disconnect method is called and the connection is successfully terminated.
  • WLXBluetoothDeviceReconnecting: Published every time a reconnection attempt is about to be executed. You can get the remaining reconnection attempts from the user info dictionary using the key WLXBluetoothDeviceRemainingReconnectionAttemps.

Device registry

A device registry encapsulates the logic of maintaining a registry of the connected devices, so the next time you want to connect with a device you do not have to discover a device a again. To create a new WLXBluetoothDeviceRegistry you need an object that conforms to the WLXBluetoothDeviceRepository protocol. The repository is the one that actually stores the required information to later be able to retrieve a peripheral. This information is encapsulated in a WLXBluetoothDeviceConnectionRecord.

Once you get an instance of WLXBluetoothDeviceRegistry you have to enable it, then every time a new connection is established the registry will save the connection record using the provided repository. To enable the registry you should set the enabled property to YES.

You can have more than one device registry but you probably need one. Keep in mind that if you have more than one registry they should use different repositories.

The library provides an repository that stores the last connection record into the user defaults, WLXBluetoothDeviceUserDefaultsRepository but you can have your own repository, say core data, by implementing the protocol WLXBluetoothDeviceRepository.

Examples

The following is an example of how to create a device registry using the user defaults repository:

WLXBluetoothDeviceManager * deviceManager = [WLXBluetoothDeviceManager deviceManager];
id<WLXBluetoothDeviceRepository> repository = [[WLXBluetoothDeviceUserDefaultsRepository alloc] initWithUserDefaults:[NSUserDefaults standardUserDefaults]];
id<WLXBluetoothDeviceRegistry> registry = [deviceManager deviceRegistryWithRepository:repository];
registry.enabled = YES;

Here is an example of how to establish a connection with a previously stored device:

id<WLXReconnectionStrategy> strategy = ...;
CBPeripheral * peripheral = registry.lastConnectedPeripheral;
id<WLXConnectionManager> connectionManager = [deviceManager connectionManagerForPeripheral:peripheral usingReconnectionStrategy:strategy];
[connectionManager connectWithTimeout:3000];

Services

As you probably already know Bluetooth 4.0 devices expose services. Those services are basically a group of characteristics and they are grouped together because they share some common logic. Most probably you will endup organizing your Bluetooth application code that handles this logic in services and those services would probably have a one-to-one relation with the Bluetooth services. This is what we got in mind when we were designing the services API.

Once a connection has been established with a device you would need to discover all its services. This is when you need the WLXServicesManager. The services manager can be obtained from a WLXConnectionManager through the servicesManager property. This property will return nil if the connection was not established. After you get a reference to the services manager, it does not matter if the connections gets lost; the services manager is bound to the CBPeripheral object. Once the connection is reestablished you can still use the same services manager (same for all its child objects).

With the WLXServicesManager instance you can discover all the device's services by calling discoverServicesUsingBlock:. The block you pass to this method will get called with an NSError in case there is a problem discovering the services or nil if the discovery process succeed.

For each discovered service an instance of WLXServiceManager is created. This class is the one that is actually responsable of the interaction with the Bluetooth service and its characteristics. In order to get an instance of WLXServiceManager you need the service's UUID and call WLXServicesManager's managerForService: method.

The following is an example of how to get a WLXServiceManager instance for a particular service:

CBUUID * serviceUUID = [CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00066"];
WLXServicesManager * servicesManager = connectionManager.servicesManager;
[servicesManager discoverServicesUsingBlock:^(NSError * error) {
  if (error) {
    NSLog(@"There was an error discovering the services");
  } else {
    WLXServiceManager * serviceManager = [servicesManager managerForService:serviceUUID];
  }
}];

Characteristics

A characteristic is the "atomic" value in the Bluetooth 4.0 world. When you want to read o write a value from a Bluetooth device, you are reading or writing a characteristic.

The WLXServiceManager exposes an API to read or write a service's characteristic. You can also subscribe for notification for those characteristic which provide notifications.

Using WLXServiceManager to interact with the characteristic allows you to not having to worry about discovery characteristics before you can write o read them. All you need is the characteristic UUID and library will discovery the characteristic if necessary.

Lets say you want to write to characteristic 68753A44-4D6F-1226-9C60-0050E4C00067 and you don't care to be acknowledge if the write was successful. All you need to do is call the writeValue:forCharacteristicUUID: method from WLXServiceManager and the library will automatically discover the characteristic and then perform a write. Once the characteristic is discovered, it gets cached so other operations don't have to discover it again.

Reading a characteristic's value

If you want to read a value from a characteristic you should invoke the readValueForCharacteristicUUID:usingBlock:. Suppose you have a characteristic with UUID 68753A44-4D6F-1226-9C60-0050E4C00067 that holds an NSUInteger value. The following is an example of how would you read that value:

CBUUID * characteristicUUID = [CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"];
[serviceManager readValueForCharacteristicUUID:characteristicUUID usingBlock:^(NSError * error, NSData * data) {
  if (error) {
    NSLog(@"There was an error reading characteristic %@: %@", characteristicUUID.UUIDString, error);
  } else {
    NSUInteger value = *((NSUInteger *)data.bytes);
    NSLog(@"Read value is %ul", (unsigned long)value);
  }
}];

Writing a characteristic's value

There are two ways of writing a characteristic's value. You can write passing a callback block or without a callback block, writeValue:forCharacteristicUUID:usingBlock: or writeValue:forCharacteristicUUID:. If you use the version with the callback block, it will translate to a write of type CBCharacteristicWriteWithResponse. If you don't provide a callback the write will be of type CBCharacteristicWriteWithoutResponse, in this case there is no way of knowing if the write succeed or not.

CBUUID * characteristicUUID = [CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"];
NSUInteger value = 10;
NSData * data = [NSData dataWithBytes:&value length:sizeof(value)];
[serviceManager writeValue:data forCharacteristicUUID:characteristicUUID usingBlock:^(NSError * error) {
  if (error) {
    NSLog(@"There was an error writing characteristic %@: %@", characteristicUUID.UUIDString, error);
  }
}];

Subscribing to a characteristic's value

There are some characteristics that allow notifications, meaning every time the characteristic value gets updated you will get a notification. To enable and disable notifications for a characteristic you can use the following methods enableNotificationsForCharacteristic:usingBlock: and disableNotificationsForCharacteristic:usingBlock:.

After you enable notifications for a characteristic you have to subscribe an observer for that characteristic using one of the following methods: addObserverForCharacteristic:usingBlock: or addObserverForCharacteristic:selector:target:. You can then remove observers using the removeObserver: method.

CBUUID * characteristicUUID = [CBUUID UUIDWithString:@"68753A44-4D6F-1226-9C60-0050E4C00067"];
[serviceManager enableNotificationsForCharacteristic:characteristicUUID usingBlock:^(NSError * error) {
  if (error) {
    NSLog(@"There was an error enabling notifications for characteristic %@", characteristicUUID.UUIDString);
  } else {
    serviceManager addObserverForCharacteristic:characteristicUUID usingBlock:^(NSError * error, NSData * data) {
      if (error) {
        NSLog(@"There was an error reading characteristic %@: %@", characteristicUUID.UUIDString, error);
      } else {
        NSUInteger value = *((NSUInteger *)data.bytes);
        NSLog(@"Value updated to %lu", (unsigned long)value);
      }
    }];
  }
}];

Implementing your own services

The following is an example of how we suggest Bluetooth based services should be implemented using this library. Lets say that we a thermostat device that provides two services: a temperature service and a battery service.

The temperature service exposes a read/write characteristic that allows you to get/set the thermostat temperature and a another characteristic that allows you to set the temperature units: Fahrenheit or Celsius

The battery services exposes a read/notify characteristic that allows you to get the battery level.

The following is an example of how you could implement such services using WLXServiceManager:

TemperatureService.h

typedef enum TemperatureUnit {
  TemperatureUnitFahrenheit,
  TemperatureUnitCelsius
} TemperatureUnit;

@interface TemperatureService : NSObject

@property (nonatomic, readonly) WLXServiceManager * serviceManager;

+ (CBUUID *)serviceUUID;

- (instancetype)initWithServiceManager:(WLXServiceManager *)serviceManager;

- (void)readTemperatureUsingBlock:(void(^)(NSError *, CGFloat));

- (void)setTemperature:(CGFloat)temperature usingBlock:(void(^)(NSError *))block;

- (void)setTemperatureUnit:(TemperatureUnit)unit usingBlock:(void(^)(NSError *))block;

@end

TemperatureService.m

static NSString * serviceUUIDString = @"68753A44-4D6F-1226-9C60-0050E4C00066";
static NSString * temperatureCharacteristicUUIDString = @"68753A44-4D6F-1226-9C60-0050E4C00067";
static NSString * temperatureUnitCharacteristicUUID = @"68753A44-4D6F-1226-9C60-0050E4C00068";
static CBUUID * serviceUUID;
static CBUUID * temperatureCharacteristicUUID;
static CBUUID * temperatureUnitCharacteristicUUID;

@implementation TemperatureService

+ (void)initialize {
  serviceUUID = [CBUUID UUIDWithString:serviceUUIDString];
  temperatureCharacteristicUUID = [CBUUID UUIDWithString:temperatureCharacteristicUUIDString];
  temperatureUnitCharacteristicUUID = [CBUUID UUIDWithString:temperatureUnitCharacteristicUUIDString];
}

+ (CBUUID *)serviceUUID {
  return serviceUUID;
}

- (instancetype)initWithServiceManager:(WLXServiceManager *)serviceManager {
  self = [super init];
  if (self) {
    _serviceManager = serviceManager;
  }
  return self;
}

- (void)readTemperatureUsingBlock:(void(^)(NSError *, CGFloat))block {
  [self.serviceManager readValueForCharacteristicUUID:temperatureCharacteriscticUUID usingBlock:^(NSError * error, NSData * data) {
    if (error) {
      block(error, 0.0);
    } else {
      CGFloat value = *((CGFloat *)data.bytes);
      block(nil, value);
    }
  }];
}

- (void)setTemperature:(CGFloat)temperature usingBlock:(void(^)(NSError *))block {
  NSData * data = [NSData dataWithBytes:&temperature size:sizeof(temperature)];
  [self.serviceManager writeValue:data forCharacteristicUUID:temperatureCharacteristicUUID usingBlock:block];
}

- (void)setTemperatureUnit:(TemperatureUnit)unit usingBlock:(void(^)(NSError *))block {
  NSData * data = [NSData dataWithBytes:&unit size:sizeof(unit)];
  [self.serviceManager writeValue:data forCharacteristicUUID:temperatureUnitCharacteristicUUID usingBlock:block];
}

@end

BatteryService.h

@interface BatteryService : NSObject

@property (nonatomic, readonly) WLXServiceManager * serviceManager;
@property (nonatomic) BOOL notificationsEnabled;
@property (nonoatomic, copy) void (^notificationsErrorHandler)(NSError *);

+ (CBUUID *)serviceUUID;

- (instancetype)initWithServiceManager:(WLXServiceManager *)serviceManager;

- (void)readBatteryLevelUsingBlock:(void(^)(NSError *, NSUInteger))block;

- (id)addBatteryLevelObserver:(void(^)(NSError *, NSUInteger))observer;

- (void)removeBatteryLevelObserver:(id)observer;

@end

BatteryService.m

static NSString * batteryCharacteristicUUIDString = @"68753A44-4D6F-1226-9C60-0050E4C00069";
static NSString * serviceUUIDString = @"68753A44-4D6F-1226-9C60-0050E4C00070";
static CBUUID * serviceUUID;
static CBUUID * batteryCharacteristicUUID;

@implementation BatteryService

+ (void)initialize {
  serviceUUID = [CBUUID UUIDWithString:serviceUUIDString];
  batteryCharacteristicUUID = [CBUUID UUIDWithString:batteryCharacteristicUUIDString];
}

+ (CBUUID *)serviceUUID {
  return serviceUUID;
}

- (instancetype)initWithServiceManager:(WLXServiceManager *)serviceManager {
  self = [super init];
  if (self) {
    _serviceManager = serviceManager;
    _notificationsEnabled = NO;
  }
  return self;
}

- (void)setNotificationsEnabled:(BOOL)enabled {
  if (enabled == _notificationsEnabled) {
    return;
  }

  _notificationsEnabled = enabled;
  if (enabled) {
    [self.serviceManager enableNotificationsForCharacteristic:batteryCharacteristicUUID usingBlock:^(NSError * error) {
      if (error) {
        _notificationsEnabled = NO;
        if (self.notificationsErrorHandler) {
          notificationsErrorHandler(error);
        }
      }
    }];
  } else {
    [self.serviceManager disableNotificationsForCharacteristic:batteryCharacteristicUUID usingBlock:^(NSError * error) {
      if (error) {
        _notificationsEnabled = YES;
        if (self.notificationsErrorHandler) {
          notificationsErrorHandler(error);
        }
      }
    }];
  }
}

- (void)readBatteryLevelUsingBlock:(void(^)(NSError *, NSUInteger))block {
  [self.serviceManager readValueForCharacteristicUUID:batteryCharacteristicUUID usingBlock:^(NSError * error, NSData * data) {
    if (error) {
      block(error, 0);
    } else {
      NSUInteger value = *((NSUInteger *)data.bytes);
      block(nil, value);
    }
  }];
}

- (id)addBatteryLevelObserver:(void(^)(NSError *, NSUInteger))observer {
  return [self.serviceManager addObserverForCharacteristic:batteryCharacteristicUUID usingBlock:^(NSError * error, NSData * data) {
    if (error) {
      block(error, 0);
    } else {
      NSUInteger value = *((NSUInteger *)data.bytes);
      block(nil, value);
    }
  }];
}

- (void)removeBatteryLevelObserver:(id)observer {
  [self.serviceManager removeObserver:observer];
}

@end

Dispatch queues

Apart from the dispatch queue that the WLXBluetoothDeviceManager uses for all the Bluetooth operations, each WLXServiceManager creates its own serial dispatch queue. Once a bluetooth event gets dispatched to the service manager all the service related operations, like executing observers or callback blocks, are executed in a dedicated serial dispatch queue.

This is done to prevent "slow clients" to congest other services. For example if one service is notifying a lot of notifications and the registered observers are taking too long to process those notifications, the only service that will be affected will be the once with "slow clients".

Bluetooth general notifications

There also are notifications triggered by the library every time the bluetooth service is powered on/off:

  • WLXBluetoothDeviceBluetoothIsOn
  • WLXBluetoothDeviceBluetoothIsOff
  • WLXBluetoothDeviceBluetoothPowerStatusChanged

Logger configuration

TODO

Clone this wiki locally