From 8815eb7b88110b3e17693eb2803230a4e231e18a Mon Sep 17 00:00:00 2001 From: erhannis Date: Fri, 28 Jul 2023 12:25:51 -0400 Subject: [PATCH 1/3] Fixes, null check, averted some exceptions, added too many logs --- .../com/example/quick_blue/QuickBluePlugin.kt | 194 +++++++++--------- .../lib/src/method_channel_quick_blue.dart | 2 +- .../quick_blue/windows/quick_blue_plugin.cpp | 64 +++++- 3 files changed, 165 insertions(+), 95 deletions(-) diff --git a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt index a096066..5174448 100644 --- a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt +++ b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt @@ -76,100 +76,108 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand fun trace() = Arrays.toString(Throwable().stackTrace) override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - when (call.method) { - "isBluetoothAvailable" -> { - result.success(bluetoothManager.adapter.isEnabled) - } - "startScan" -> { - bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback) - result.success(null) - } - "stopScan" -> { - bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback) - result.success(null) - } - "connect" -> { - val deviceId = call.argument("deviceId")!! - if (knownGatts.find { it.device.address == deviceId } != null) { - return result.success(null) + try { + when (call.method) { + "isBluetoothAvailable" -> { + result.success(bluetoothManager.adapter.isEnabled) } - val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId) - val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - remoteDevice.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) - } else { - remoteDevice.connectGatt(context, false, gattCallback) + "startScan" -> { + bluetoothManager.adapter.bluetoothLeScanner?.startScan(scanCallback) + result.success(null) } - knownGatts.add(gatt) - result.success(null) - // TODO connecting - } - "disconnect" -> { - val deviceId = call.argument("deviceId")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - cleanConnection(gatt) - result.success(null) - //FIXME If `disconnect` is called before BluetoothGatt.STATE_CONNECTED - // there will be no `disconnected` message any more - } - "discoverServices" -> { - val deviceId = call.argument("deviceId")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - gatt.discoverServices() - result.success(null) - } - "setNotifiable" -> { - val deviceId = call.argument("deviceId")!! - val service = call.argument("service")!! - val characteristic = call.argument("characteristic")!! - val bleInputProperty = call.argument("bleInputProperty")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - val c = gatt.getCharacteristic(service, characteristic) - ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) - gatt.setNotifiable(c, bleInputProperty) - result.success(null) - } - "readValue" -> { - val deviceId = call.argument("deviceId")!! - val service = call.argument("service")!! - val characteristic = call.argument("characteristic")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - val c = gatt.getCharacteristic(service, characteristic) - ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) - if (gatt.readCharacteristic(c)) + "stopScan" -> { + bluetoothManager.adapter.bluetoothLeScanner?.stopScan(scanCallback) result.success(null) - else - result.error("Characteristic unavailable", null, trace()) - } - "writeValue" -> { - val deviceId = call.argument("deviceId")!! - val service = call.argument("service")!! - val characteristic = call.argument("characteristic")!! - val value = call.argument("value")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - val c = gatt.getCharacteristic(service, characteristic) - ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) - c.value = value - if (gatt.writeCharacteristic(c)) + } + "connect" -> { + val deviceId = call.argument("deviceId")!! + if (knownGatts.find { it.device.address == deviceId } != null) { + return result.success(null) + } + val remoteDevice = bluetoothManager.adapter.getRemoteDevice(deviceId) + val gatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + remoteDevice.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) + } else { + remoteDevice.connectGatt(context, false, gattCallback) + } + knownGatts.add(gatt) result.success(null) - else - result.error("Characteristic unavailable", null, trace()) - } - "requestMtu" -> { - val deviceId = call.argument("deviceId")!! - val expectedMtu = call.argument("expectedMtu")!! - val gatt = knownGatts.find { it.device.address == deviceId } - ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) - gatt.requestMtu(expectedMtu) - result.success(null) - } - else -> { - result.notImplemented() + // TODO connecting + } + "disconnect" -> { + val deviceId = call.argument("deviceId")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + cleanConnection(gatt) + result.success(null) + //FIXME If `disconnect` is called before BluetoothGatt.STATE_CONNECTED + // there will be no `disconnected` message any more + } + "discoverServices" -> { + val deviceId = call.argument("deviceId")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + gatt.discoverServices() + result.success(null) + } + "setNotifiable" -> { + val deviceId = call.argument("deviceId")!! + val service = call.argument("service")!! + val characteristic = call.argument("characteristic")!! + val bleInputProperty = call.argument("bleInputProperty")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + val s = gatt.getService(UUID.fromString(service)) + ?: return result.error("IllegalArgument", "Unknown service: $service", trace()) + val c = s.getCharacteristic(UUID.fromString(characteristic)) + ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) + gatt.setNotifiable(c, bleInputProperty) + result.success(null) + } + "readValue" -> { + val deviceId = call.argument("deviceId")!! + val service = call.argument("service")!! + val characteristic = call.argument("characteristic")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + val c = gatt.getCharacteristic(service, characteristic) + ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) + if (gatt.readCharacteristic(c)) + result.success(null) + else + result.error("Characteristic unavailable", null, trace()) + } + "writeValue" -> { + val deviceId = call.argument("deviceId")!! + val service = call.argument("service")!! + val characteristic = call.argument("characteristic")!! + val value = call.argument("value")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + val c = gatt.getCharacteristic(service, characteristic) + ?: return result.error("IllegalArgument", "Unknown characteristic: $characteristic", trace()) + c.value = value + if (gatt.writeCharacteristic(c)) { + result.success(null) + } else { + result.error("Characteristic unavailable", null, trace()) + } + } + "requestMtu" -> { + val deviceId = call.argument("deviceId")!! + val expectedMtu = call.argument("expectedMtu")!! + val gatt = knownGatts.find { it.device.address == deviceId } + ?: return result.error("IllegalArgument", "Unknown deviceId: $deviceId", trace()) + gatt.requestMtu(expectedMtu) + result.success(null) + } + else -> { + result.notImplemented() + } } + } catch (e: Throwable) { + e.printStackTrace() + result.error("Error", "Error", trace()) } } @@ -335,7 +343,7 @@ fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.LITTLE_ENDIAN): ByteArray ByteBuffer.allocate(2 /*Short.SIZE_BYTES*/).order(byteOrder).putShort(this).array() fun BluetoothGatt.getCharacteristic(service: String, characteristic: String): BluetoothGattCharacteristic? = - getService(UUID.fromString(service)).getCharacteristic(UUID.fromString(characteristic)) + getService(UUID.fromString(service))?.getCharacteristic(UUID.fromString(characteristic)) private val DESC__CLIENT_CHAR_CONFIGURATION = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") @@ -346,6 +354,8 @@ fun BluetoothGatt.setNotifiable(gattCharacteristic: BluetoothGattCharacteristic, "indication" -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE to true else -> BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE to false } - descriptor.value = value - setCharacteristicNotification(descriptor.characteristic, enable) && writeDescriptor(descriptor) + if (setCharacteristicNotification(gattCharacteristic, enable) && descriptor != null) { + descriptor.value = value + writeDescriptor(descriptor) + } } \ No newline at end of file diff --git a/packages/quick_blue/lib/src/method_channel_quick_blue.dart b/packages/quick_blue/lib/src/method_channel_quick_blue.dart index 68b3809..8242cc4 100644 --- a/packages/quick_blue/lib/src/method_channel_quick_blue.dart +++ b/packages/quick_blue/lib/src/method_channel_quick_blue.dart @@ -86,7 +86,7 @@ class MethodChannelQuickBlue extends QuickBluePlatform { if (message['ServiceState'] == 'discovered') { String deviceId = message['deviceId']; String service = message['service']; - List characteristics = (message['characteristics'] as List).cast(); + List characteristics = ((message['characteristics'] ?? []) as List).cast(); onServiceDiscovered?.call(deviceId, service, characteristics); } } else if (message['characteristicValue'] != null) { diff --git a/packages/quick_blue/windows/quick_blue_plugin.cpp b/packages/quick_blue/windows/quick_blue_plugin.cpp index 9ec7456..849d7a4 100644 --- a/packages/quick_blue/windows/quick_blue_plugin.cpp +++ b/packages/quick_blue/windows/quick_blue_plugin.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include +#include + #define GUID_FORMAT "%08x-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" #define GUID_ARG(guid) guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7] @@ -88,15 +92,22 @@ struct BluetoothDeviceAgent { } IAsyncOperation GetServiceAsync(std::string service) { + std::cerr << "--> GetServiceAsync\n"; if (gattServices.count(service) == 0) { auto serviceResult = co_await device.GetGattServicesAsync(); - if (serviceResult.Status() != GattCommunicationStatus::Success) + if (serviceResult.Status() != GattCommunicationStatus::Success) { + std::cerr << "<-E GetServiceAsync\n"; co_return nullptr; + } for (auto s : serviceResult.Services()) - if (to_uuidstr(s.Uuid()) == service) + if (to_uuidstr(s.Uuid()) == service) { + std::wcerr << ((L"gattServices addr: " + std::to_wstring((unsigned long long)&gattServices) + L"\n").c_str()); + std::wcerr << ((L"gattServices size: " + std::to_wstring(gattServices.size()) + L"\n").c_str()); gattServices.insert(std::make_pair(service, s)); + } } + std::cerr << "<-S GetServiceAsync\n"; co_return gattServices.at(service); } @@ -238,14 +249,21 @@ winrt::fire_and_forget QuickBluePlugin::InitializeAsync() { } } +//void dbg(std::wstring s) { +// std::cerr +//} + void QuickBluePlugin::HandleMethodCall( const flutter::MethodCall &method_call, std::unique_ptr> result) { auto method_name = method_call.method_name(); OutputDebugString((L"HandleMethodCall " + winrt::to_hstring(method_name) + L"\n").c_str()); if (method_name.compare("isBluetoothAvailable") == 0) { + std::wcerr << (L"--> isBluetoothAvailable\n"); result->Success(EncodableValue(bluetoothRadio && bluetoothRadio.State() == RadioState::On)); + std::wcerr << (L"<-S isBluetoothAvailable\n"); } else if (method_name.compare("startScan") == 0) { + std::wcerr << (L"--> startScan\n"); if (bluetoothRadio && bluetoothRadio.State() == RadioState::On) { if (!bluetoothLEWatcher) { bluetoothLEWatcher = BluetoothLEAdvertisementWatcher(); @@ -253,10 +271,13 @@ void QuickBluePlugin::HandleMethodCall( } bluetoothLEWatcher.Start(); result->Success(nullptr); + std::wcerr << (L"<-S startScan\n"); } else { result->Error("IllegalState", "Bluetooth unavailable"); + std::wcerr << (L"<-E startScan\n"); } } else if (method_name.compare("stopScan") == 0) { + std::wcerr << (L"--> stopScan\n"); if (bluetoothRadio && bluetoothRadio.State() == RadioState::On) { if (bluetoothLEWatcher) { bluetoothLEWatcher.Stop(); @@ -264,31 +285,49 @@ void QuickBluePlugin::HandleMethodCall( } bluetoothLEWatcher = nullptr; result->Success(nullptr); + std::wcerr << (L"<-S stopScan\n"); } else { result->Error("IllegalState", "Bluetooth unavailable"); + std::wcerr << (L"<-E stopScan\n"); } } else if (method_name.compare("connect") == 0) { + std::wcerr << (L"--> connect\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); + OutputDebugStringW(L"asdf X\n"); + std::cerr << ("asdf 0\n"); + std::wcerr << (L"asdf 0b\n"); + std::wstring wsTmp(deviceId.begin(), deviceId.end()); + std::wcerr << (L"asdf 1\n"); + std::wcerr << ((L"connect bluetoothAddress string: " + wsTmp + L"\n").c_str()); + std::wcerr << ((L"connect bluetoothAddress stoull: " + std::to_wstring(std::stoull(deviceId)) + L"\n").c_str()); + std::wcerr << (L"asdf 2\n"); ConnectAsync(std::stoull(deviceId)); result->Success(nullptr); + std::wcerr << (L"<-S connect\n"); } else if (method_name.compare("disconnect") == 0) { + std::wcerr << (L"--> disconnect\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); CleanConnection(std::stoull(deviceId)); // TODO send `disconnected` message result->Success(nullptr); + std::wcerr << (L"<-S disconnect\n"); } else if (method_name.compare("discoverServices") == 0) { + std::wcerr << (L"--> discoverServices\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); auto it = connectedDevices.find(std::stoull(deviceId)); if (it == connectedDevices.end()) { result->Error("IllegalArgument", "Unknown devicesId:" + deviceId); + std::wcerr << (L"<-E discoverServices\n"); return; } DiscoverServicesAsync(*it->second); result->Success(nullptr); + std::wcerr << (L"<-S discoverServices\n"); } else if (method_name.compare("setNotifiable") == 0) { + std::wcerr << (L"--> setNotifiable\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); auto service = std::get(args[EncodableValue("service")]); @@ -297,6 +336,7 @@ void QuickBluePlugin::HandleMethodCall( auto it = connectedDevices.find(std::stoull(deviceId)); if (it == connectedDevices.end()) { result->Error("IllegalArgument", "Unknown devicesId:" + deviceId); + std::wcerr << (L"<-E setNotifiable\n"); return; } @@ -308,12 +348,15 @@ void QuickBluePlugin::HandleMethodCall( auto c = sender.GetResults(); if (c == nullptr) { result_pointer->Error("IllegalArgument", "Unknown characteristic:" + characteristic); + std::wcerr << (L"<-E setNotifiable 2\n"); return; } SetNotifiableAsync(bluetoothAgent, c, bleInputProperty); result_pointer->Success(nullptr); + std::wcerr << (L"<-S setNotifiable\n"); }); } else if (method_name.compare("readValue") == 0) { + std::wcerr << (L"--> readValue\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); auto service = std::get(args[EncodableValue("service")]); @@ -321,6 +364,7 @@ void QuickBluePlugin::HandleMethodCall( auto it = connectedDevices.find(std::stoull(deviceId)); if (it == connectedDevices.end()) { result->Error("IllegalArgument", "Unknown devicesId:" + deviceId); + std::wcerr << (L"<-E readValue\n"); return; } @@ -332,12 +376,15 @@ void QuickBluePlugin::HandleMethodCall( auto c = sender.GetResults(); if (c == nullptr) { result_pointer->Error("IllegalArgument", "Unknown characteristic:" + characteristic); + std::wcerr << (L"<-E readValue\n"); return; } ReadValueAsync(c); result_pointer->Success(nullptr); + std::wcerr << (L"<-S readValue\n"); }); } else if (method_name.compare("writeValue") == 0) { + std::wcerr << (L"--> writeValue\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); auto service = std::get(args[EncodableValue("service")]); @@ -347,6 +394,7 @@ void QuickBluePlugin::HandleMethodCall( auto it = connectedDevices.find(std::stoull(deviceId)); if (it == connectedDevices.end()) { result->Error("IllegalArgument", "Unknown devicesId:" + deviceId); + std::wcerr << (L"<-E writeValue\n"); return; } @@ -358,25 +406,32 @@ void QuickBluePlugin::HandleMethodCall( auto c = sender.GetResults(); if (c == nullptr) { result_pointer->Error("IllegalArgument", "Unknown characteristic:" + characteristic); + std::wcerr << (L"<-E writeValue\n"); return; } WriteValueAsync(c, value, bleOutputProperty); result_pointer->Success(nullptr); + std::wcerr << (L"<-S writeValue\n"); }); } else if (method_name.compare("requestMtu") == 0) { + std::wcerr << (L"--> requestMtu\n"); auto args = std::get(*method_call.arguments()); auto deviceId = std::get(args[EncodableValue("deviceId")]); auto expectedMtu = std::get(args[EncodableValue("expectedMtu")]); auto it = connectedDevices.find(std::stoull(deviceId)); if (it == connectedDevices.end()) { result->Error("IllegalArgument", "Unknown devicesId:" + deviceId); + std::wcerr << (L"<-E requestMtu\n"); return; } RequestMtuAsync(*it->second, expectedMtu); result->Success(nullptr); + std::wcerr << (L"<-S requestMtu\n"); } else { + std::wcerr << (L"--> NotImplemented\n"); result->NotImplemented(); + std::wcerr << (L"<-- NotImplemented\n"); } } @@ -482,7 +537,12 @@ std::unique_ptr> QuickBluePlugin::On } winrt::fire_and_forget QuickBluePlugin::ConnectAsync(uint64_t bluetoothAddress) { + std::wcerr << ((L"ConnectAsync bluetoothAddress: " + std::to_wstring(bluetoothAddress) + L"\n").c_str()); auto device = co_await BluetoothLEDevice::FromBluetoothAddressAsync(bluetoothAddress); + if (!device) { + std::wcerr << ((L"ConnectAsync null device! "+ std::to_wstring(bluetoothAddress) +L"\n").c_str()); + co_return; + } auto servicesResult = co_await device.GetGattServicesAsync(); if (servicesResult.Status() != GattCommunicationStatus::Success) { OutputDebugString((L"GetGattServicesAsync error: " + winrt::to_hstring((int32_t)servicesResult.Status()) + L"\n").c_str()); From b8b6ae306050320d4d9eb3ec43018ee23714ba13 Mon Sep 17 00:00:00 2001 From: erhannis Date: Fri, 28 Jul 2023 21:08:49 -0400 Subject: [PATCH 2/3] Permissions weren't showing up on phones I tested, but logs claim they were needed, so I removed the sdk limits and now it works. --- .../android/src/main/AndroidManifest.xml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/quick_blue/android/src/main/AndroidManifest.xml b/packages/quick_blue/android/src/main/AndroidManifest.xml index da336fa..5c9a8aa 100644 --- a/packages/quick_blue/android/src/main/AndroidManifest.xml +++ b/packages/quick_blue/android/src/main/AndroidManifest.xml @@ -1,19 +1,13 @@ - + - - - + + + - + \ No newline at end of file From 349a3c24535638b6fcfa1f6e7d1eb537e1c4e07d Mon Sep 17 00:00:00 2001 From: erhannis Date: Fri, 28 Jul 2023 21:10:23 -0400 Subject: [PATCH 3/3] Removed null unwrap that crashed; there's a null check immediately after anyway --- .../src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt index 8f595fa..337a477 100644 --- a/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt +++ b/packages/quick_blue/android/src/main/kotlin/com/example/quick_blue/QuickBluePlugin.kt @@ -86,7 +86,7 @@ class QuickBluePlugin: FlutterPlugin, MethodCallHandler, EventChannel.StreamHand result.success(bluetoothManager.adapter.isEnabled) } "startScan" -> { - val serviceUUIDs = call.argument>("serviceUUIDs")!! + val serviceUUIDs = call.argument>("serviceUUIDs") if (serviceUUIDs != null && serviceUUIDs.size > 0) { val filters: ArrayList = ArrayList() for (serviceUUID in serviceUUIDs) {