Skip to content

Latest commit

 

History

History
432 lines (334 loc) · 17.7 KB

GATTProfileDefinition.md

File metadata and controls

432 lines (334 loc) · 17.7 KB

GATT profile definitions are required to add support for a device to the BluCap 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 as they are created and provides serialization and deserialization of Characteristic values to and from Strings. The examples in this section are also available in a Playground project.

Content

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

public protocol ServiceConfigurable {
    // Service name.
    static var name: String { get }
    // Service UUID.
    static var uuid: String { get }
    // Used to organize services in the BlueCap app profile browser.
    static var tag: String { get }
}}

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

public protocol CharacteristicConfigurable {
    // Characteristic name.
    static var name: String { get }
    // Characteristic UUID.
    static var uuid: String { get }
    // Charcteristic permissions
    static var permissions: CBAttributePermissions { get }
    // Charcteristic properties
    static var properties: CBCharacteristicProperties { get }
    // Characteristic initial value.
    static var initialValue: Data? { get }
}

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

public protocol StringDeserializable {
    // Used for enums to specify Strings for values but ignored for other types.
    static var stringValues: [String] { get }
    // The String values of the rawType.
    var stringValue: [String : String] { get }
    // Create object from stringValue.
    init?(stringValue:[String : String])
}

String values of Characteristics are assumed to be Dictionaries containing the name-value pairs.

A ServiceProfile is used to define Service configuration. It can be used to instantiate either Service or MutableService objects.

let serviceProfile = ServiceProfile(uuid: "F000AA10-0451-4000-B000-000000000000", name: "Cool Service") 

The CharacteristicProfiles belonging to a ServiceProfile are added using a method defined on ServiceProfile,

public func addCharacteristic(characteristicProfile: CharacteristicProfile)

A ConfiguredServiceProfile object encapsulates a Service configuration and is a subclass of ServiceProfile. It 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>() 

CharacteristicProfile is the base class for CharacteristicProfile types and is instantiated as the default Characteristic profile if one was not explicitly defined for a discovered Characteristic. In this case, with no String conversions implemented in a GATT Profile definition, a Characteristic will support the default String conversions to and from Data using hexadecimal Strings. It can be used to instantiate either Characteristic or MutableCharacteristic objects. CharacteristicProfile have the following initializer,

public init(uuid: String, name: String, permissions: CBAttributePermissions = [.readable, .writeable], properties: CBCharacteristicProperties = [.read, .write, .notify], initialValue: Data? = nil)

public convenience init(uuid: String)

Default implementations are provided forth following methods,

public func propertyEnabled(_ property: CBCharacteristicProperties) -> Bool
    
public func permissionEnabled(_ permission: CBAttributePermissions) -> Bool
        
public func stringValue(_ data: Data) -> [String : String]?
    
public func data(fromString data: [String : String]) -> Data? 

A RawCharacteristicProfile object encapsulates configuration and serialization/desserialization for a Characteristic implementing RawDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects and is a subclass of CharacteristicProfile.

The CharacteristicProfile type for the TiSensorTag Accelerometer Service Enabled Characteristic implementing RawDeserializable, StringDeserializable, CharacteristicConfigurable is given by,

public enum Enabled: UInt8, RawDeserializable, StringDeserializable, CharacteristicConfigurable {
    public typealias RawType = UInt8

    case no = 0
    case yes = 1
    
    // CharacteristicConfigurable
    public static let uuid = "F000AA12-0451-4000-B000-000000000000"
    public static let name = "Accelerometer Enabled"
    public static let properties: CBCharacteristicProperties = [.read, .write]
    public static let permissions: CBAttributePermissions = [.readable, .writeable]
    public static let initialValue: Foundation.Data? = SerDe.serialize(Enabled.no.rawValue)

    // StringDeserializable
    public static let stringValues = ["no", "yes"]
    
    public init(boolValue: Bool) {
        if boolValue {
            self = Enabled.yes
        } else {
            self = Enabled.no
        }
    }
    
    public 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
        }
    }
    
    public var stringValue: [String: String] {
        switch self {
        case .no:
            return [Enabled.name : "no"]
        case .yes:
            return [Enabled.name : "yes"]
        }
    }
    
    public var boolValue: Bool {
        switch self {
        case .no:
            return false
        case .yes:
            return true
        }
    }
}
if let value = Enabled(stringValue:[Enabled.name : "Yes"]) {
    print(value.stringValue)
}

A RawArrayCharacteristicProfile object encapsulates configuration and serialization/deserialization for a characteristic implementing RawArrayDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects. An example profile for an [Int8] raw value implementing RawArrayDeserializable, CharacteristicConfigurable and StringDeserializable is given by,

struct ArrayData : RawArrayDeserializable, CharacteristicConfigurable, StringDeserializable {
    
    // CharacteristicConfigurable
    static let uuid = "F000AA11-0451-4000-B000-000000000000"
    static let name = "Accelerometer Data"
    static let properties: CBCharacteristicProperties = [.read, .write]
    static let permissions: CBAttributePermissions = [.readable, .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 : [String : String] {
        return ["value1" : "\(self.rawValue[0])",
            "value2" : "\(self.rawValue[1])"]
    }
    
    init?(stringValue:[String:String]) {
        if  let stringValue1 = stringValue["value1"],
            let stringValue2 = stringValue["value2"],
            let value1 = Int8(stringValue1),
            let value2 = Int8(stringValue2) {
                self.rawValue = [value1, value2]
        } else {
            return nil
        }
    }
}

if let value = ArrayData(rawValue: [1, 100]) {
    print(value.rawValue)
}

if let value = ArrayData(stringValue:["value1" : "1", "value2" : "100"]) {
    print(value.stringValue)
}

To instantiate a profile in an application,

let profile = RawArrayPairCharacteristicProfile<PairData>()

A RawPairCharacteristicProfile object encapsulates configuration and serialization/deserialization for a characteristic implementing RawPairDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects. An example profile for UInt8 and Int8 raw values implementing RawPairDeserializable, CharacteristicConfigurable and StringDeserializable is given by,

struct PairData : RawPairDeserializable, CharacteristicConfigurable, StringDeserializable {
    
    // CharacteristicConfigurable
    static let uuid = "F000AA30-0451-4000-B000-000000000000"
    static let name = "Magnetometer Data"
    static let properties: CBCharacteristicProperties = [.read, .notify]
    static let permissions: CBAttributePermissions = [.readable, .writeable]
    static let initialValue: Data? = SerDe.serialize(PairData(rawValue1: 10, rawValue2: -10)!)
    
    // RawArrayDeserializable
    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"],
           let stringValue2 = stringValue["value2"],
           let value1 = UInt8(stringValue1),
           let value2 = Int8(stringValue2) {
                self.rawValue1 = value1
                self.rawValue2 = value2
        } else {
            return nil
        }
    }
}

if let value = PairData(stringValue: ["value1" : "1", "value2" : "-2"]) {
    print(value.stringValue)
}

To instantiate a profile in an application,

let profile = RawArrayPairCharacteristicProfile<PairData>()

A RawArrayPairCharacteristicProfile object encapsulates configuration and serialization/deserialization for a characteristic implementing RawArrayPairDeserializable. It can be used to instantiate both Characteristic and MutableCharacteristic objects. An example profile for [UInt8] and [Int8] raw values implementing RawArrayPairDeserializable, CharacteristicConfigurable and StringDeserializable is given by,

struct ArrayPairData : RawArrayPairDeserializable, CharacteristicConfigurable, StringDeserializable {
    
    // CharacteristicConfigurable
    static let uuid = "F000AA11-0451-4000-B000-000000000000"
    static let name = "Accelerometer Data"
    static let properties: CBCharacteristicProperties = [.read, .notify]
    static let permissions: CBAttributePermissions = [.readable, .writeable]
    static let initialValue: Data? = SerDe.serialize(ArrayPairData(rawValue1: [1,2], rawValue2: [-1, -2])!)
    
    // RawArrayDeserializable
    let rawValue1 : [UInt8]
    let rawValue2 : [Int8]
    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"],
            let stringValue12 = stringValue["value12"],
            let value11 = UInt8(stringValue11),
            let value12 = UInt8(stringValue12),
            let stringValue21 = stringValue["value21"],
            let stringValue22 = stringValue["value22"],
            let value21 = Int8(stringValue21),
            let value22 = Int8(stringValue22) {
                self.rawValue1 = [value11, value12]
                self.rawValue2 = [value21, value22]
        } else {
            return nil
        }
    }
}

if let value = ArrayPairData(stringValue:["value11" : "1", "value12" : "2", "value21" : "-1", "value22" : "-2"]) {
    print(value.stringValue)
}

To instantiate a profile in an application,

let profile = RawArrayPairCharacteristicProfile<ArrayPairData>()

A StringCharacteristicProfile 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, .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 any application but is not required.

A ServiceProfile is added to ProfileManager using a method defined on ProfileManager,

public func addService(serviceProfile: ServiceProfile) -> ServiceProfile 

To add ServiceProfiles and CharacteristicProfiles to ProfileManager,

let profileManager = ProfileManager()

// create service profile
let serviceProfile = ConfiguredServiceProfile<AccelerometerService>()

// create characteristic profiles
let enabledProfile = RawCharacteristicProfile<Enabled>()
let rawArrayProfile = RawArrayCharacteristicProfile<ArrayData>()

// add characteristic profiles to service profile
serviceProfile.addCharacteristic(enabledProfile)
serviceProfile.addCharacteristic(rawArrayProfile)

// add service profile too profile manager
profileManager.addService(serviceProfile)

To add a GATT Profile to the BluCap app you need to add a file to the project containing all ServiceProfiles and CharacteristicProfiles with public access level. See GnosusProfiles in the BlueCap Project for an example. Add the profile to BlueCap in AppDelegate.swift.