3

I am beginner in swift development. I was working on BLE based application. Today I updated Xcode 8, iOS 10 and convert my code to swift3. Then some of my syntax are need to convert. After fixing this , I found one issue on CBCharacteristic.

Issue

Inside didUpdateValueforCharacteristic , I can get updated CBCharacteristic object. If I print out whole object, it show correctly. -> value = <3a02> When I retrieved value from CBCharacteristic , characteristic.value -> 2bytes (size of this value)

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic:     CBCharacteristic, error: Error?)
{
if (characteristic.uuid.description == LED_WAVELENGTH_CHARACTERISTIC_UUID)
{
            print("Characteristic - \(characteristic)")
            print("Data for characteristic  Wavelength - \  (characteristic.value)")
        }
 }

Log Result :

Characteristic - <CBCharacteristic: 0x1742a50a0, UUID = 2C14, properties = 0xE, value = <3a02>, notifying = NO>
Data for characteristic  Wavelength - Optional(2 bytes)

PS : This code is completely working fine on previous version.

Thanks for attention and hope someone can help me to fix this issue.

NyoSeint Lay
  • 156
  • 1
  • 11
  • What is the question? You have an optional value. You have force unwrapped it in the previous line, but you are printing the optional, not the unwrapped value. – Paulw11 Sep 14 '16 at 10:00
  • My question is why I getting result for characteristic.value is 2 bytes. I should get <3a02> according to CBCharacteristic Object. – NyoSeint Lay Sep 15 '16 at 01:48
  • Because it is two bytes. The data doesn't contain "3a02" as a string, it contains 0x3a (58 decimal) - one byte and 0x02 (02 decimal) the second byte. This is 570 as a little-endian 16 bit value, but you need to confirm the format used by your peripheral – Paulw11 Sep 15 '16 at 01:50
  • Yes, correct. I also mention in my question. This is size of the value. In swift2.2, return result is "<3a02>". Now in swift 3, return result is size of value. – NyoSeint Lay Sep 15 '16 at 01:52
  • If you interpret an NSData in a string context then you will get the result of the `description` method, but this is intended only for debug/logging. You should not rely on the return value of `description` being consistent. If you need to operate on the data values then access the bytes as required using `getBytes` – Paulw11 Sep 15 '16 at 01:54
  • **didUpdateValueForCharacteristic** is instance method of CBPeripheral. It call when the peripheral device notifies your app that the characteristic’s value has changed. This changed characteristic object is **characteristic** inside my snippet of code. I would like to know is why **characteristic.value** is not giving correct result in **swift 3** but working in **swift 2.2**. – NyoSeint Lay Sep 15 '16 at 02:15
  • I don't understand what you mean by "correct result"? What result do you expect? The output you have shown looks correct to me. Do you mean that `print("Data for characteristic Wavelength - \ (characteristic.value)")` is giving a different result to what it did previously? What if you use `print("Data for characteristic Wavelength - \ (characteristic.value!)")` ? You are logging the result of `description` which cannot be relied upon to have a consistent value – Paulw11 Sep 15 '16 at 02:23
  • According to my code, Result that I expect for characteristic.value is <3a02>. if I use print("Data for characteristic Wavelength - \ (characteristic.value!)") , result for now is 2 bytes (Not include Optional). – NyoSeint Lay Sep 15 '16 at 03:20
  • For eg, I have one CBCharacteristic object , it have two variable, UUID and value. `Characteristic ` , when i call `characteristic.UUID` , it give `FFDF`. so when I call characteristic.value, it should give `234`. (Not size of this variable). I do not expect to get consistent value . I would like to get `data of characteristic.value` instead of `size of characteristic.value`. – NyoSeint Lay Sep 15 '16 at 03:36
  • You need to use `characteristic.value?.getBytes()` - You need to access the bytes in the NSData. You can't just refer to `characteristic.value`; well you can, but that will give you an `NSData`, which is fine to pass to some other function that wants an NSData instance, but eventually you will need to get the actual bytes that are *in* the NSData – Paulw11 Sep 15 '16 at 03:42
  • Exactly - `(characteristic.value as NSData).getBytes(pointer, range)` . because characteristic.value return only `Data Type` not `NSData`. I already have these conversion functions. My problem is not conversion problem from NSData to real UInt16 or UInt32. My problem is retrieving CBCharacteristic Data value from BLE delegate. Am I so weak to make u understand about my question ? Pls check my log two line . First line value in CBCharacteristic object is `<3a02>`. When I retrieve this value, I got `2 Bytes`. That is my problem. – NyoSeint Lay Sep 15 '16 at 05:23
  • You didn't get 2 bytes. the function `characteristic.value.description()` returned the string "2 bytes". When you evaluate an object in Swift in a string context (which is what you are doing in the print statement via value interpolation) Swift actually calls the object's `description` function and uses the result. In this case the NSData (or just `Data` in Swift 3) contains 2 bytes so that is the value that `description` returned. This is just a *description* of the object, not its value. 3a02 *is* literally two bytes. It isn't one byte, it isn't four bytes. – Paulw11 Sep 15 '16 at 05:26
  • I don't understand why you are obsessing about the output of a print statement. Just use the data. – Paulw11 Sep 15 '16 at 05:30
  • The one I developing app is to connect with Photometry device via BLE. Depending on photometry device, their supported wavelength are different. I need to get updated wavelength by using this Characteristic UUID. Even though I show only 2 print statement, in my real code, there have a lot to do from this result. Now I cannot get any characteristic data value. Anyway Thanks for ur attention. Now I am testing with swift 2.3. – NyoSeint Lay Sep 15 '16 at 06:08
  • If you have a problem accessing the data, then show that code, what is happening and what your expected result is. The code shown here is doing what is expected as it doesn't actually access the data. – Paulw11 Sep 15 '16 at 06:13
  • I think I already show my code enough in there. Please check my posted log. In first line - printing CBCharacteristic object. This object have value - `<3a02>`. I would like to retrieve this value from this CBCharacteristic object. That is all I want. In swift 2.2, I use `CBCharacteristicObj.value`. But In swift 3, it is not working and I get size of this value only. – NyoSeint Lay Sep 15 '16 at 06:32
  • As I have said multiple times. You need to use .getBytes to get the bytes from the data object. `NSData` is a wrapper – Paulw11 Sep 15 '16 at 06:35
  • I already checked with getBytes. It still cannot get the result. I know it is working fine in swift 2.2 /2.3. But it is not in swift 3 . please check my code `Wavelengthdata = characteristic.value! let count = Wavelengthdata.count / MemoryLayout.size var array = [UInt16](repeating: 0, count: count) print((Wavelengthdata as NSData).getBytes(&array, length: count * MemoryLayout.size))` – NyoSeint Lay Sep 15 '16 at 06:40
  • Right. So now you are showing some code. What result do you get from this? – Paulw11 Sep 15 '16 at 06:42
  • print result is "2 bytes". I need is UInt16 value from this NSData. – NyoSeint Lay Sep 15 '16 at 06:44
  • The results are in `array`. Take the first byte and add it to the second byte * 256 (assuming little endian). – Paulw11 Sep 15 '16 at 06:47
  • No.. Return result is UInt16 type. Not array. In swift 2.2/2.3, I can get this type of result from this code. `Data for characteristic Wavelength - Optional(<1801>) ` `Data for characteristic Wavelength - Optional(<3a02>) ` `Data for characteristic Wavelength - Optional(<5802>)` – NyoSeint Lay Sep 15 '16 at 06:52
  • You are relying on the string format of the description method. This is a bad idea as you have found. You need to get the two bytes and do the conversion – Paulw11 Sep 15 '16 at 06:53
  • I can get 280 from <1801>, 570 from <3a02> and 600 from <5802>. These are already okay with my conversion method. My current problem is I'm not even get this NSData value from BLE with swift 3, xcode 8. – NyoSeint Lay Sep 15 '16 at 07:03

2 Answers2

5

It seems that you have been relying on the description of NSData returning a string of the form <xxxx> in order to retrieve the value of your data. This is fragile, as you have found, since the description function is only meant for debugging and can change without warning.

The correct approach is to access the byte array that is wrapped in the Data object. This has been made a little bit more tricky, since Swift 2 would let you copy UInt8 values into a single element UInt16 array. Swift 3 won't let you do this, so you need to do the math yourself.

var wavelength: UInt16?
if let data = characteristic.value {
    var bytes = Array(repeating: 0 as UInt8, count:someData.count/MemoryLayout<UInt8>.size)

    data.copyBytes(to: &bytes, count:data.count)
    let data16 = bytes.map { UInt16($0) }
    wavelength = 256 * data16[1] + data16[0]
}

print(wavelength) 
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Correct !! Whoo hoo .. Finally I can get correct result. Thanks a lot .. Now I know what is my problem. Again thank you . @Paulw11 – NyoSeint Lay Sep 16 '16 at 01:49
1

Now, you can use String(bytes: characteristic.value!, encoding: String.Encoding.utf8), to get the string value of the characteristic.

Clément E.
  • 61
  • 1
  • 4