chipweinberger / flutter_blue_plus

Flutter plugin for connecting and communicationg with Bluetooth Low Energy devices, on Android, iOS, macOS
Other
773 stars 468 forks source link

[Help]: Why is the format of `manufacturerData` different between iOS and Android? #1047

Open P0labrD opened 1 day ago

P0labrD commented 1 day ago

Requirements

Have you checked this problem on the example app?

No

FlutterBluePlus Version

1.33.2

Flutter Version

3.24.3

What OS?

Android, iOS

OS Version

iOS 15.8.3, Android 14

Bluetooth Module

ESP32-WROOM-32E

What is your problem?

I'm looking at the advertisementData of my bluetooth device on both Android and iOS, and I can't figure why the format of manufacturerData and msd properties are different: the manufacturerId key is accessed differently between both os

{
  "android": {
    "advName": "advName",
    "txPowerLevel": null,
    "appearance": null,
    "connectable": true,
    "manufacturerData": {
      "0": [5, 54 /* ... */],
      "12345": []
    },
    "msd": [
      [0, 0, 5, 54 /* ... */],
      [48, 57]
    ],
    "serviceData": {},
    "serviceUuids": []
  },

  "ios": {
    "advName": "advName",
    "txPowerLevel": null,
    "appearance": null,
    "connectable": true,
    "manufacturerData": {
      "12345": [0, 0, 5, 54 /* ... */]
    },
    "msd": [[48, 57, 0, 0, 5, 54 /* ... */]],
    "serviceData": {},
    "serviceUuids": []
  }
}

I can't find any documentation on this difference, so before introducing platform specific code in production, I would like to be sure it is developed as intented.

Thank you very much!

Logs

{
  "android": {
    "advName": "advName",
    "txPowerLevel": null,
    "appearance": null,
    "connectable": true,
    "manufacturerData": {
      "0": [5, 54 /* ... */],
      "12345": []
    },
    "msd": [
      [0, 0, 5, 54 /* ... */],
      [48, 57]
    ],
    "serviceData": {},
    "serviceUuids": []
  },

  "ios": {
    "advName": "advName",
    "txPowerLevel": null,
    "appearance": null,
    "connectable": true,
    "manufacturerData": {
      "12345": [0, 0, 5, 54 /* ... */]
    },
    "msd": [[48, 57, 0, 0, 5, 54 /* ... */]],
    "serviceData": {},
    "serviceUuids": []
  }
}
chipweinberger commented 16 hours ago

they should not be different. please open a PR if there is a bug.

msd = list of full raw data, split by manufacturer manufacturerData = map of <manufactureId, data without manufactureId>

chipweinberger commented 11 hours ago

also, where is this json coming from?

please print out the advertisement data directly and report back

print(advertisementData)

  @override
  String toString() {
    return 'AdvertisementData{'
        'advName: $advName, '
        'txPowerLevel: $txPowerLevel, '
        'appearance: $appearance, '
        'connectable: $connectable, '
        'manufacturerData: $manufacturerData, '
        'serviceData: $serviceData, '
        'serviceUuids: $serviceUuids'
        '}';
  }
P0labrD commented 6 hours ago

I wrote the json with what I saw in my debugger, anonymizing the data Here's the real (still anonymized) print ouput for iOS:

flutter: AdvertisementData{advName: product-name, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {12345: [0, 0, 5, 54, 48, 48, 85, 48, 54, 57, 48, 57, 50, 49]}, serviceData: {}, serviceUuids: []}

And for Android:

I/flutter (19040): AdvertisementData{advName: product-name, txPowerLevel: null, appearance: null, connectable: true, manufacturerData: {0: [5, 54, 48, 48, 85, 48, 54, 57, 48, 57, 50, 49], 12345: []}, serviceData: {}, serviceUuids: []}

So if I understand correctly, my device is seen with one manufacturerId 12345 for iOS, but with two for android, 0 and 12345, but with the rawData being on the wrong side

I'm not a hardware developer myself, but I could ask the one who did develop on the chip what he implemented in order to give good reproduction info.

Here's the plugin method I call to start scanning:

FlutterBluePlus.startScan(
  timeout: timeout,
  withMsd: [MsdFilter(12345)],
  removeIfGone: const Duration(seconds: 3),
  continuousUpdates: true,
);
chipweinberger commented 3 hours ago

the android code is here. you should debug it further :)

particularly, you should print the result of : byte[] bytes = adv.getBytes();

it probably has a bug. iOS is probably correct.

    Map<Integer, byte[]> getManufacturerSpecificData(ScanRecord adv) {
        byte[] bytes = adv.getBytes();
        Map<Integer, byte[]> manufacturerDataMap = new HashMap<>();
        int n = 0;
        while (n < bytes.length) {

            // layout:
            // n[0] = fieldlen
            // n[1] = datatype (MSD)
            // n[2] = manufacturerId (low)
            // n[3] = manufacturerId (high)
            // n[4] = data...
            int fieldLen = bytes[n] & 0xFF;

            // no more or malformed data
            if (fieldLen <= 0) {
                break;
            }

            // Ensuring we don't go past the bytes array
            if (n + fieldLen >= bytes.length) {
                break;
            }

            int dataType = bytes[n + 1] & 0xFF;

            // Manufacturer Specific Data magic number
            // At least 3 bytes: 2 for manufacturer ID & 1 for dataType
            if (dataType == 0xFF && fieldLen >= 3) {

                // Manufacturer Id
                int high = (bytes[n + 3] & 0xFF) << 8;
                int low = (bytes[n + 2] & 0xFF);
                int manufacturerId = high | low;

                // the length of the msd data,
                // excluding manufacturerId & dataType
                int msdLen = fieldLen - 3;

                // ptr to msd data
                // excluding manufacturerId & dataType
                int msdPtr = n + 4;

                // add to map
                if (manufacturerDataMap.containsKey(manufacturerId)) {
                    // If the manufacturer ID already exists, append the new data to the existing list
                    byte[] existingData = manufacturerDataMap.get(manufacturerId);
                    byte[] mergedData = new byte[existingData.length + msdLen];
                    // Merge arrays
                    System.arraycopy(existingData, 0, mergedData, 0, existingData.length);
                    System.arraycopy(bytes, msdPtr, mergedData, existingData.length, msdLen);
                    manufacturerDataMap.put(manufacturerId, mergedData);
                } else {
                    // Otherwise, put the new manufacturer ID and its data into the map
                    byte[] data = new byte[msdLen];
                    // Starting from n+4 because manufacturerId occupies n+2 and n+3
                    System.arraycopy(bytes, msdPtr, data, 0, data.length);
                    manufacturerDataMap.put(manufacturerId, data);
                }
            }

            n += fieldLen + 1;
        }

        return manufacturerDataMap;
    }
chipweinberger commented 2 hours ago

@MrCsabaToth