The BlueCap PeripheralManager implementation replaces CBPeripheralManagerDelegate protocol implementations with with a Scala Futures interface using SimpleFutures. Futures provide an interface for performing nonblocking asynchronous requests and serialization of multiple requests. This section will give example implementations for supported use cases.
- PowerOn/PowerOff: Detect when the bluetooth transceiver is powered on and off.
- Add Services and Characteristics: Add services and characteristics to a Peripheral application.
- Advertising: Advertise a Peripheral application.
- Set Characteristic Value: Set a characteristic value for a Peripheral application.
- Update Characteristic Value: Send characteristic value update notifications to Centrals.
- Respond to Characteristic Write: Respond to characterize value writes from a Central.
- iBeacon Emulation: Emulate an iBeacon with a Peripheral application.
- State Restoration: Restore state of
PeripheralManagerusing iOS state restoration. - Errors: Description of all errors.
ManagerState is a direct mapping to CBManagerState namely,
public enum ManagerState: CustomStringConvertible {
case unauthorized
case unknown
case unsupported
case resetting
case poweredOff
case poweredOn
}The state of CBPeripheralManager is communicated to an application by the PeripheralManager method,
public func whenStateChanges() -> FutureStream<ManagerState>To process events,
let manager = PeripheralManager(options: [CBPeripheralManagerOptionRestoreIdentifierKey : "us.gnos.BlueCap.peripheral-manager-documentation" as NSString])
let stateChangeFuture = manager.whenStateChanges()
stateChangeFuture.onSuccess { state in
switch state {
case .poweredOn:
break
case .poweredOff, .unauthorized:
break
case .resetting:
break
case .unknown:
break
case .unsupported:
break
}
}Services and Characteristics are added to a PeripheralManager application before advertising.
PeripheralManager provides the following methods used for managing Services are,
// add a single service
public func add(_ service: MutableService) -> Future<Void>
// remove a service
public func remove(_ service: MutableService)
// remove all services
public func removeAllServices()MutableService provides the methods for adding MutableCharacteristics,
// add characteristics
public var characteristics = [MutableCharacteristic] {get set}
// create characteristics from profiles
public func characteristicsFromProfiles()A PeripheralManager application adds MutableServices and MutableCharacteristics using,
enum AppError: Error {
case invalidState
case resetting
case poweredOff
case unsupported
case unlikely
}
let manager = PeripheralManager(options: [CBPeripheralManagerOptionRestoreIdentifierKey : "us.gnos.BlueCap.peripheral-manager-documentation" as NSString])
let service = MutableService(uuid: TISensorTag.AccelerometerService.uuid)
let characteristic = MutableCharacteristic(profile: RawArrayCharacteristicProfile<TISensorTag.AccelerometerService.Data>())
// Add Characteristic to Service
service.characteristics = [characteristic]
let addServiceFuture = manager.whenStateChanges().flatMap { state -> Future<Void> in
guard let manager = manager else {
throw AppError.unlikely
}
switch state {
case .poweredOn:
manager.removeAllServices()
return manager.add(self.accelerometerService)
case .poweredOff:
throw AppError.poweredOff
case .unauthorized, .unknown:
throw AppError.invalidState
case .unsupported:
throw AppError.unsupported
case .resetting:
throw AppError.resetting
}
}
startAdvertiseFuture.onFailure { error in
guard let manager = manager else {
return
}
switch error {
case AppError.poweredOff:
manager.reset()
case AppError.resetting:
manager.reset()
case AppError.unsupported:
break
default:
manager.reset()
}
}First MutableServices and MutableCharacteristics are created using the TISensorTag.AccelerometerService profile. The Characteristic is then added to the Service and when .powerOn is received existing services are removed and the new 'Service` added.
Also, An error was added to handle PeripheralManager state transitions other than .powerOn and PeripheralManager#reset is used to recreate CBPripheralManager.
After Services and Characteristics have been added the PeripheralManager is ready to begin advertising. PeripheralManager provides the following methods to manage advertisement,
// start advertising with name and services
public func startAdvertising(_ name: String, uuids: [CBUUID]? = nil) -> Future<Void>
// stop advertising
public func stopAdvertising(timeout: TimeInterval = 10.0) -> Future<Void>A PeripheralManager application can start advertising after MutableServices and MutableCharacteristics are added,
let serviceUUID = CBUUID(string: TISensorTag.AccelerometerService.uuid)
let startAdvertisingFuture = addServiceFuture.flatMap { _ -> Future<Void> in
guard let manager = manager else {
throw AppError.unlikely
}
manager.startAdvertising(TISensorTag.AccelerometerService.name, uuids: [serviceUUID])
}Here the addServiceFuture is completed after Services are added and PeripheralManager is advertising that it supports the TISensorTag.AccelerometerService.
A MutableCharacteristic value can be set any time after its supporting MutableService has been successfully added to PeripheralManager. The value is defined by,
var value : NSData? {get set}A PeripheralManager application can set a MutableCharacteristic value using,
characteristic.value = Serde.serialize(Enabled.yes)
guard let value: Enabled = characteristic.value {
return
}If a MutableCharacteristic supports either CBCharacteristicProperties of CBCharacteristicProperties.notify, CBCharacteristicProperties.indicate, CBCharacteristicProperties.notifyEncryptionRequired, or CBCharacteristicProperties.indicateEncryptionRequired and a MutableService supporting the MutableCharacteristic have been successfully added to PeripheralManager a Central can subscribe to value updates. In addition to setting the new value an update notification must be sent. MutableCharacteristic provides the following methods to support notification updates,
// update with Data
public func update(withData value: Data) throws
// update with String
public func update(withString value: [String:String]) throws
// update with object supporting Deserializable
public func update<T: Deserializable>(_ value: T) throws {
// update with object supporting RawDeserializable
public func update<T: RawDeserializable>(_ value: T) throws
// update with object supporting RawArrayDeserializable
public func update<T: RawArrayDeserializable>(_ value: T) throws
// update with object supporting RawPairDeserializable
public func update<T: RawPairDeserializable>(_ value: T) throws
// update with object supporting RawArrayPairDeserializable
public func update<T: RawArrayPairDeserializable>(_ value: T) throwsAll methods throw if the MutableCharacteristic either has not been added to PeripheralManager or supports none of the notify CBCharacteristicProperties. Additionally update(withString:) will throw if the String value cannot be serialized. If the value is updated and there are no subscribers or the system CoreBluetooth update fails the update will be queued and sent when a Central subscribes or the system indicates that updates can continue. In addition to sending an update notification to a subscribing Central update sets the MutabaleCharacteristic value.
Peripheral applications would send notification updates using,
let updateStatus = try characteristic.updateValue(Enabled.no)If a MutableCharacteristic supports CBCharacteristicProperties.write a Central can change the MutableCharacteristic value. MutableCharacteristic supports the following methods supporting write requests,
// start processing write requests with specified stream capacity
public func startRespondingToWriteRequests(capacity: Int = Int.max) -> FutureStream<(request: CBATTRequestInjectable, central: CBCentralInjectable)>
// respond to received write request
public func respondToRequest(_ request: CBATTRequestInjectable, withResult result: CBATTError.Code)
// stop processing write requests
public func stopRespondingToWriteRequests()CBATTRequest encapsulates Central write requests, CBATTError encapsulates the response error code and a SimpleFutures.
PeripheralManager applications would start responding to Central writes requests using,
let writeResponseFuture = characteristic.startRespondingToWriteRequests(capacity: 10)
writeResponseFuture.onSuccess { [weak characteristic] (request, _) in
guard let characteristic = characteristic else {
throw AppError.unlikely
}
guard request.value.length == 1 else {
characteristic.respondToRequest(request, withResult:CBATTError.InvalidAttributeValueLength)
Return
}
characteristic.value = request.value
characteristic.respondToRequest(request, withResult:CBATTError.Success)
}Here the length of the Characteristic value is expected to be 1 byte.
PeripheralManager applications will stop responding to write requests using,
characteristic.stopProcessingWriteRequests()iBeacon emulation does not require MutableServices or MutableCharcteristics to be added to PeripheralManager. Only advertising is required. PeripheralManager provides the following methods supporting iBeacon advertisement,
// start advertising beceacon region
public func startAdvertising(_ region: BeaconRegion) -> Future<Void>
// stop advertising
public func stopAdvertising(timeout: TimeInterval = 10.0) -> Future<Void>Creation of a FutureLocation BeaconRegion is also required,
public convenience init(proximityUUID: UUID, identifier: String, major: UInt16, minor: UInt16, capacity: Int = Int.max)The BeaconRegion init parameters are,
| proximityUUID | The proximityUUID of the beacon targeted. |
| identifier | A unique identifier for region used by application. |
| major | The major value can be used to distinguish between different beacons with the same proximityUUID. |
| minor | The minor value can be used to distinguish between different beacons with the same proximityUUID and major value. |
A PeripheralManager application would use the flooring to advertise and iBeacon,
enum AppError: Error {
case invalidState
case resetting
case poweredOff
case unsupported
}
let manager = PeripheralManager(options: [CBPeripheralManagerOptionRestoreIdentifierKey : "us.gnos.BlueCap.peripheral-manager-documentation" as NSString])
let uuid = UUID(uuidString: "B9407F30-F5F8-466E-AFF9-25556B57FE6D")!
BeaconRegion(proximityUUID: uuid, identifier: "iBeacon", major: 1, minor: 1)
let startAdvertiseFuture = manager.whenStateChanges().flatMap { state -> Future<Void> in
guard let manager = manager else {
throw AppError.unlikely
}
switch state {
case .poweredOn:
return manager.startAdvertising(beaconRegion)
case .poweredOff:
throw AppError.poweredOff
case .unauthorized, .unknown:
throw AppError.invalidState
case .unsupported:
throw AppError.unsupported
case .resetting:
throw AppError.resetting
}
}
startAdvertiseFuture.onFailure { error in
switch error {
case AppError.poweredOff:
manager.reset()
case AppError.resetting:
manager.reset()
case AppError.unsupported:
break
default:
manager.reset()
}
_ = manager.stopAdvertising()
}See the Beacon Example for details.
CoreBluetooth provides state restoration for apps that have declared bluetooth-peripheral background execution permission. Apps with this permission can be restarted with a previous state if evicted from memory while in the background.
PeripheralManager provides the following method to process the restored application state,
public func whenStateRestored() -> Future<PeripheralAdvertisements>public enum PeripheralManagerError: Swift.Error {
// Thrown by startAdvertising if the PeripheralManager is already advertising
case isAdvertising
// Thrown is state restoration fails
case restoreFailed
// Thrown if the stop advertising timeout is exceeded
case stopAdvertisingTimeout
}
public enum MutableServiceError: Swift.Error {
// MutableService has no CBMutableService.
case unconfigured
}
public enum MutableCharacteristicError : Swift.Error {
// Thrown by startRespondingToWriteRequests and update if Mutablecharcteristic has not been added to a PeripheralManager
case unconfigured
// Thrown by update(withString:) if String Characteristic value cannot be serialized
case notSerializable
// Thrown by update if Characteristic notifiy or indicate property is not enabled
case notifyNotSupported
}