Skip to content

linebreak-co/BlueCap

 
 

Repository files navigation

BlueCap: Swifter CoreBluetooth

BlueCap provides a swift wrapper around CoreBluetooth and much more.

Features

  • A futures interface replacing protocol implementations.
  • Connection events for connect, disconnect and timeout.
  • Service scan timeout.
  • Characteristic read/write timeout.
  • A DSL for specification of GATT profiles.
  • Characteristic profile types encapsulating serialization and deserialization.
  • Example applications implementing Central and Peripheral.
  • A full featured extendable Central scanner and Peripheral emulator available in the App Store.
  • Comprehensive test coverage.

Requirements

  • iOS 8.0+
  • Xcode 7.0+

Installation

  1. Place the BlueCap somewhere in your project directory. You can either copy it or add it as a git submodule.
  2. Open the BlueCap project folder and drag BlueCapKit.xcodeproj into the project navigator of your applications Xcode project.
  3. Under your Projects Info tab set the iOS Deployment Target to 8.0 and verify that the BlueCapKit.xcodeproj iOS Deployment Target is also 8.0.
  4. Under the General tab for your project target add the top BlueCapKit.framework as an Embedded Binary.
  5. Under the Build Phases tab add BlueCapKit.framework as a Target Dependency and under Link Binary With Libraries add CoreLocation.framework and CoreBluetooth.framework.
  6. To enable debug log output select your project target and the Build Settings tab. Under Other Swift Flags and Debug add -D DEBUG.

Getting Started

With BlueCap it is possible to easily implement Central and Peripheral applications, serialize and deserialize messages exchanged with bluetooth devices and define reusable GATT profile definitions. This section will provide a short overview of the BLE model and simple application implementations. Following sections will describe all use cases supported in some detail. Example applications are also available.

BLE Model

Communication in BLE uses the Client-Server model. The Client is called a Central and the server a Peripheral. The Peripheral presents structured data to the Central organized into Services that have Characteristics. This called is a Generic Attribute Table (GATT) Profile. Characteristics contain the data values as well as attributes describing permissions and properties. At a high level Central and Peripheral use cases are,

Central Peripheral
Scans for Services Advertises Services
Discovers Services and Characteristics Supports GATT Profile
Data Read/Write/Notifications Data Read/Write/Notifications

Simple Central Implementation

A simple Central implementation that scans for Peripherals advertising a TI SensorTag Accelerometer Service, connects on peripheral discovery and then discovers the service characteristics is listed below,

public enum CentralExampleError : Int {
    case PeripheralNotConnected = 1
}

public struct CentralError {
    public static let domain = "Central Example"
    public static let peripheralNotConnected = NSError(domain:domain, code:CentralExampleError.PeripheralNotConnected.rawValue, userInfo:[NSLocalizedDescriptionKey:"Peripheral not connected"])
}
    
let serviceUUID = CBUUID(string:TISensorTag.AccelerometerService.uuid)!
                                
// on power, start scanning. when peripheral is discovered connect and stop scanning
let manager = CentralManager.sharedInstance
let peripheralConnectFuture = manager.powerOn().flatmap {_ -> FutureStream<Peripheral> in
	manager.startScanningForServiceUUIDs([serviceUUID], capacity:10)
}.flatmap {peripheral -> FutureStream<(Peripheral, ConnectionEvent)> in
	manager.stopScanning()
	return peripheral.connect(capacity:10, timeoutRetries:5, disconnectRetries:5)
}

// on .Timeout and .Disconnect try to reconnect on .Giveup terminate connection
peripheralConnectFuture.onSuccess{(peripheral, connectionEvent) in
	switch connectionEvent {
	case .Connect:
		
	case .Timeout:
		peripheral.reconnect()
	case .Disconnect:
		peripheral.reconnect()
	case .ForceDisconnect:
		
	case .Failed:
		
	case .GiveUp:
		peripheral.terminate()
	}
}
                
// discover services and characteristics
let peripheralDiscoveredFuture = peripheralConnectFuture.flatmap {(peripheral, connectionEvent) -> Future<Peripheral> in
	if peripheral.state == .Connected {
		return peripheral.discoverPeripheralServices([serviceUUID])
	} else {
		let promise = Promise<Peripheral>()
		promise.failure(CentralError.peripheralNotConnected)
		return promise.future
	}
}
peripheralDiscoveredFuture.onSuccess {peripheral in
	
}
peripheralDiscoveredFuture.onFailure {error in
	
}

Simple Peripheral Implementation

A simple Peripheral application that emulates a TI SensorTag Accelerometer Service with all characteristics and responds to characteristic writes is listed below,

// create service and characteristics using profile definitions
let accelerometerService = MutableService(profile:ConfiguredServiceProfile<TISensorTag.AccelerometerService>())
let accelerometerDataCharacteristic = MutableCharacteristic(profile:RawArrayCharacteristicProfile<TISensorTag.AccelerometerService.Data>())
let accelerometerEnabledCharacteristic = MutableCharacteristic(profile:RawCharacteristicProfile<TISensorTag.AccelerometerService.Enabled>())
let accelerometerUpdatePeriodCharacteristic = MutableCharacteristic(profile:RawCharacteristicProfile<TISensorTag.AccelerometerService.UpdatePeriod>())

// add characteristics to service
accelerometerService.characteristics = [accelerometerDataCharacteristic, accelerometerEnabledCharacteristic, accelerometerUpdatePeriodCharacteristic]

// respond to update period write requests
let accelerometerUpdatePeriodFuture = accelerometerUpdatePeriodCharacteristic.startRespondingToWriteRequests(capacity:2)
accelerometerUpdatePeriodFuture.onSuccess {request in
	if request.value.length > 0 &&  request.value.length <= 8 {
		accelerometerUpdatePeriodCharacteristic.value = request.value
		accelerometerUpdatePeriodCharacteristic.respondToRequest(request, withResult:CBATTError.Success)
	} else {
		accelerometerUpdatePeriodCharacteristic.respondToRequest(request, withResult:CBATTError.InvalidAttributeValueLength)
	}
}

// respond to enabled write requests
let accelerometerEnabledFuture = self.accelerometerEnabledCharacteristic.startRespondingToWriteRequests(capacity:2)
accelerometerEnabledFuture.onSuccess {request in  
	if request.value.length == 1 {
		accelerometerEnabledCharacteristic.value = request.value
		accelerometerEnabledCharacteristic.respondToRequest(request, withResult:CBATTError.Success)
	} else {
		accelerometerEnabledCharacteristic.respondToRequest(request, withResult:CBATTError.InvalidAttributeValueLength)
	}
}

// power on remove all services add service and start advertising
let manager = PeripheralManager.sharedInstance
let startAdvertiseFuture = manager.powerOn().flatmap {_ -> Future<Void> in
	manager.removeAllServices()
}.flatmap {_ -> Future<Void> in
	manager.addService(accelerometerService)
}.flatmap {_ -> Future<Void> in
	manager.startAdvertising(TISensorTag.AccelerometerService.name, uuids:[uuid])
}
startAdvertiseFuture.onSuccess {
	
}
startAdvertiseFuture.onFailure {error in
	
}

BlueCap supports many features that simplify writing Bluetooth LE applications. This section will describe all features in detail and provide code examples.

  1. Serialization/Deserialization: Serialization and deserialization of device messages.
  1. GATT Profile Definition: Define reusable GATT profiles and add profiles to the BlueCap app.
  1. CentralManager: The BlueCap CentralManager implementation replaces CBCentralManagerDelegate and CBPeripheralDelegate protocol implementations with a Scala Futures interface using SimpleFutures.
  1. PeripheralManager: The BlueCap PeripheralManager implementation replaces CBPeripheralManagerDelegate protocol implementations with a Scala Futures interface using SimpleFutures.

Serialization and deserialization of device messages requires protocol implementations. Then application objects can be converted to and from NSData objects using methods on Serde. Example implantations of each protocol can be found in the TiSensorTag GATT profile available in BlueCapKit and the following examples are implemented in a BlueCap Playground.

For Strings Serde serialize and deserialize are defined by,

// Deserialize Strings
public static func deserialize(data:NSData, encoding:NSStringEncoding = NSUTF8StringEncoding) -> String?

// Serialize Strings
public static func serialize(value:String, encoding:NSStringEncoding = NSUTF8StringEncoding) -> NSData?

NSStringEncoding supports many encodings.

to use in an application,

if let data = Serde.serialize("Test") {
    if let value = Serde.deserialize(data) {
        println(value)
    }
}

The Deserializable protocol is used to define deserialization of numeric objects and is defined by,

public protocol Deserializable {
    static var size : Int {get}
    static func deserialize(data:NSData) -> Self?
    static func deserialize(data:NSData, start:Int) -> Self?
    static func deserialize(data:NSData) -> [Self]
    init?(stringValue:String)
}

Description

size Size of object in bytes
deserialize(data:NSData) -> Self? Deserialize entire message to object
deserialize(data:NSData, start:Int) -> Self? Deserialize message starting at offset to object
deserialize(data:NSData) -> [Self] Deserialize entire message to array of objects
init?(stringValue:String) Create object from string

BlueCalKit provides implementation of Deserializable for UInt8, Int8, UInt16 and Int16. The Serde serialize and deserialize are defined by,

// Deserialize objects supporting Deserializable
public static func deserialize<T:Deserializable>(data:NSData) -> T?

// Serialize objects supporting Deserializable
public static func serialize<T:Deserializable>(value:T) -> NSData

For UInt8 data,

let data = Serde.serialize(UInt8(31))
if let value : UInt8 = Serde.deserialize(data) {
    println("\(value)")
}

The RawDeserializable protocol is used to define a message that contains a single value and is defined by,

public protocol RawDeserializable {
    typealias RawType
    static var uuid   : String  {get}
    var rawValue      : RawType {get}
    init?(rawValue:RawType)
}

Description

uuid Characteristic UUID
rawValue Characteristic RawType value
init?(rawValue:RawType) Create object from rawValue

The Serde serialize and deserialize are defined by,

// Deserialize objects supporting RawDeserializable
public static func deserialize<T:RawDeserializable where T.RawType:Deserializable>(data:NSData) -> T?

// Serialize objects supporting RawDeserializable
public static func serialize<T:RawDeserializable>(value:T) -> NSData

Note that RawType is required to be Deserializable to be deserialized. An Enum partially supports RawDeserializable, so,

enum Enabled : UInt8, RawDeserializable {
	case No  = 0
	case Yes = 1
	public static let uuid = "F000AA12-0451-4000-B000-000000000000"
}

and,

let data = Serde.serialize(Enabled.Yes)
if let value : Enabled = Serde.deserialize(data) {
    println("\(value.rawValue)")
}

RawDeserializable can also be implemented in a struct or class.

struct Value : RawDeserializable {
	let rawValue : UInt8
	static let uuid = "F000AA13-0451-4000-B000-000000000000"
	init?(rawValue:UInt8) {
	  self.rawValue = rawValue
	}
}

and,

if let initValue = Value(rawValue:10) {
    let data = Serde.serialize(initValue)
    if let value : Value = Serde.deserialize(data) {
        println(\(value.rawValue))
    }
}

The RawArrayDeserializable protocol is used to define a message that contains multiple values of a single type and is defined by,

public protocol RawArrayDeserializable {
    typealias RawType
    static var uuid   : String    {get}
    static var size   : Int       {get}
    var rawValue      : [RawType] {get}
    init?(rawValue:[RawType])
}

Description

uuid Characteristic UUID
size Size of array
rawValue Characteristic RawType values
init?(rawValue:[RawType]) Create object from rawValues

The Serde serialize and deserialize are defined by,

// Deserialize objects supporting RawArrayDeserializable
public static func deserialize<T:RawArrayDeserializable where T.RawType:Deserializable>(data:NSData) -> T?

// Serialize objects supporting RawArrayDeserializable
public static func serialize<T:RawArrayDeserializable>(value:T) -> NSData

Note that RawType is required to be Deserializable to be deserialized. RawArrayDeserializable can be implemented in a struct or class.

struct RawArrayValue : RawArrayDeserializable {    
    let rawValue : [UInt8]
    static let uuid = "F000AA13-0451-4000-B000-000000000000"
    static let size = 2
    
    init?(rawValue:[UInt8]) {
        if rawValue.count == 2 {
            self.rawValue = rawValue
        } else {
            return nil
        }
    }
}

and,

if let initValue = RawArrayValue(rawValue:[4,10]) {
    let data = Serde.serialize(initValue)
    if let value : RawArrayValue = Serde.deserialize(data) {
        println("\(value.rawValue)")
    }
}

The RawPairDeserializable is used to define a message that contains two values of different types and is defined by,

public protocol RawPairDeserializable {
    typealias RawType1
    typealias RawType2
    static var uuid : String   {get}
    var rawValue1   : RawType1 {get}
    var rawValue2   : RawType2 {get}
    init?(rawValue1:RawType1, rawValue2:RawType2)
}

Description

uuid Characteristic UUID
rawValue1 Characteristic RawType1 value
rawValue2 Characteristic RawType2 value
init?(rawValue1:RawType1, rawValue2:RawType2) Create object from rawValues

The Serde serialize and deserialize are defined by,

// Deserialize objects supporting RawPairDeserializable
public static func deserialize<T:RawPairDeserializable where T.RawType1:Deserializable, T.RawType2:Deserializable>(data:NSData) -> T?

// Serialize objects supporting RawPairDeserializable
public static func serialize<T:RawPairDeserializable>(value:T) -> NSData

Note that RawType1 and RawType2 are required to be Deserializable to be deserialized. RawPairDeserializable can be implemented in a struct or class.

struct RawPairValue : RawPairDeserializable {
    let rawValue1 : UInt8
    let rawValue2 : Int8
    static let uuid = "F000AA13-0451-4000-B000-000000000000"
    
    init?(rawValue1:UInt8, rawValue2:Int8) {
        self.rawValue1 = rawValue1
        self.rawValue2 = rawValue2
    }
}

and,

if let initValue = RawPairValue(rawValue1:10, rawValue2:-10) {
    let data = Serde.serialize(initValue)
    if let value : RawPairValue = Serde.deserialize(data) {
        println("\(value.rawValue1)")
        println("\(value.rawValue2)")
    }
}

The RawArrayPairDeserializable is used to define a message that contains multiple values of two different types and is defined by,

public protocol RawArrayPairDeserializable {
    typealias RawType1
    typealias RawType2
    static var uuid   : String     {get}
    static var size1  : Int        {get}
    static var size2  : Int        {get}
    var rawValue1     : [RawType1] {get}
    var rawValue2     : [RawType2] {get}
    init?(rawValue1:[RawType1], rawValue2:[RawType2])
}

Description

uuid Characteristic UUID
size1 Size of RawType1 array
size2 Size of RawType2 array
rawValue1 Characteristic RawType1 value
rawValue2 Characteristic RawType2 value
init?(rawValue1:[RawType1], rawValue2:[RawType2]) Create object from rawValues

The Serde serialize and deserialize are defined by,

// Deserialize objects supporting RawPairDeserializable
public static func deserialize<T:RawArrayPairDeserializable where T.RawType1:Deserializable,  T.RawType2:Deserializable>(data:NSData) -> T?

// Deserialize objects supporting RawPairDeserializable
public static func serialize<T:RawArrayPairDeserializable>(value:T) -> NSData

Note that RawType1 and RawType2 are required to be Deserializable to be deserialized. RawArrayPairDeserializable can be implemented in a struct or class.

struct RawArrayPairValue : RawArrayPairDeserializable {
    let rawValue1 : [UInt8]
    let rawValue2 : [Int8]
    static let uuid = "F000AA13-0451-4000-B000-000000000000"
    static let size1 = 2
    static let size2 = 2
    
    init?(rawValue1:[UInt8], rawValue2:[Int8]) {
        if rawValue1.count == 2 && rawValue2.count == 2 {
            self.rawValue1 = rawValue1
            self.rawValue2 = rawValue2
        } else {
            return nil
        }
    }
}

and,

if let initValue = RawArrayPairValue(rawValue1:[10, 100], rawValue2:[-10, -100]) {
    let data = Serde.serialize(initValue)
    if let value : RawArrayPairValue = Serde.deserialize(data) {
        println("\(value.rawValue1)")
        println("\(value.rawValue2)")
    }
}

GATT profile definitions are required to add support for a device to the BlueCap app but are not required to build a functional application using the framework. Implementing a GATT profile for a device allows the framework to automatically identify and configure Services and Characteristics and provides serialization and deserialization of Characteristic values to and from Strings. The examples in this section are also available in a BlueCap Playground

The ServiceConfigurable protocol is used to specify Service configuration and is defined by,

public protocol ServiceConfigurable {
    static var name  : String {get}
    static var uuid  : String {get}
    static var tag   : String {get}
}

Description

name Service name
uuid Service UUID
tag Used to organize services in the BlueCap app profile browser

The CharacteristicConfigurable is used to specify Characteristic configuration and is defined by,

public protocol CharacteristicConfigurable {
    static var name          : String {get}
    static var uuid          : String {get}
    static var permissions   : CBAttributePermissions {get}
    static var properties    : CBCharacteristicProperties {get}
    static var initialValue  : NSData? {get}
}

Description

name Characteristic name
uuid Characteristic UUID
permissions CBAttributePermissions
properties CBCharacteristicProperties
initialValue Characteristic initial value

The StringDeserializable protocol is used to specify conversion of rawValues to Strings and is defined by,

public protocol StringDeserializable {
    static var stringValues : [String] {get}
    var stringValue         : [String:String] {get}
    init?(stringValue:[String:String])
}

Description

stringValues Used for enums to specify Strings for values but ignored for other types
stringValue The String values of the rawType
init?(stringValue:[String:String]) Create object from stringValue

A ConfiguredServiceProfile object encapsulates a service configuration and can be used to instantiate either Service or MutableService objects.

struct AccelerometerService : ServiceConfigurable  {
  static let uuid  = "F000AA10-0451-4000-B000-000000000000"
  static let name  = "TI Accelerometer"
  static let tag   = "TI Sensor Tag"
}
let serviceProfile = ConfiguredServiceProfile<AccelerometerService>() 

The CharacteristicProfiles belonging to a ServiceProfile are added using the method,

public func addCharacteristic(characteristicProfile:CharacteristicProfile)

CharacteristicProfile is the base class for each of the following profile types and is instantiated as the characteristic profile if a profile is not explicitly defined for a discovered Characteristic. In this case, with no String conversions implemented in a GATT Profile definition, a Characteristic will support String conversions to a from hexadecimal Strings.

When defining a GATT profile it is sometimes convenient to specify that something be done after a Characteristic is discovered by a Central.

public func afterDiscovered(capacity:Int?) -> FutureStream<Characteristic>

A RawCharacteristicProfile object encapsulates configuration and String conversions for a Characteristic implementing RawDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects.

enum Enabled : UInt8, RawDeserializable, StringDeserializable, CharacteristicConfigurable {
  case No     = 0
  case Yes    = 1

  // CharacteristicConfigurable
  static let uuid = "F000AA12-0451-4000-B000-000000000000"
  static let name = "Accelerometer Enabled"
  static let properties = CBCharacteristicProperties.Read | CBCharacteristicProperties.Write
  static let permissions = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
  static let initialValue : NSData? = Serde.serialize(Enabled.No.rawValue)
    
  // StringDeserializable
  static let stringValues = ["No", "Yes"]
    
  init?(stringValue:[String:String]) {
    if let value = stringValue[Enabled.name] {
      switch value {
      case "Yes":
        self = Enabled.Yes
      case "No":
        self = Enabled.No
      default:
        return nil
      }
    } else {
      return nil
    }
  }
    
  var stringValue : [String:String] {
    switch self {
      case .No:
        return [Enabled.name:"No"]
      case .Yes:
        return [Enabled.name:"Yes"]
    }
  }
}

To instantiate a profile in an application,

let profile = RawCharacteristicProfile<Enabled>()

A RawArrayCharacteristicProfile object encapsulates configuration and String conversions for a characteristic implementing RawArrayDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects.

struct ArrayData : RawArrayDeserializable, CharacteristicConfigurable, StringDeserializable {
  // CharacteristicConfigurable
  static let uuid = "F000AA11-0451-4000-B000-000000000000"
  static let name = "Accelerometer Data"
  static let properties = CBCharacteristicProperties.Read | CBCharacteristicProperties.Notify
  static let permissions = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
  static let initialValue : NSData? = Serde.serialize(ArrayData(rawValue:[1,2])!)
    
  // RawArrayDeserializable
  let rawValue : [Int8]
  static let size = 2
    
  init?(rawValue:[Int8]) {
    if rawValue.count == 2 {
      self.rawValue = rawValue
    } else {
      return nil
    }
  }
    
  // StringDeserializable
  static let stringValues = [String]()
    
  var stringValue : Dictionary<String,String> {
    return ["value1":"\(self.rawValue[0])",
            "value2":"\(self.rawValue[1])"]
  }
    
  init?(stringValue:[String:String]) {
    if  let stringValue1 = stringValue["value1"],
            stringValue2 = stringValue["value2"],
            value1 = Int8(stringValue:stringValue1),
            value2 = Int8(stringValue:stringValue2) {
      self.rawValue = [value1, value2]
    } else {
      return nil
    }
  }
}

To instantiate a profile in an application,

let profile = RawArrayCharacteristicProfile<ArrayData>()

A RawPairCharacteristicProfile object encapsulates configuration and String conversions for a characteristic implementing RawPairDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects.

struct PairData : RawPairDeserializable, CharacteristicConfigurable, StringDeserializable {    
  // CharacteristicConfigurable
  static let uuid = "F000AA30-0451-4000-B000-000000000000"
  static let name = "Magnetometer Data"
  static let properties = CBCharacteristicProperties.Read | CBCharacteristicProperties.Notify
  static let permissions = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
  static let initialValue : NSData? = Serde.serialize(PairData(rawValue1:10, rawValue2:-10)!)
    
  // RawPairDeserializable
  let rawValue1 : UInt8
  let rawValue2 : Int8
    
  init?(rawValue1:UInt8, rawValue2:Int8) {
    self.rawValue1 = rawValue1
    self.rawValue2 = rawValue2
  }
    
  // StringDeserializable
  static let stringValues = [String]()
    
  var stringValue : Dictionary<String,String> {
    return ["value1":"\(self.rawValue1)",
            "value2":"\(self.rawValue2)"]}
    
  init?(stringValue:[String:String]) {
    if  let stringValue1 = stringValue["value1"],
            stringValue2 = stringValue["value2"],
            value1 = UInt8(stringValue:stringValue1),
            value2 = Int8(stringValue:stringValue2) {
      self.rawValue1 = value1
      self.rawValue2 = value2
    } else {
      return nil
    }
  }            
}

To instantiate a profile in an application,

let profile = RawPairCharacteristicProfile<PairData>()

A RawArrayPairCharacteristicProfile object encapsulates configuration and String conversions for a characteristic implementing RawArrayPairDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects.

struct ArrayPairData : RawArrayPairDeserializable, CharacteristicConfigurable, StringDeserializable {    
  // CharacteristicConfigurable
  static let uuid = "F000AA11-0451-4000-B000-000000000000"
  static let name = "Accelerometer Data"
  static let properties = CBCharacteristicProperties.Read | CBCharacteristicProperties.Notify
  static let permissions = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
static let initialValue : NSData? = Serde.serialize()
            
	// RawArrayPairDeserializable
	let rawValue1 : [UInt8]
	let rawValue2 : [Int8]
	static let uuid = "F000AA13-0451-4000-B000-000000000000"
	static let size1 = 2
	static let size2 = 2

	init?(rawValue1:[UInt8], rawValue2:[Int8]) {
	  if rawValue1.count == 2 && rawValue2.count == 2 {
	     self.rawValue1 = rawValue1
	     self.rawValue2 = rawValue2
	  } else {
      return nil
	  }
	}
            
	// StringDeserializable
	static let stringValues = [String]()
            
	var stringValue : Dictionary<String,String> {
	  return ["value11":"\(self.rawValue1[0])",
            "value12":"\(self.rawValue1[1])"],
            "value21":"\(self.rawValue2[0])",
            "value22":"\(self.rawValue2[1])"]}

  init?(stringValue:[String:String]) {
	  if  let stringValue11 = stringValue["value11"], 
				 	  stringValue12 = stringValue["value12"]
            value11 = Int8(stringValue:stringValue11),
					  value12 = Int8(stringValue:stringValue12),
					  stringValue21 = stringValue["value21"], 
					  stringValue22 = stringValue["value22"]
            value21 = Int8(stringValue:stringValue21),
					  value22 = Int8(stringValue:stringValue22) {
        self.rawValue1 = [value11, value12]
        self.rawValue2 = [value21, value22]
    } else {
        return nil
    }
  }            
}

To instantiate a profile in an application,

let profile = RawArrayPairCharacteristicProfile<ArrayPairData>()

A String Profile only requires the implementation of CharacteristicConfigurable

struct SerialNumber : CharacteristicConfigurable {
  // CharacteristicConfigurable
  static let uuid = "2a25"
  static let name = "Device Serial Number"
  static let permissions  = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
  static let properties   = CBCharacteristicProperties.Read
  static let initialValue = Serde.serialize("AAA11")          
}

To instantiate a profile in an application,

let profile = StringCharacteristicProfile<SerialNumber>()

ProfileManager is used by the BlueCap app as a repository of GATT profiles to be used to instantiate Services and Characteristics. ProfileManager can be used in an implementation but is not required.

To add ServiceProfiles and CharacteristicProfiles to ProfileManager,

let profileManager = ProfileManager.sharedInstance

let serviceProfile = ConfiguredServiceProfile<AccelerometerService>()

let enabledProfile = RawCharacteristicProfile<Enabled>()
let rawArrayProfile = RawArrayCharacteristicProfile<ArrayData>()

serviceProfile.addCharacteristic(enabledProfile)
serviceProfile.addCharacteristic(rawArrayProfile)

profileManager.addService(serviceProfile)

To add a GATT Profile to the BlueCap app you need to add a file to the project containing all Service and Characteristic profile definitions with public access level. See GnosusProfiles in the BlueCap Project fro an example. A very simple but illustrative example is to consider a Service with a single Characteristic.

public struct MyServices {
    
    // Service
    public struct NumberService : ServiceConfigurable  {
        public static let uuid  = "F000AA10-0451-4000-B000-000000000000"
        public static let name  = "NumberService"
        public static let tag   = "My Services"
    }
    
    // Characteristic
    public struct Number : RawDeserializable, StringDeserializable, CharacteristicConfigurable {
        
        public let rawValue : Int16
        
        public init?(rawValue:Int16) {
            self.rawValue = rawValue
        }
        
        public static let uuid = "F000AA12-0451-4000-B000-000000000000"
        public static let name = "Number"
        public static let properties = CBCharacteristicProperties.Read | CBCharacteristicProperties.Write
        public static let permissions = CBAttributePermissions.Readable | CBAttributePermissions.Writeable
        public static let initialValue : NSData? = Serde.serialize(Int16(22))
        
        public static let stringValues = [String]()
        
        public init?(stringValue:[String:String]) {
            if let svalue = stringValue[Number.name], value = Int16(stringValue:svalue) {
                self.rawValue = value
            } else {
                return nil
            }
        }
        
        public var stringValue : [String:String] {
            return [Number.name:"\(self.rawValue)"]
        }
    }
    
    // add to ProfileManager
    public static func create() {
        let profileManager = ProfileManager.sharedInstance
        let service = ConfiguredServiceProfile<NumberService>()
        let characteristic = RawCharacteristicProfile<Number>()
        service.addCharacteristic(characteristic)
        profileManager.addService(service)
    }
    
}

Next place,

MyServices.create()

in the BlueCap AppDelegate.swift and rebuild the app.

The BlueCap CentralManager implementation replaces CBCentralManagerDelegate and CBPeripheralDelegate protocol implementations with with a Scala Futures interface using SimpleFutures. Futures provide inline implementation of asynchronous callbacks and allow chaining asynchronous calls as well as error handling and recovery. Also, provided are callbacks for connection events and connection and service scan timeouts. This section will describe interfaces and give example implementations for all supported use cases. Simple example applications can be found in the BlueCap project.

The state of the Bluetooth transceiver on a device is communicated to BlueCap CentralManager by the powerOn and powerOff futures,

public func powerOn() -> Future<Void>
public func powerOff() -> Future<Void>

Both methods return a SimpleFutures Future<Void>. For an application to process events,

let manager = CentralManager.sharedInstance
let powerOnFuture = manager.powerOn()
powerOnFuture.onSuccess {
  
}
let powerOffFuture = manager.powerOff()
powerOffFuture.onSuccess {
	
}

When CentralManager is instantiated a message giving the current Bluetooth transceiver state is received and while the CentralManager is instantiated messages are received if the transceiver is powered or powered off.

Central scans for advertising peripherals are initiated by calling the BlueCap CentralManager methods,

// Scan promiscuously for all advertising peripherals
public func startScanning(capacity:Int? = nil) -> FutureStream<Peripheral>

// Scan for peripherals advertising services with UUIDs
public func startScanningForServiceUUIDs(uuids:[CBUUID]!, capacity:Int? = nil) -> FutureStream<Peripheral>

Both methods return a SimpleFutures FutureStream<Peripheral> yielding the discovered Peripheral and take the FutureStream capacity as input.

For an application to scan for Peripherals advertising Services with uuids after powerOn,

let manager = CentralManager.sharedInstance
let serviceUUID = CBUUID(string:"F000AA10-0451-4000-B000-000000000000")!

let peripheraDiscoveredFuture = manager.powerOn().flatmap {_ -> FutureStream<Peripheral> in
	manager.startScanningForServiceUUIDs([serviceUUID], capacity:10)
}
peripheraDiscoveredFuture.onSuccess {peripheral in
	
}

Here the powerOn future has been flatmapped to startScanning(capacity:Int?) -> FutureStream<Peripheral> to ensure that the service scan starts after the bluetooth transceiver is powered on.

To stop a peripheral scan use the CentralManager method,

public func stopScanning()

and in an application,

let manager = CentralManager.sharedInstance
manager.stopScanning()

BlueCap CentralManager can scan for advertising peripherals with a timeout. TimedScannerator methods are used to start a scan instead ob the CentralManager methods. The declarations include a timeout parameter but are otherwise the same,

// Scan promiscuously for all advertising peripherals
public func startScanning(timeoutSeconds:Double, capacity:Int? = nil) -> FutureStream<Peripheral>

// Scan for peripherals advertising services with UUIDs
public func startScanningForServiceUUIDs(timeoutSeconds:Double, uuids:[CBUUID]!, capacity:Int? = nil) -> FutureStream<Peripheral>

Both methods return a SimpleFutures FutureStream<Peripheral> yielding the discovered peripheral and take the FutureStream capacity as input.

For an application to scan for Peripherals advertising Services with UUIDs and a specified timeout after powerOn,

let manager = CentralManager.sharedInstance
let serviceUUID = CBUUID(string:"F000AA10-0451-4000-B000-000000000000")!

let peripheraDiscoveredFuture = manager.powerOn().flatmap {_ -> FutureStream<Peripheral> in
	TimedScannerator.sharedinstance.startScanningForServiceUUIDs(10.0, uuids:[serviceUUID], capacity:10)
}
peripheraDiscoveredFuture.onSuccess {peripheral in
	
}
peripheraDiscoveredFuture.onFailure {error in
	
}

Here the powerOn future has been flatmapped to startScanning(capacity:Int?) -> FutureStream<Peripheral> to ensure that the service scan starts after the bluetooth transceiver is powered on. On timeout peripheraDiscoveredFuture will complete with error BCError.peripheralDiscoveryTimeout.

To stop a peripheral scan use the TimedScannerator method,

public func stopScanning()

and in an application,

TimedScannerator.sharedInstance.stopScanning()

Peripheral advertisements are can be obtained using the following Peripheral properties,

// Local peripheral name with key CBAdvertisementDataLocalNameKey
public var advertisedLocalName : String? 

// Manufacture data with key CBAdvertisementDataManufacturerDataKey    
public var advertisedManufactuereData : NSData? 

// Tx power with with key CBAdvertisementDataTxPowerLevelKey
public var advertisedTxPower : NSNumber? 

// Is connectable with key CBAdvertisementDataIsConnectable
public var advertisedIsConnectable : NSNumber? 
    
// Advertised service UUIDs with key CBAdvertisementDataServiceUUIDsKey
public var advertisedServiceUUIDs : [CBUUID]? 

// Advertised service data with key CBAdvertisementDataServiceDataKey
public var advertisedServiceData : [CBUUID:NSData]? 

// Advertised overflow services with key CBAdvertisementDataOverflowServiceUUIDsKey
public var advertisedOverflowServiceUUIDs : [CBUUID]? 

// Advertised solicited services with key CBAdvertisementDataSolicitedServiceUUIDsKey
public var advertisedSolicitedServiceUUIDs : [CBUUID]? 

After discovering a peripheral a connection must be established to begin messaging. Connecting and maintaining a connection to a bluetooth device can be difficult since signals are weak and devices may have relative motion. BlueCap provides connection events to enable applications to easily handle anything that can happen. ConnectionEvent is an enum with values,

Event Description
Connect Connected to peripheral
Timeout Connection attempt timeout
Disconnect Peripheral disconnected
ForceDisconnect Peripheral disconnected by application
Failed Connection failed without error
GiveUp Give-up trying to connect.

To connect to a peripheral use The BlueCap Peripheral method,

public func connect(capacity:Int? = nil, timeoutRetries:UInt? = nil, disconnectRetries:UInt? = nil, connectionTimeout:Double = 10.0) -> FutureStream<(Peripheral, ConnectionEvent)>

Discussion

BlueCap Peripheral connect returns a SimpleFutures FutureStream<(Peripheral, ConnectionEvent)> yielding a tuple containing the connected Peripheral and the ConnectionEvent.

capacity FutureStream capacity
timeoutRetries Number of connection retries on timeout. Equals 0 if nil.
disconnectRetries Number of connection retries on disconnect. Equals 0 if nil.
connectionTimeout Connection timeout in seconds. Default is 10s.

Other BlueCap Peripheral connection management methods are,

// Reconnect peripheral if disconnected
public func reconnect()

// Disconnect peripheral
public func disconnect()

// Terminate peripheral
public func terminate()

An application can connect a Peripheral using,

let manager = CentralManager.sharedInstance
let serviceUUID = CBUUID(string:"F000AA10-0451-4000-B000-000000000000")!

let peripheralConnectFuture = manager.powerOn().flatmap {_ -> FutureStream<Peripheral> in
	manager.startScanningForServiceUUIDs([serviceUUID], capacity:10)
}.flatmap{peripheral -> FutureStream<(Peripheral, ConnectionEvent)> in
	return peripheral.connect(capacity:10, timeoutRetries:5, disconnectRetries:5, connectionTimeout:10.0)
}
peripheralConnectFuture.onSuccess {(peripheral, connectionEvent) in
	switch connectionEvent {
  case .Connect:
	  
  case .Timeout:
    peripheral.reconnect()
		
  case .Disconnect:
    peripheral.reconnect()
		
  case .ForceDisconnect:
	  
  case .Failed:
	  
  case .GiveUp:
	  peripheral.terminate()
		
  }
}
peripheralConnectFuture.onFailure {error in
	
}

Here the peripheraDiscoveredFuture from the previous section is flatmapped to connect(capacity:Int? = nil, timeoutRetries:UInt, disconnectRetries:UInt?, connectionTimeout:Double) -> FutureStream<(Peripheral, ConnectionEvent)> to ensure that connections are made after Peripherals are discovered. When ConnectionEvents of .Timeout and .Disconnect are received an attempt is made to reconnect the Peripheral. The connection is configured for a maximum of 5 timeout retries and 5 disconnect retries. If either of these thresholds is exceeded a .GiveUp event is received and the Peripheral connection is terminated ending all reconnection attempts.

After a Peripheral is connected its Services and Characteristics must be discovered before Characteristic values can be read or written to or update notifications can be received.

There are several BlueCap Peripheral methods that can be used to discover Services and Characteristics.

// Discover services and characteristics for services with UUIDs
public func discoverPeripheralServices(services:[CBUUID]!) -> Future<Peripheral>

// Discover all services and characteristics supported by peripheral
public func discoverAllPeripheralServices() -> Future<Peripheral>

Both methods return a SimpleFutures Future<Peripheral> yielding the connected Peripheral.

An application can discover a Peripheral using,

// errors
public enum ApplicationErrorCode : Int {
    case PeripheralNotConnected = 1
}

public struct ApplicationError {
    public static let domain = "Application"
    public static let peripheralNotConnected = NSError(domain:domain, code:ApplicationErrorCode.PeripheralNotConnected.rawValue, userInfo:[NSLocalizedDescriptionKey:"Peripheral not connected"])
}

// peripheralConnectFuture and serviceUUID are defined in previous section

let characteristicsDiscoveredFuture = peripheralConnectFuture.flatmap {(peripheral, connectionEvent) -> Future<Peripheral> in
	if peripheral.state == .Connected {
	  return peripheral.discoverPeripheralServices([serviceUUID])
	} else {
	  let promise = Promise<Peripheral>()
    promise.failure(ApplicationError.peripheralNotConnected)
    return promise.future
  }
}
characteristicsDiscoveredFuture.onSuccess {peripheral in
	
}
characteristicsDiscoveredFuture.onFailure {error in
	
}

Here the peripheralConnectFuture from the previous section is flatmapped to discoverPeripheralServices(services:[CBUUID]!) -> Future<Peripheral> to ensure that the Peripheral is connected before Service and Characteristic discovery starts. Also, the Peripheral is discovered only if it is connected and an error is returned if the Peripheral is not connected.

After a Peripherals Characteristics are discovered writing Characteristic values is possible. Many BlueCap Characteristic methods are available,

// Write an NSData object to characteristic value
public func writeData(value:NSData, timeout:Double = 10.0) -> Future<Characteristic>

// Write a characteristic String Dictionary value
public func writeString(stringValue:[String:String], timeout:Double = 10.0) -> Future<Characteristic>

// Write a Deserializable characteristic value
public func write<T:Deserializable>(value:T, timeout:Double = 10.0) -> Future<Characteristic>

// Write a RawDeserializable characteristic value
public func write<T:RawDeserializable>(value:T, timeout:Double = 10.0) -> Future<Characteristic>

// Write a RawArrayDeserializable characteristic value
public func write<T:RawArrayDeserializable>(value:T, timeout:Double = 10.0) -> Future<Characteristic>

// Write a RawPairDeserializable characteristic value
public func write<T:RawPairDeserializable>(value:T, timeout:Double = 10.0) -> Future<Characteristic>

// Write a RawArrayPairDeserializable characteristic value
public func write<T:RawArrayPairDeserializable>(value:T, timeout:Double = 10.0) -> Future<Characteristic>

Using the RawDeserializable enum an application can write a BlueCap Characteristic as follows,

// errors
public enum ApplicationErrorCode : Int {
    case CharacteristicNotFound = 1
}

public struct ApplicationError {
    public static let domain = "Application"
    public static let characteristicNotFound = NSError(domain:domain, code:ApplicationErrorCode.CharacteristicNotFound.rawValue, userInfo:[NSLocalizedDescriptionKey:"Characteristic Not Found"])
}

// RawDeserializable enum
enum Enabled : UInt8, RawDeserializable {
    case No  = 0
    case Yes = 1
    public static let uuid = "F000AA12-0451-4000-B000-000000000000"
}
let enabledUUID = CBUUID(string:Enabled.uuid)!

// characteristicsDiscoveredFuture and serviceUUID are defined in a previous section

let writeCharacteristicFuture = characteristicsDiscoveredFuture.flatmap {peripheral -> Future<Characteristic> in
	if let service = peripheral.service(serviceUUID), characteristic = service.characteristic(enabledUUID) {
		return characteristic.write(Enabled.Yes, timeout:20.0)
	} else {
		let promise = Promise<Characteristic>()
		promise.failure(ApplicationError.characteristicNotFound)
		return promise.future
	}
}
writeCharacteristicFuture.onSuccess {characteristic in
	
}
writeCharacteristicFuture.onFailure {error in
	
}

Here the characteristicsDiscoveredFuture previously defined is flatmapped to write<T:RawDeserializable>(value:T, timeout:Double) -> Future<Characteristic> to ensure that characteristic has been discovered before writing. An error is returned if the characteristic is not found.

After a Peripherals Characteristics are discovered reading Characteristic values is possible. Many BlueCap Characteristic methods are available,

// Read a characteristic from a peripheral service
public func read(timeout:Double = 10.0) -> Future<Characteristic>

// Return the characteristic value as and NSData object
public var dataValue : NSData!

// Return the characteristic value as a String Dictionary.
public var stringValue :[String:String]?

// Return a Deserializable characteristic value
public func value<T:Deserializable>() -> T?

// Return a RawDeserializable characteristic value
public func value<T:RawDeserializable where T.RawType:Deserializable>() -> T?

// Return a RawArrayDeserializable characteristic value
public func value<T:RawArrayDeserializable where T.RawType:Deserializable>() -> T?

// Return a RawPairDeserializable characteristic value
public func value<T:RawPairDeserializable where T.RawType1:Deserializable, T.RawType2:Deserializable>() -> T?

Using the RawDeserializable enum an application can read a BlueCap Characteristic as follows,

// errors
public enum ApplicationErrorCode : Int {
    case CharacteristicNotFound = 1
}

public struct ApplicationError {
    public static let domain = "Application"
    public static let characteristicNotFound = NSError(domain:domain, code:ApplicationErrorCode.CharacteristicNotFound.rawValue, userInfo:[NSLocalizedDescriptionKey:"Characteristic Not Found"])
}

// RawDeserializable enum
enum Enabled : UInt8, RawDeserializable {
    case No  = 0
    case Yes = 1
    public static let uuid = "F000AA12-0451-4000-B000-000000000000"
}
let enabledUUID = CBUUID(string:Enabled.uuid)!

// characteristicsDiscoveredFuture and serviceUUID 
// are defined in a previous section

let readCharacteristicFuture = characteristicsDiscoveredFuture.flatmap {peripheral -> Future<Characteristic> in
	if let service = peripheral.service(serviceUUID), characteristic = service.characteristic(enabledUUID) {
		return characteristic.read(timeout:20.0)
	} else {
		let promise = Promise<Characteristic>()
		promise.failure(ApplicationError.characteristicNotFound)
		return promise.future
	}
}
writeCharacteristicFuture.onSuccess {characteristic in
	if let value : Enabled = characteristic.value {
		
	}
}
writeCharacteristicFuture.onFailure {error in
	
}

Here the characteristicsDiscoveredFuture previously defined is flatmapped to read(timeout:Double) -> Future<Characteristic> to ensure that characteristic has been discovered before reading. An error is returned if the characteristic is not found.

After a Peripherals Characteristics are discovered subscribing to Characteristic value update notifications is possible. Several BlueCap Characteristic methods are available,

// subscribe to characteristic update
public func startNotifying() -> Future<Characteristic>

// receive characteristic value updates
public func receiveNotificationUpdates(capacity:Int? = nil) -> FutureStream<Characteristic>

// unsubscribe from characteristic updates
public func stopNotifying() -> Future<Characteristic>

// stop receiving characteristic value updates
public func stopNotificationUpdates()

Using the RawDeserializable enum an application can receive notifications from a BlueCap Characteristic as follows,

// errors
public enum ApplicationErrorCode : Int {
    case CharacteristicNotFound = 1
}

public struct ApplicationError {
    public static let domain = "Application"
    public static let characteristicNotFound = NSError(domain:domain, code:ApplicationErrorCode.CharacteristicNotFound.rawValue, userInfo:[NSLocalizedDescriptionKey:"Characteristic Not Found"])
}

// RawDeserializable enum
enum Enabled : UInt8, RawDeserializable {
    case No  = 0
    case Yes = 1
    public static let uuid = "F000AA12-0451-4000-B000-000000000000"
}
let enabledUUID = CBUUID(string:Enabled.uuid)!

// characteristicsDiscoveredFuture and serviceUUID are defined in a previous section

let subscribeCharacteristicFuture = characteristicsDiscoveredFuture.flatmap {peripheral -> Future<Characteristic> in
	if let service = peripheral.service(serviceUUID), characteristic = service.characteristic(enabledUUID) {
		return characteristic.startNotifying()
	} else {
		let promise = Promise<Characteristic>()
		promise.failure(ApplicationError.characteristicNotFound)
		return promise.future
	}
}
subscribeCharacteristicFuture.onSuccess {characteristic in
	
}
subscribeCharacteristicFuture.onFailure {error in
	
}

let updateCharacteristicFuture = subscribeCharacteristicFuture.flatmap{characteristic -> FutureStream<Characteristic> in
	return characteristic.receiveNotificationUpdates(capacity:10)
}
updateCharacteristicFuture.onSuccess {characteristic in
	if let value : Enabled = characteristic.value {
		
	}
}
updateCharacteristicFuture.onFailure {error in 
}

Here the characteristicsDiscoveredFuture previously defined is flatmapped to startNotifying() -> Future<Characteristic> to ensure that characteristic has been discovered before subscribing to updates. An error is returned if the characteristic is not found. Then updateCharacteristicFuture is flatmapped again to receiveNotificationUpdates(capacity:Int?) -> FutureStream<Characteristic> to ensure that the subsections is completed before receiving updates.

For an application to unsubscribe to Characteristic value updates and stop receiving updates,

// serviceUUID and enabledUUID are define in the example above
if let service = peripheral.service(serviceUUID), characteristic = service.characteristic(enabledUUID) {
	
	// stop receiving updates
	characteristic.stopNotificationUpdates()

	// unsubscribe to notifications
	characteristic.stopNotifying()
}

The BlueCap PeripheralManager implementation replaces CBPeripheralManagerDelegate protocol implementations with with a Scala Futures interface using SimpleFutures. Futures provide inline implementation of asynchronous callbacks and allows chaining asynchronous calls as well as error handling and recovery. This section will describe interfaces and give example implementations for all supported use cases. Simple example applications can be found in the BlueCap github repository.

The state of the Bluetooth transceiver on a device is communicated to BlueCap PeripheralManager by the powerOn and powerOff futures,

public func powerOn() -> Future<Void>
public func powerOff() -> Future<Void>

Both methods return a SimpleFutures Future yielding Void. For an application to process events,

let manager = PeripheralManager.sharedInstance
let powerOnFuture = manager.powerOn()
powerOnFuture.onSuccess {
  
}
let powerOffFuture = manager.powerOff()
powerOffFuture.onSuccess {
	
}

When PeripheralManager is instantiated a message giving the current bluetooth transceiver state is received and while the PeripheralManager is instantiated messages are received if the transceiver is powered or powered off.

Services and characteristics are added to a peripheral application before advertising. The BlueCap PeripheralManager methods used for managing services are,

// add a single service
public func addService(service:MutableService) -> Future<Void>

// add multiple services
public func addServices(services:[MutableService]) -> Future<Void>

// remove a service
public func removeService(service:MutableService) -> Future<Void>

// remove all services
public func removeAllServices() -> Future<Void>

All methods return a SimpleFutures Future<Void>. The methods can only be used before PeripheralManager begins advertising.

The BlueCap MutableService methods are,

// add characteristics
public var characteristics : [MutableCharacteristic] {get set}

// create characteristics from profiles
public func characteristicsFromProfiles(profiles:[CharacteristicProfile])

A Peripheral application will add Services and Characteristics using,

// service UUId and characteristic value definition
let serviceUUID = CBUUID(string:"F000AA10-0451-4000-B000-000000000000")
enum Enabled : UInt8, RawDeserializable {
    case No  = 0
    case Yes = 1
    public static let uuid = "F000AA12-0451-4000-B000-000000000000"
}

// create service and characteristic
let service = MutableService(uuid:serviceUUID)
let characteristic = MutableCharacteristic(uuid:Enabled.uuid,                                            properties:CBCharacteristicProperties.Read|CBCharacteristicProperties.Write,                                                 permissions:CBAttributePermissions.Readable|CBAttributePermissions.Writeable,                                                value:Serde.serialize(Enabled.No)))

// add characteristics to service 
service.characteristics = [characteristic]

// add service to peripheral
let manager = PeripheralManager.sharedInstance
let addServiceFuture = manager.powerOn().flatmap {_ -> Future<Void> in
	manager.removeAllServices()
}.flatmap {_ -> Future<Void> in
	manager.addService(service)
}

addServiceFuture.onSuccess {
	
}
addServiceFuture.onFailure {error in
	
}

First BlueCap MutableServices and MutableCharacteristics are created and CBCharacteristicProperties and CBAttributePermissions are specified. The Characteristic is then added to the Service. Then the PeripheralManager powerOn() -> Future<Void> is flatmapped to removeAllServices() -> Future<Void> which is then flatmapped to addServices(services:[MutableService]) -> Future<Void>. This sequence ensures that the Peripheral is powered and with no services before the new services are added.

If Service and Characteristic GATT profile definitions are available creating Services and Characteristics is a little simpler,

let  service = MutableService(profile:ConfiguredServiceProfile<TISensorTag.AccelerometerService>())
let characteristic = MutableCharacteristic(profile:RawCharacteristicProfile<TISensorTag.AccelerometerService.Enabled>())

Here the BlueCap the TiSensorTag GATT profile was used.

After services and characteristics have been added the peripheral is ready to begin advertising using the methods,

// start advertising with name and services
public func startAdvertising(name:String, uuids:[CBUUID]?) -> Future<Void>

// start advertising with name and no services
public func startAdvertising(name:String) -> Future<Void> 

// stop advertising
public func stopAdvertising() -> Future<Void>

All methods return a SimpleFutures Future<Void>. For a Peripheral application to advertise,

// use service and characteristic defined in previous section
let manager = PeripheralManager.sharedInstance
let startAdvertiseFuture = manager.powerOn().flatmap {_ -> Future<Void> in
	manager.removeAllServices()
}.flatmap {_ -> Future<Void> in
	manager.addService(service)
}.flatmap {_ -> Future<Void> in
	manager.startAdvertising("My Service", uuids:[serviceUUID])
}
            
startAdvertiseFuture.onSuccess {
	
}
startAdvertiseFuture.onFailure {error in
	
}

Here the addServiceFuture of the previous section is flatmapped to startAdvertising(name:String, uuids:[CBUUID]?) -> Future<Void> ensuring that services and characteristics are available before advertising begins.

A BlueCap Characteristic value can be set any time after creation of the Characteristic. The BlueCap MutableCharacteristic methods used are,

var value : NSData? {get set}

It is not necessary for the PeripheralManager to be powered on or advertising to set a characteristic value.

A peripheral application can set a characteristic value using,

// Enabled and characteristic defined above
characteristic.value = Serde.serialize(Enabled.Yes)

If a Characteristic value supports the property CBCharacteristicProperties.Notify a Central can subscribe to value updates. In addition to setting the new value an update notification must be sent. The BlueCap MutableCharacteristic methods used are,

// update with NSData
func updateValueWithData(value:NSData) -> Bool

// update with String Dictionary
public func updateValueWithString(value:Dictionary<String, String>) -> Bool

// update with object supporting Deserializable
public func updateValue<T:Deserializable>(value:T) -> Bool

// update with object supporting RawDeserializable
public func updateValue<T:RawDeserializable>(value:T) -> Bool

// update with object supporting RawArrayDeserializable
public func updateValue<T:RawArrayDeserializable>(value:T) -> Bool

// update with object supporting RawPairDeserializable
public func updateValue<T:RawPairDeserializable>(value:T) -> Bool

// update with object supporting RawArrayPairDeserializable
public func updateValue<T:RawArrayPairDeserializable>(value:T) -> Bool

All methods return a Bool which is true if the update succeeds and false if either there are no subscribers, CBCharacteristicProperties.Notify is not supported or the length of the update queue is exceeded. In addition to sending an update notification to a subscribing Central the Characteristic value is set. A BlueCap Characteristic value can be updated any time after creation of the characteristic. It is not necessary for the PeripheralManager to be powered on or advertising. Though in this case the update will fail and return false.

Peripheral applications would send notification updates using,

// Enabled and characteristic defined above
characteristic.updateValue(Enabled.No)

If a Characteristic value supports the property CBCharacteristicProperties.Write a Central can change the Characteristic value. The BlueCap MutableCharacteristic methods used are,

// start processing write requests with stream capacity
public func startRespondingToWriteRequests(capacity:Int? = nil) -> FutureStream<CBATTRequest>

// respond to received write request
func respondToRequest(request:CBATTRequest, withResult result:CBATTError)

// stop processing write requests
public func stopProcessingWriteRequests()

CBATTRequest encapsulates Central write requests, CBATTError encapsulates response the code and a SimpleFutures FutureStream<CBATTRequest> is used to respond to write requests.

Peripheral applications would start responding to Central writes using,

let writeFuture = characteristic.startRespondingToWriteRequests(capacity:10)
writeFuture.onSuccess {request in
	if request.value.length == 1 {
		characteristic.value = request.value
		characteristic.respondToRequest(request, withResult:CBATTError.Success)
	} else {  
		characteristic.respondToRequest(request, withResult:CBATTError.InvalidAttributeValueLength)
	}
}

Peripheral applications would stop responding to write requests using,

characteristic.stopProcessingWriteRequests()

iBeacon emulation does not require Services and Characteristics. Only advertising is required. The BlueCap PeripheralManager methods used are,

// start advertising beceacon region
public func startAdvertising(region:BeaconRegion) -> Future<Void>

// stop advertising
public func stopAdvertising() -> Future<Void>

All methods return a SimpleFutures Future<Void>. Creation of a FutureLocation BeaconRegion is also required,

public convenience init(proximityUUID:NSUUID, identifier:String, major:UInt16, minor:UInt16)
proximityUUID The proximity ID of the beacon targeted
identifier A unique identifier for region used by application
major The major value used to identify one or more beacons
minor The minor value used to identify a specific beacon

For an iBeacon application to advertise,

// use service and characteristic defined in previous section
let regionUUID = CBUUID(string:"DE6E8DAD-8D99-4E20-8C4B-D9CC2F9A7E83")!
let startAdvertiseFuture = manager.powerOn().flatmap {_ -> Future<Void> in
	let beaconRegion = BeaconRegion(proximityUUID:regionUUID, identifier:"My iBeacon", major:100, minor:1, capacity:10)
	manager.startAdvertising(beaconRegion)
}
            
startAdvertiseFuture.onSuccess {
	
}
startAdvertiseFuture.onFailure {error in
	
}

Here the powerOn() -> Future<Void> flatmapped to startAdvertising(region:BeaconRegion) -> Future<Void> ensuring that the bluetooth transceiver is powered on before advertising begins.

Packages

No packages published

Languages

  • Swift 99.9%
  • Objective-C 0.1%