如何阅读Swift中的BLE特征浮点

时间:2016-04-15 19:13:15

标签: swift swift2 bluetooth-lowenergy binaryfiles core-bluetooth

我正在尝试连接蓝牙LE /蓝牙智能/ BLE健康设备的健康温度计服务(0x1809),正如此处正式描述的那样:https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.health_thermometer.xml。具体来说,我要从健康温度计特征(0x2A1C)请求通知,其中的描述如下:https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.temperature_measurement.xml

我有一个不错的Swift 2背景,但我从未与NSData,字节或按位运算符密切合作,而且我对Little Endian与Big Endian完全陌生,所以这是相当新的对我来说,我可以使用一些帮助。该特性具有一些内置逻辑,可确定您将收到哪些数据。到目前为止,我已经按照标准,温度测量值和时间戳的顺序接收了100%的数据,但不幸的是,我总是得到" 010"的控制逻辑。这意味着我没有错误地读取旗帜。事实上,我认为我错误地引入了时间戳的所有内容。我包括我在代码评论中看到的数据。

我尝试了多种获取此二进制数据的方法。标志是带有位运算符的单字节。温度测量本身就是Float,我花了一些时间才意识到它不是Swift Float,而是ISO / IEEE标准" IEEE-11073 32位FLOAT"与BLE规范所说的一样,没有指数价值"在这里:https://www.bluetooth.com/specifications/assigned-numbers/format-types。我甚至都不知道这意味着什么。这是我来自didUpdateValueForCharacteristic()函数的代码,您可以在其中查看我在尝试新注释时发出的多次尝试:

// Parse Characteristic Response
let stream = NSInputStream( data: characteristic.value! )
stream.open()    // IMPORTANT

// Retrieve Flags
var readBuffer = Array<UInt8>( count: 1, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
var flags = String( readBuffer[ 0 ], radix: 2 )
flags = String( count: 8 - flags.characters.count, repeatedValue: Character( "0" ) ) + flags
flags = String( flags.characters.reverse() )
print( "FLAGS: \( flags )" )

// Example data:
// ["01000000"]
//
// This appears to be wrong.  I should be getting "10000000" according to spec

// Bluetooth FLOAT-TYPE is defined in ISO/IEEE Std. 11073
// FLOATs are 32 bit
// Format [8bit exponent][24bit mantissa]

/* Attempt 1 - Read in a Float - Doesn't work since it's an IEEE Float
readBuffer = Array<UInt8>( count: 4, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
var tempData = UnsafePointer<Float>( readBuffer ).memory

// Attempt 2 - Inverted bytes- Doesn't work since it's wrong and it's an IEEE Float
let readBuffer2 = [ readBuffer[ 3 ], readBuffer[ 2 ], readBuffer[ 1 ], readBuffer[ 0 ] ]
var tempValue = UnsafePointer<Float>( readBuffer2 ).memory
print( "TEMP: \( tempValue )" )

// Attempt 3 - Doesn't work for 1 or 2 since it's an IEEE Float
var f:Float = 0.0
memccpy(&f, readBuffer, 4, 4)
print( "TEMP: \( f )" )
var f2:Float = 0.0
memccpy(&f2, readBuffer2, 4, 4)
print( "TEMP: \( f2 )" )

// Attempt 4 - Trying to Read an Exponent and a Mantissa - Didn't work
readBuffer = Array<UInt8>( count: 1, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let exponent = UnsafePointer<Int8>( readBuffer ).memory

readBuffer = Array<UInt8>( count: 3, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let mantissa = UnsafePointer<Int16>( readBuffer ).memory

let temp = NSDecimalNumber( mantissa: mantissa, exponent: exponent, isNegative: false )
print( "TEMP: \( temp )" )

// Attempt 5 - Invert bytes - Doesn't work
readBuffer = Array<UInt8>( count: 4, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let exponentBuffer = [ readBuffer[ 3 ] ]
let mantissaBuffer = [ readBuffer[ 2 ], readBuffer[ 1 ], readBuffer[ 0 ] ]
let exponent = UnsafePointer<Int16>( exponentBuffer ).memory
let mantissa = UnsafePointer<UInt64>( mantissaBuffer ).memory
let temp = NSDecimalNumber( mantissa: mantissa, exponent: exponent, isNegative: false )
print( "TEMP: \( temp )" )

// Attempt 6 - Tried a bitstream frontwards and backwards - Doesn't work
readBuffer = Array<UInt8>( count: 4, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )

var bitBuffer: [String] = Array<String>( count:4, repeatedValue: "" )
for var i = 0; i < bitBuffer.count; i++ {
  bitBuffer[ i ] = String( readBuffer[ i ], radix: 2 )
  bitBuffer[ i ] = String( count: 8 - bitBuffer[ i ].characters.count, repeatedValue: Character( "0" ) ) + bitBuffer[ i ]
  //bitBuffer[ i ] = String( bitBuffer[ i ].characters.reverse() )
}
print( "TEMP: \( bitBuffer )" )

// Attempt 7 - More like the Obj. C code - Doesn't work
readBuffer = Array<UInt8>( count: 4, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let value = UnsafePointer<UInt32>( readBuffer ).memory
let tempData = CFSwapInt32LittleToHost( value )

let exponent = tempData >> 24
let mantissa = tempData & 0x00FFFFFF

if ( tempData == 0x007FFFFF ) {
  print(" *** INVALID *** ")
  return
}

let tempValue = Double( mantissa ) * pow( 10.0, Double( exponent ) )
print( "TEMP: \( tempValue )" )

// Attempt 8 - Saw that BLE spec says "NO Exponent" - Doesnt' work
readBuffer = Array<UInt8>( count: 1, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )

readBuffer = Array<UInt8>( count: 3, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let tempValue = UnsafePointer<Float>( readBuffer ).memory
print( "TEMP: \( tempValue )" )

// Example data:
// ["00110110", "00000001", "00000000", "11111111"]
//
// Only the first byte appears to ever change.
*/


// Timestamp - Year - works
readBuffer = Array<UInt8>( count: 2, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let year = UnsafePointer<UInt16>( readBuffer ).memory

// Timestamp Remainder - works
readBuffer = Array<UInt8>( count: 5, repeatedValue: 0 )
stream.read( &readBuffer, maxLength: readBuffer.count )
let month = readBuffer[ 0 ]
let day = readBuffer[ 1 ]
let hour = readBuffer[ 2 ]
let minute = readBuffer[ 3 ]
let second = readBuffer[ 4 ]
print( "TIMESTAMP: \( month )/\( day )/\( year ) \( hour ):\( minute ):\( second )" )

我在目标C中找到了这个例子,我不知道(https://github.com/AngelSensor/angel-sdk/blob/b7459d9c86c6a5c72d8e58b696345b642286b876/iOS/SDK/Services/HealthThermometer/ANHTTemperatureMeasurmentCharacteristic.m),并且我已经尝试过它,但它不是向我清楚究竟发生了什么:

    // flags
    uint8_t flags = dataPointer[0];
    dataPointer++;

    // temperature
    uint32_t tempData = (uint32_t)CFSwapInt32LittleToHost(*(uint32_t *)dataPointer);
    dataPointer += 4;

    int8_t  exponent = (int8_t)(tempData >> 24);
    int32_t mantissa = (int32_t)(tempData & 0x00FFFFFF);

    if (tempData == 0x007FFFFF) {
        return;
    }

    float tempValue = (float)(mantissa*pow(10, exponent));

如果有人可以帮我解决如何从这个BLE特征中拉出旗帜和温度计的测量结果,我将非常感激。感谢。

我被要求提供以下样本数据。这是我的样本数据(总共12个字节):

["00000010", "00110011", "00000001", "00000000", "11111111", "11100000", "00000111", "00000100", "00001111", "00000001", "00000101", "00101100"]

-OR-

<025e0100 ffe00704 0f11150f>

2 个答案:

答案 0 :(得分:2)

有时候出行可能有点棘手,但这是我的简单实现,希望它可以帮助你

private func parseThermometerReading(withData someData : NSData?) {
    var pointer = UnsafeMutablePointer<UInt8>(someData!.bytes)
    let flagsValue = Int(pointer.memory) //First 8 bytes are the flag

    let temperatureUnitisCelsius = (flagsValue & 0x01) == 0
    let timeStampPresent         = (flagsValue & 0x02) > 0
    let temperatureTypePresent   = ((flagsValue & 0x04) >> 2) > 0

    pointer = pointer.successor() //Jump over the flag byte (pointer is 1 bytes, so successor will automatically hot 8 bits), you can also user pointer = pointer.advanceBy(1), which is the same

    let measurementValue : Float32 = self.parseFloat32(withPointer: pointer) //the parseFloat32 method is where the IEEE float conversion magic happens
    pointer = pointer.advancedBy(4) //Skip 32 bits (Since pointer holds 1 byte (8 bits), to skip over 32 bits we need to jump 4 bytes (4 * 8 = 32 bits), we are now jumping over the measurement FLOAT

    var timeStamp : NSDate?

    if timeStampPresent {
        //Parse timestamp
        //ParseDate method is also a simple way to convert the 7 byte timestamp to an NSDate object, see it's implementation for more details
        timeStamp = self.parseDate(withPointer: pointer)
        pointer = pointer.advancedBy(7) //Skip over 7 bytes of timestamp
    }

    var temperatureType : Int = -1 //Some unknown value

    if temperatureTypePresent {
        //Parse measurement Type
        temperatureType = Int(pointer.memory))
    }
}

现在转换为将字节转换为IEEE Float的小方法

internal func parseFloat32(withPointer aPointer : UnsafeMutablePointer<UInt8>) -> Float32 {
    // aPointer is 8bits long, we need to convert it to an 32Bit value
    var rawValue = UnsafeMutablePointer<UInt32>(aPointer).memory //rawValue is now aPointer, but with 32 bits instead of just 8
    let tempData = Int(CFSwapInt32LittleToHost(rawValue)) //We need to convert from BLE Little endian to match the current host's endianness

    // The 32 bit value consists of a 8 bit exponent and a 24 bit mantissa
    var mantissa : Int32  = Int32(tempData & 0x00FFFFFF) //We get the mantissa using bit masking (basically we mask out first 8 bits)

    //UnsafeBitCast is the trick in swift here, since this is the only way to convert an UInt8 to a signed Int8, this is not needed in the ObjC examples that you'll see online since ObjC supports SInt* types
    let exponent = unsafeBitCast(UInt8(tempData >> 24), Int8.self)
    //And we get the exponent by shifting 24 bits, 32-24 = 8 (the exponent)
    var output : Float32 = 0

    //Here we do some checks for specific cases of Negative infinity/infinity, Reserved MDER values, etc..
    if mantissa >= Int32(FIRST_RESERVED_VALUE.rawValue) && mantissa <= Int32(ReservedFloatValues.MDER_NEGATIVE_INFINITY.rawValue) {
        output = Float32(RESERVED_FLOAT_VALUES[mantissa - Int32(FIRST_S_RESERVED_VALUE.rawValue)])
    }else{
        //This is not a special reserved value, do the normal mathematical calculation to get the float value using mantissa and exponent.
        if mantissa >= 0x800000 {
            mantissa = -((0xFFFFFF + 1) - mantissa)
        }
        let magnitude = pow(10.0, Double(exponent))
        output = Float32(mantissa) * Float32(magnitude)
    }
    return output
}

以下是如何将日期解析为NSDate对象

internal func parseDate(withPointer aPointer : UnsafeMutablePointer<UInt8>) -> NSDate {

    var bytePointer = aPointer //The given Unsigned Int8 pointer
    var wordPointer = UnsafeMutablePointer<UInt16>(bytePointer) //We also hold a UInt16 pointer for the year, this is optional really, just easier to read
    var year        = Int(CFSwapInt16LittleToHost(wordPointer.memory)) //This gives us the year
    bytePointer     = bytePointer.advancedBy(2) //Skip 2 bytes (year)
    //bytePointer     = wordPointer.successor() //Or you can do this using the word Pointer instead (successor will make it jump 2 bytes)

    //The rest here is self explanatory
    var month       = Int(bytePointer.memory)
    bytePointer     = bytePointer.successor()
    var day         = Int(bytePointer.memory)
    bytePointer     = bytePointer.successor()
    var hours       = Int(bytePointer.memory)
    bytePointer     = bytePointer.successor()
    var minutes     = Int(bytePointer.memory)
    bytePointer     = bytePointer.successor()
    var seconds     = Int(bytePointer.memory)

    //Timestamp components parsed, create NSDate object
    var calendar            = NSCalendar.currentCalendar()
    var dateComponents      = calendar.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: NSDate())
    dateComponents.year     = year
    dateComponents.month    = month
    dateComponents.day      = day
    dateComponents.hour     = hours
    dateComponents.minute   = minutes
    dateComponents.second   = seconds

    return calendar.dateFromComponents(dateComponents)!
}

这几乎是使用FLOAT类型的任何其他BLE特征所需的所有技巧

答案 1 :(得分:0)

我已经完成了一些类似于你的事情......我不确定这是否与你相关,但让我们去挖掘它......也许我的代码可以给你一些见解:

首先,将NSData获取到一个UInt8数组:

let arr = Array(UnsafeBufferPointer(start: UnsafePointer<UInt8>(data.bytes), count: data.length))

我们遵循的规范说这个数组中的前3个位置代表尾数,最后一个位置是指数(在-128..127范围内):

    let exponentRaw = input[3]
    var exponent = Int16(exponentRaw)

    if exponentRaw > 0x7F {
        exponent = Int16(exponentRaw) - 0x100
    }

    let mantissa = sumBits(Array(input[0...2]))

    let magnitude = pow(10.0, Float32(exponent))
    let value = Float32(mantissa) * magnitude

...辅助功能:

func sumBits(arr: [UInt8]) -> UInt64 {
    var sum : UInt64 = 0
    for (idx, val) in arr.enumerate() {
        sum += UInt64(val) << ( 8 * UInt64(idx) )
    }
    return sum
}