woodemi / quick_blue

A cross-platform (Android/iOS/macOS/Windows/Linux) BluetoothLE plugin for Flutter
137 stars 70 forks source link

[Windows] Crash on writeValue #90

Open ivs opened 2 years ago

ivs commented 2 years ago

Hi! This code gives me sigterm on Windows:

void handleConnection(String deviceId, BlueConnectionState state) {
    d.log('handleConnection ' + state.value);
    QuickBlue.discoverServices(deviceId);
    QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        BleInputProperty.notification);
    QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        Uint8List.fromList(key_cmd + key), BleOutputProperty.withResponse);
  }

  void handleService(String deviceId, String serviceId) {
    d.log('handleService ' + serviceId);
  }

  void handleValue(String deviceId, String characteristicId, Uint8List value) {
    d.log('handleValue ' + characteristicId);
  }

  @override
  void initState() {
    super.initState();
    QuickBlue.setConnectionHandler(handleConnection);
    QuickBlue.setServiceHandler(handleService);
    QuickBlue.setValueHandler(handleValue);

    QuickBlue.scanResultStream.listen((result) {
      d.log('onScanResult ' + result.name);

      if (result.name == "Mi Band 3" && !bandFound) {
        bandFound = true;

        d.log('onScanResult ' + result.name);
        QuickBlue.stopScan();
        QuickBlue.connect(result.deviceId);
      }
    });

    QuickBlue.startScan();
  }

The output:

Launching lib\main.dart on Windows in debug mode...
lib\main.dart:1
Connecting to VM Service at w[s://127.0.0.1:56503]()/sOU5DQmnXDg=/ws
flutter: startScan invokeMethod success
4[log] onScanResult [Monitor] Samsung M7 (32)
[log] onScanResult Amazfit GTS2 mini
[log] onScanResult [Monitor] Samsung M7 (32)
flutter: stopScan invokeMethod success
2[log] onScanResult Mi Band 3
[log] handleConnection connected
Lost connection to device.
Exited (sigterm)

How I can debug it? I'm new to BLE maybe I do something wrong. I'm trying to port this project: https://github.com/4lhc/MiBand_HRX/blob/master/base.py#L103

Is setNotifiable with BleInputProperty.notification the same as writing 0x01 to 0x2902 descriptor?

There's no crash if I set BleOutputProperty.withoutResponse for writeValue but no response from device too.

Sunbreak commented 2 years ago

Could you post flutter doctor -v?

Try DebugView if you need OutputDebugString result, for example:

https://github.com/woodemi/quick_blue/blob/acc21420688b9b6e3c099cb6c2ed400731d89709/quick_blue_windows/windows/quick_blue_windows_plugin.cpp#L221

ivs commented 2 years ago
> flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.10.4, on Microsoft Windows [Version 10.0.22000.613], locale ru-RU)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[√] Chrome - develop for the web
[√] Visual Studio - develop for Windows (Visual Studio Community 2019 16.11.9)
[√] Android Studio (version 2020.3)
[√] VS Code (version 1.66.2)
[√] Connected device (3 available)
    ! Device emulator-5562 is offline.
[√] HTTP Host Availability

• No issues found!

DebugView logs:

[39572] HandleMethodCall startScan
[39572] Received BluetoothAddress:207251698759179, Name:Bluetooth bc:7e:8b:e2:36:0b, LocalName:
[39572] Received BluetoothAddress:207251698759179, Name:Bluetooth bc:7e:8b:e2:36:0b, LocalName:
[39572] Received BluetoothAddress:207251698759179, Name:Bluetooth bc:7e:8b:e2:36:0b, LocalName:
[39572] Received BluetoothAddress:207251698759179, Name:Bluetooth bc:7e:8b:e2:36:0b, LocalName:
[39572] Received BluetoothAddress:207251698759179, Name:Bluetooth bc:7e:8b:e2:36:0b, LocalName:
[39572] Received BluetoothAddress:257642978838462, Name:Mi Band 3, LocalName:
[39572] HandleMethodCall stopScan
[39572] HandleMethodCall connect
[39572] HandleMethodCall discoverServices
[39572] HandleMethodCall setNotifiable
[39572] HandleMethodCall writeValue
[39572] ConnectionStatusChanged 1
Sunbreak commented 2 years ago

Is setNotifiable with BleInputProperty.notification the same as writing 0x01 to 0x2902 descriptor?

I think so according to Android implementation of setNotifiable

https://github.com/woodemi/quick_blue/blob/acc21420688b9b6e3c099cb6c2ed400731d89709/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt#L287-L298

Sunbreak commented 2 years ago
    QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        BleInputProperty.notification);
    QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        Uint8List.fromList(key_cmd + key), BleOutputProperty.withResponse);

You'd better await the result

    await QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        BleInputProperty.notification);
    await QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        Uint8List.fromList(key_cmd + key), BleOutputProperty.withResponse);
Sunbreak commented 2 years ago
    QuickBlue.discoverServices(deviceId);
    QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        BleInputProperty.notification);
    QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
        Uint8List.fromList(key_cmd + key), BleOutputProperty.withResponse);

Try this on Android before Windows. Android BLE is much reasonable

ivs commented 2 years ago

The same crash with await. I've tried to debug plugin with Visual Studio, found out this method: https://stackoverflow.com/questions/71406447/flutter-desktop-windows-plugin-debug-native-c-code I copied my code into quick_blue_example app and attach VS debugger, it gives me this stack trace: image Maybe it helps.

Seems I'm unlucky user:) Android code do not work at all, my device Realme SuperZoom, RMX2086. QuickBlue.scanResultStream.listen lamda do not called neither in my app nor in quick_blue_example app(windows build works just fine), this breakpoint never stops: image

If you point me how, I can debug android and windows code.

Sunbreak commented 2 years ago

Could you try below APPs on Android as peripheral?

ivs commented 2 years ago

Finally I resolved all my issues! Android code didn't work because of permissions. Complete list of permission for AndroidManifest.xml

<uses-permission android:name="android.permission.BLUETOOTH" />  
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />  
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />  
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>  

I got permissions exception on another phone TECNO CC7 and I have to give Location permission manually in App Info.

The crash reproduced on Android too. I've fixed it by adding await and delays. It seems my device needs some time to respond and without delays it disconnected and cause library crash.

This code works both on Windows and Android:

void mibandAuth(String deviceId) async {
  await QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2,
      CHARACTERISTIC_AUTH, BleInputProperty.notification);
  await Future.delayed(const Duration(seconds: 1));
  await QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
      Uint8List.fromList(key_cmd + key), BleOutputProperty.withoutResponse);
  await Future.delayed(const Duration(seconds: 1));
}

void handleConnection(String deviceId, BlueConnectionState state) {
  d.log('handleConnection $deviceId');
  if (defaultTargetPlatform != TargetPlatform.windows) {
    QuickBlue.discoverServices(deviceId);
  } else {
    mibandAuth(deviceId);
  }
}

void handleService(String deviceId, String serviceId) {
  d.log('handleService $serviceId');
  if (serviceId == SERVICE_MIBAND2) {
    mibandAuth(deviceId);
  }
}

void handleValue(String deviceId, String characteristicId, Uint8List value) {
  d.log('handleValue $characteristicId $value');
}

@override
void initState() {
  super.initState();

  QuickBlue.setConnectionHandler(handleConnection);
  QuickBlue.setServiceHandler(handleService);
  QuickBlue.setValueHandler(handleValue);
  QuickBlue.isBluetoothAvailable();
  QuickBlue.scanResultStream.listen((result) {
    d.log('onScanResult ' + result.name);
    if (result.name == "Mi Band 3" && !bandFound) {
      bandFound = true;

      QuickBlue.stopScan();
      QuickBlue.connect(result.deviceId);
    }
  });

  QuickBlue.startScan();
}

Is this right way to use library? If it so I can make an example from it to library README.md.

Sunbreak commented 2 years ago

Android code didn't work because of permissions. Complete list of permission for AndroidManifest.xml

https://github.com/woodemi/quick_blue/commit/e907904bfc10163f32e345577d5151d8de7b824a is here

I don't think <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> is needed

Sunbreak commented 2 years ago
void mibandAuth(String deviceId) async {
  await QuickBlue.setNotifiable(deviceId, SERVICE_MIBAND2,
      CHARACTERISTIC_AUTH, BleInputProperty.notification);
  await Future.delayed(const Duration(seconds: 1));
  await QuickBlue.writeValue(deviceId, SERVICE_MIBAND2, CHARACTERISTIC_AUTH,
      Uint8List.fromList(key_cmd + key), BleOutputProperty.withoutResponse);
  await Future.delayed(const Duration(seconds: 1));
}

When using with our product authentication on Windows, Future.delayed is not needed. Maybe a debug vs profile/release problem?

Sunbreak commented 2 years ago

I got permissions exception on another phone TECNO CC7 and I have to give Location permission manually in App Info.

If it so I can make an example from it to library README.md.

Agreed. Location permission on pre-Android-12 is quite cumbersome and needs documentation

https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android12-or-higher

ot-repo commented 2 years ago

Hello,

I got the same error, when I use writeValue on Windows with more than 233 chracters. By 234 characters the app is crashing with Exited (sigterm)

That's why I chunk my data into 233 characters and send them to device as follows:

var chunks = _chunkData(data, 233);
for (var chunk in chunks) {
    await QuickBlue.writeValue(
      address,
      "49535343-fe7d-4ae5-8fa9-9fafd205e455",
      "49535343-8841-43f4-a8d4-ecbe34729bb3",
      Uint8List.fromList(chunk),
      BleOutputProperty.withoutResponse,
    );
    await Future.delayed(Duration(milliseconds: 250)); // this delay is only necessary on windows
}

static _chunkData(List<int> list, int chunkSize) {
    var chunks = [];
    for (var i = 0; i < list.length; i += chunkSize) {
      chunks.add(list.sublist(i, i + chunkSize > list.length ? list.length : i + chunkSize));
    }

    return chunks;
  }

The code above works perfectly on Android, IOS and MacOS. However on Windows, if I do not wait the tread, although I await the writeValue call, the chunks will not be received in the same order. For example, the second chunk will be printed before the first chunk. I beleive the function writeValue on Windows is not really async.

The questions here are, why the characters I can send to the device is limited on all platforms? why writeValue is not behaving as a real async function on windows?

Is there anything better I can try?

Thank you!

Sunbreak commented 2 years ago

https://github.com/woodemi/quick_blue/blob/1083edc15b0076a929969987fe3a72b5ce71350b/quick_blue_windows/windows/quick_blue_windows_plugin.cpp#L274-L285

Some useful doc

ot-repo commented 2 years ago

Thank you for the quick answer and links.

I really don't understand what requestMtu is or whether I should call it before calling writeValue method.

I have seen that the WriteValueAsync method is not awaited. Could this be the reason of the problem which is the data not received in the same order I send it via writeValue?

https://github.com/woodemi/quick_blue/blob/1083edc15b0076a929969987fe3a72b5ce71350b/quick_blue_windows/windows/quick_blue_windows_plugin.cpp#L312-L313

Sunbreak commented 2 years ago

I got the same error, when I use writeValue on Windows with more than 233 chracters. By 234 characters the app is crashing with Exited (sigterm)

That is what MTU means

Sunbreak commented 2 years ago

I have seen that the WriteValueAsync method is not awaited. Could this be the reason of the problem which is the data not received in the same order I send it via writeValue?

We're considering add onValuteWrite notification https://github.com/woodemi/quick.flutter/pull/36

ot-repo commented 2 years ago

Nice idea! When are you planing to merge the PR?

I haven't seen the window implementation. Have I missed it?

dlewis2017 commented 1 year ago

@ivs The delay worked! Thanks!