Core Bluetooth Characteristic Value mismatch between iOS and macOS

Hi there!

I am writing the BLE portion of my iOS application and have run into some strange behaviour while testing with a nordic dev kit (nRF52840) running an in-house dev build. The BLE workflow seems to be flowing correctly however the characteristic value for all characteristics seem to be applying some sort of little-endian byte encoding on a different characteristic's UUID plus 3 additional bytes of random information.

I believe this issue lies with the implementation of Core Bluetooth in Swift as reading the same characteristic with Bleak in python on macOS (which uses Core Bluetooth) retrieves the correct characteristic value. I've read through the package and spoken to the developer that wrote the Bleak/Core Bluetooth backend and we both agree that the workflow is identical and as follows:

  1. Swift View loads and initializes the BLE Manager. centralManagerDidUpdateState is called and the central state is .poweredOn. Peripheral scan starts.
  2. Connect to a peripheral calling centralManager(didConnect) which calls peripheral.discoverServices.
  3. Then peripheral(didDiscoverServices) which calls peripheral.discoverCharacteristics(for: service)
  4. Then peripheral(didDiscoverCharacteristicsFor: ) which calls peripheral.readValue(for: characteristic) after checking characteristic.properties.contains(.read)
  5. Then peripheral(didUpdateValueFor: characteristic)

Note: All characteristics are behaving this way in iOS. The specific characteristic used in my code snippets are for a constant, read-only value.

Code Snippets

python Bleak code (taken from example)

async def simple_callback(device: BLEDevice, advertisement_data: AdvertisementData):
   if device.address == address:
     print(f'Connecting to client {address}')
     client = BleakClient(device.address)
     try:
       await client.connect()
       svcs = await client.get_services()
       for s in svcs:
         print(f"Service: {s}")
 
       value = await client.read_gatt_char(UUID)
 
       print(f"{UUID}: {value}")
     except Exception as e:
       print(e)
     finally:
       await client.disconnect()

Python output (correct):

Read Characteristic 2f1a0004-1edf-47e3-9ea6-423c2c7c2913 : bytearray(b'\xfc\x00\x03\x00')

Swift Snippet - my peripheral(didUpdateValueFor: )

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
     
     print("didUpdateValueFor characteristic: service=\(characteristic.service!.uuid.uuidString) characteristic=\(characteristic.uuid.uuidString)")
     
     if let data = characteristic.value {
       var values = [UInt8](repeating:0, count:data.count)
      data.copyBytes(to: &values, count: data.count)
       print(characteristic)
    } else {
       print("ERROR: NO DATA from characteristic")
    }
 }

Xcode console output (incorrect):

didUpdateValueFor characteristic: service=2F1A0001-1EDF-47E3-9EA6-423C2C7C2913 characteristic=2F1A0004-1EDF-47E3-9EA6-423C2C7C2913
<CBCharacteristic: 0x283013cc0, UUID = 2F1A0004-1EDF-47E3-9EA6-423C2C7C2913, properties = 0x2, value = {length = 19, bytes = 0x02180013297c2c3c42a69ee347df1e0b001a2f}, notifying = NO>

Here the 19 byte value seems to include a UUID for a different characteristic reversed plus 3 bytes. I’ve attempted to sanitize and isolate those additional bytes but that is still incorrect.

Strangely enough the nordic nrfConnect iOS application also reads back this same incorrect value but the Android release works correctly.

iOS Screenshot

Android Screenshot

Additional Info

I am running Xcode 13.0 beta on my MacBook Pro and deploying onto an iPhone 11 Pro Max. Application is reading all other attributes correctly (RSSI, etc.).

Any help is greatly appreciated! Please let me know if I can provide any more information to support.

Thanks,

Crudough

Answered by ForumsContributor in

One possible interpretation is that the device is showing a different value only when connected to iOS device. What does that characteristic mean?

@OOPer That characteristic is the SW version which is static for the build. I've added an answer to this question after working though debug builds on the dev kit.

Accepted Answer

After debugging more of the application code we've found that there is different behaviour based on if the Service Changed characteristic is set. (See the Bluetooth 4.0 specification, Volume 3, Part G, Section 7.1) When that characteristic is enabled it seems that the iOS side uses cached values from the initial characteristic discovery but only on the iOS side, macOS works correctly.

This still isn't clear as the service changed characteristic is used to notify of when to not use the cached values. I will still be investigating why this issues only appears on iOS but I will mark this as closed for the time being. I will still monitor if anyone has any comments or insight! Thanks!

Core Bluetooth Characteristic Value mismatch between iOS and macOS
 
 
Q