Search code examples
swiftbluetoothnsdatabtle

Characteristic.value from Bluetooth reading in Swift


I'm trying to read and convert into String a reading from a BT device.

<CBCharacteristic: 0x1700a2e80, UUID = 2A9D, properties = 0x20, value = <02ac08e1 07010a14 0029>, notifying = YES>

Based on example I found online I did

let u16 = (characteristic.value! as NSData).bytes.bindMemory(to: Int.self, capacity: characteristic.value!.count).pointee

but my u16 is null, even though characteristic.value contains

(lldb) dp characteristic.value! as NSData
<02ac08e1 07010a14 0029>

Can anyone point me in the right direction?


Solution

  • It's not super easy.

    Weight Measurement

    You may need to write something like this:

    let data = Data([0x02,0xac,0x08,0xe1,0x07,0x01,0x0a,0x14,0x00,0x29])
    
    struct DataReader {
        var data: Data
        var offset: Int = 0
    
        mutating func read() -> UInt8 {
            let result = data[offset]
            offset += MemoryLayout<UInt8>.size
            return result
        }
        mutating func read() -> UInt16 {
            let subdata = data.subdata(in: offset..<offset+MemoryLayout<UInt16>.size)
            let result: UInt16 = subdata.withUnsafeBytes {(bytes: UnsafePointer<UInt16>) in
                bytes.pointee.littleEndian
            }
            offset += MemoryLayout<UInt16>.size
            return result
        }
    }
    
    typealias  BLEUserID = UInt8
    
    struct BLEDateTime {
        var year: UInt16
        var month: UInt8
        var day: UInt8
        var hours: UInt8
        var minutes: UInt8
        var seconds: UInt8
    }
    extension DataReader {
        mutating func read() -> BLEDateTime {
            let year: UInt16 = read()
            let month: UInt8 = read()
            let day: UInt8 = read()
            let hours: UInt8 = read()
            let minutes: UInt8 = read()
            let seconds: UInt8 = read()
            return BLEDateTime(year: year, month: month, day: day, hours: hours, minutes: minutes, seconds: seconds)
        }
    }
    struct WeightMeasurement: CustomStringConvertible {
        struct Flags: OptionSet {
            var rawValue: UInt8
            init(rawValue: UInt8) {
                self.rawValue = rawValue
            }
            static let measurementImperial = Flags(rawValue: 1<<0)
            static let timeStampPresent = Flags(rawValue: 1<<1)
            static let userIDPresent = Flags(rawValue: 1<<2)
            static let bmiAndHeightPresent = Flags(rawValue: 1<<3)
        }
        var isMeasuremntImperial: Bool
        var weight: Decimal
        var timeStamp: BLEDateTime?
        var userID: BLEUserID?
        var bmi: Decimal?
        var height: Decimal?
    
        var description: String {
            return weight.description
        }
    }
    extension DataReader {
        mutating func read() -> WeightMeasurement {
            let flags = WeightMeasurement.Flags(rawValue: read())
            let isMeasuremntImperial = flags.contains(.measurementImperial)
            let weight: Decimal
            if isMeasuremntImperial {
                weight = Decimal(read() as UInt16) * Decimal(string: "0.01")!
            } else {
                //SI
                weight = Decimal(read() as UInt16) * Decimal(string: "0.005")!
            }
            var timeStamp: BLEDateTime?
            if flags.contains(.timeStampPresent) {
                timeStamp = read()
            }
            var userID: BLEUserID?
            if flags.contains(.userIDPresent) {
                userID = read()
            }
            var bmi: Decimal?
            var height: Decimal?
            if flags.contains(.bmiAndHeightPresent) {
                bmi = Decimal(read() as UInt16) * Decimal(string: "0.1")!
                if isMeasuremntImperial {
                    height = Decimal(read() as UInt16) * Decimal(string: "0.1")!
                } else {
                    //SI
                    height = Decimal(read() as UInt16) * Decimal(string: "0.001")!
                }
            }
            return WeightMeasurement(isMeasuremntImperial: isMeasuremntImperial, weight: weight, timeStamp: timeStamp, userID: userID, bmi: bmi, height: height)
        }
    }
    
    var reader = DataReader(data: data, offset: 0)
    let weightMeasurement: WeightMeasurement = reader.read()
    print(weightMeasurement) //->11.1
    print(weightMeasurement.timeStamp!) //->BLEDateTime(year: 2017, month: 1, day: 10, hours: 20, minutes: 0, seconds: 41)
    

    (Sorry, not fully tested and you may need some modification.)