-
Notifications
You must be signed in to change notification settings - Fork 8
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];
}
}];
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.
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);
}
}];
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);
}
}];
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);
}
}];
}
}];
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
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".